]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/tharre/seccon19.md
Add tharre/seccon19.md
[pub/jan/ctf-seminar.git] / writeups / tharre / seccon19.md
1 # SECCON 2019 Online CTF - pwn one
2
3 ## Overview
4
5 This was probably the hardest challange I've done during the course, besides
6 engine which was unsolvable anyway. It starts relatively simple, it's a pwn
7 challange and you get a binary, `one_ef36d5ef6169aeda65259f627f282930b93cf6e5`
8 and `libc-2.27.so_18292bd12d37bfaf58e8dded9db7f1f5da1192cb`. When running the
9 binary you're presented with four options, add, show, delete a memo or exit the
10 program.
11
12 ## Find the vulnerability
13
14 After playing around with it a bit you find a bug rather quickly, adding a memo
15 and then deleting it twice produces a double free:
16
17 ```
18 free(): double free detected in tcache 2
19 [1]    43966 abort (core dumped)  ./one_ef36d5ef6169aeda65259f627f282930b93cf6e5
20 ```
21
22 This alone would not really be exploitable, however this was run directly on my
23 updated Arch Linux machine which was running `glibc-2.30`. Since there was
24 suspiciously also a `libc-2.27.so` included in the challange it was rather
25 obvious that it was meant to only be exploitable with that or older versions.
26 And indeed if you google `glibc 2.27 double free` you get tons of results
27 describing exploitation methods related to tcache double-free.
28
29 ## Trying to build an exploit
30
31 So we know what vulnerability we want to exploit and there's tons of additional
32 material available describing similar exploits so this should be easy right?
33
34 Well, not so fast. First of all we need to actually run the binary with
35 `libc-2.27`. I tried for quite a bit to actually run the thing on my machine
36 directly with commands like `LD_LIBRARY_PATH=$(pwd) ./ld-linux-x86-64.so.2
37 ./one_ef36d5ef6169aeda65259f627f282930b93cf6e5` but couldn't get it to work in a
38 timely fashion, so I just settled on working in a Ubuntu 18.04 docker container
39 after reading `cluosh` hint that they use the same libc version.
40
41 The next difficulty was actually applying what I've read about tcache's freelist
42 to write to arbitrary addresses in memory. The details of how this works are
43 quite complicated, I probably don't understand them fully myself either, but the
44 gist of it is that you can trick glibc into returning a pointer to a memory
45 region that was never part of the original heap region that glibc uses and that
46 we can control. This happens because after a double free there's a loop in the
47 tcache freelist, the linked-list that holds available memory chunks, which
48 causes the chunk we recieve from `malloc()` to not be removed from the freelist.
49 We can then write to this address by adding a memo, where the first 8 byte will
50 override the next pointer, which we can point to any memory region we want. Now
51 we just need to do force two more allocations, one to reach our crafted `next`
52 pointer and one to actually get back our target pointer.
53
54 So to write value to an arbitrary pointer `ptr`, we just do `add -> add(ptr) ->
55 add -> add(value)`.
56
57 ## ASLR makes everything hard
58
59 This is were I ultimately got stuck. So we can write to arbitrary memory
60 locations, how do we actually turn this into a shell? As `cluosh` also noted on
61 mattermost, we can't override GOT entries since the binary is full-relro, so
62 probably the easiest way to exploit this is by overriding glibc hooks,
63 especially `__free_hook`. And indeed with ASLR turned off and a debugger (and
64 some fiddling) attached to get the memory address of `__free_hook` I managed to
65 successfully override the hook.
66
67 With ASLR turned on not so much. I did manage to find a information leak by
68 doing `add -> add -> delete -> delete -> show`, however I ultimately failed to
69 do anything useful with the address I leaked. Even worse, leaking the address
70 made the rest of the exploit fail. The reason for this is actually rather
71 obvious in hindsight, adding a memo twice meant that the tcache was getting
72 messed up. Just doing `add -> delete -> delete -> delete -> delete -> show`
73 would've been better.
74
75 Looking at writeups from other people, the rest would actually have been doable
76 as well, I really just failed at properly determining the offset to the libc
77 base address.
78
79 I spend around 35h on this challange, most of it after the original challange
80 already ended though since SECCONs timeframe was so short.