文章总结: 本文档详细解析了2026年天府杯Pwn比赛的零解题Writeup。文章逆向了程序逻辑,分析了基于输入计算的堆分配机制,揭示了隐藏的Show后门功能及内存对齐漏洞。通过构造特定的堆块布局和伪造堆地址绕过非法指针检查,作者给出了最终的Exploit利用思路和脚本,展示了二进制漏洞挖掘与利用的高阶技巧。 综合评分: 88 文章分类: CTF,二进制安全,漏洞分析,逆向分析
2026年天府杯pwn的0解题目WP
原创
teacher李 teacher李
由由学习吧
2026年2月5日 22:15 重庆
2026年天府杯pwn的0解题目WP
libc版本为2.27
整体代码逻辑如下:
extern FILE *stderr;
extern FILE *stdin;
extern FILE *stdout;
unsignedintmain(unsignedinta0, unsignedlonglonga1)
{
unsignedlonglong v0; // [bp-0x28]
unsignedint v1; // [bp-0x1c]
char result; // [bp-0x11]
unsignedlonglong v3; // [bp-0x10]
v1= a0;
v0= a1;
setvbuf(stdout, NULL, 2, 0);
setvbuf(stderr, NULL, 2, 0);
setvbuf(stdin, NULL, 2, 0);
result =0;
while (true)
{
mean();
v3 =input();
if (v3 ==1)
{
add(sub_400b7f);
}
elseif (v3 >=1)
{
if (v3 ==2)
{
show(sub_400bea);
}
elseif (v3 ==1337)
{
if (result)
return0;
show(sub_400ccd);
result =1;
}
else
{
return0;
}
}
}
}
intmean()
{
puts(“1. malloc”);
puts(“2. delete”);
puts(“3. exit”);
returnprintf(“>> “);
}
longlonginput()
{
unsignedlonglong v0; // [bp-0x30]
char v1; // [bp-0x28]
v0=16;
memset(&v1, 0, 16);
shuru(&v1, 15);
returnstrtol(&v1, NULL, 10);
}
longlongshuru(char*a0, longlonga1)
{
char v0; // [bp-0x21]
void* i; // [bp-0x20]
unsignedlonglong count; // [bp-0x18]
for (i =0; i < a1; i +=1)
{
count =read(0, &v0, 1);
if (count !=1)
exit(-1); /* do not return */
if (v0 ==10)
return i;
*((char*)(a0 + i)) = v0;
}
return i;
}
typedefstruct struct_0 {
struct struct_0 *field_0;
} struct_0;
externunsignedlonglongg_602060[4];
externunsignedlonglong g_602160;
void*sub_400ec0(struct_0 **a0)
{
char v0; // [bp-0x29]
void* idx; // [bp-0x28]
void* j; // [bp-0x20]
unsignedlonglong v3; // [bp-0x18]
unsignedlonglong v4; // [bp-0x10]
puts(“Length: “);
v3=input();
if (v3 >640)
exit(-1); /* do not return */
v4= v3 +15>>4;
for (idx =0; idx <=31&&*((longlong*)(0x8* idx + (char*)&g_602060[0])); idx +=1);
if (idx ==32)
exit(-1); /* do not return */
*((unsignedlonglong*)(0x8* idx + (char*)&g_602060[0])) =a0(v4);
*((unsignedlonglong*)(0x8* idx + (char*)&g_602160)) = v4;
puts(“Data: “);
for (j =0; j < v4; j +=1)
{
v0 =sub_40108a(j *24+*((longlong*)(0x8* idx + (char*)&g_602060[0])));
if (v0 !=1)
return v0 ^1;
}
return j;
}
typedefstruct struct_0 {
charpadding_0[16];
unsignedlonglong field_10;
} struct_0;
unsignedlonglongsub_40108a(struct_0 *ptr)
{
ptr->field_10=shuru(ptr, 16);
returnptr->field_10&0xffffffffffffff00|ptr->field_10==16;
}
unsignedlonglongsub_400b7f(unsignedlonga0)
{
void* i; // [bp-0x18]
unsignedlonglong ptr; // [bp-0x10]
ptr =malloc(a0 *24);
for (i =0; i < a0; i +=1)
{
sub_400e4a(ptr + i *24);
}
return ptr;
}
typedefstruct struct_0 {
charpadding_0[16];
void* field_10;
} struct_0;
longlongsub_400e4a(struct_0 *ptr)
{
ptr->field_10=0;
returnsub_401062(ptr);
}
longlongsub_401062(struct_0 *a0)
{
returnmemset(a0, 0, a0->field_10);
}
typedefstruct struct_0 {
struct struct_0 *field_0;
} struct_0;
externunsignedlonglongg_602060[4];
unsignedlonglongshow(struct_0 **a0)
{
unsignedlonglong v0; // [bp-0x10]
puts(“Index: “);
v0=input();
if (v0 <=31)
return (!g_602060[v0] ?puts(“Nope”) :a0(v0));
exit(-1); /* do not return */
}
externvoid g_602060;
void*sub_400bea(unsignedlongidx)
{
void* i; // rbx
if (*((longlong*)&(&g_602060)[8* idx]))
{
i =*((longlong*)&(&g_602060)[8* idx]) +*((longlong*)(*((longlong*)&(&g_602060)[8* idx]) -8)) *24;
while (i !=*((longlong*)&(&g_602060)[8* idx]))
{
sub_400e72(i -24);
}
sub_400d45(*((longlong*)&(&g_602060)[8* idx]) -8);
}
*((unsignedlonglong*)&(&g_602060)[8* idx]) =0;
return&g_602060;
}
longlongsub_400e72(void*a0)
{
returnsub_401062(a0);
}
longlongsub_401062(struct_0 *a0)
{
returnmemset(a0, 0, a0->field_10);
}
longlongsub_400d45(unsignedlonga0)
{
return (unsignedlonglong)free(a0 +15&0xfffffffffffffff0);
}
externunsignedlonglongg_602060[4];
externunsignedlonglongg_602160[4];
intsub_400ccd(unsignedlonga0)
{
void* i; // [bp-0x10]
for (i =0; i <g_602160[a0]; i +=1)
{
sub_400e8e(i *24+g_602060[a0]);
}
returnputchar(10);
}
typedefstruct struct_0 {
charpadding_0[16];
char field_10;
} struct_0;
extern FILE *stdout;
longlongsub_400e8e(struct_0 *a0)
{
returnfwrite(a0, *((longlong*)&a0->field_10), 1, stdout);
}
保护机制、ROP链、gadget、sh:
详细分析:
这里需要注意绕过,我们输入的size经过了运算和移位
然后就是注意对齐,清空 field_10的最低字节,然后设置其值为 0x10
注意用户实际输入的计算:
1. 用户输入size = v3 (比如0x60 = 96)
2. 计算结构体数量:v4 = (v3 + 15) / 16
3. 实际malloc:malloc(v4 * 24)
举个例子:
– 用户输入:0x60 = 96字节
– 结构体数量:(96 + 15) / 16 = 111 / 16 = 6 (整数除法)
– 实际malloc:6 * 24 = 144字节
- malloc对齐后:0x90字节,但堆头占0x10,所以用户看到0x80
但是也可以不用纠结这个问题,直接按照常规来写脚本就可以,就是注意分配的地址大小
然后有一个隐藏的show函数,当输入1337时候出现,但是只有一次机会
运行之后result = 1,就不能第二次进去了
然后是delete,就是free之后把指针清零(因为这个程序不是多线程程序所以不存在UAF,如果是多线程就得分情况导致潜在UAF漏洞的核心问题在于,释放内存和清空全局表指针的操作不是原子性的,在这之间存在一个时间窗口,并且代码逻辑可能创建了指向已释放内存的“悬垂指针”)
但是要注意delete内存对齐问题函数中存在堆操作越界 / 非法内存访问,正确的对齐计算:free((void*)((a0 + 15) & ~0xF)),否则,错误的地址导致破坏glibc的堆管理结构程序会退出
所以:先申请了 17 个堆块(索引0-16),free 10/7/6/2——这些索引在show()的检查范围内(v0<=31),且先通过add操作把g_602060的对应位置写入了合法的堆地址(而非随机值),避免了puts("Nope")后触发的非法访问。先大量add不同大小的堆块(0x70/0x10/0xb0/0xd0等),让g_602060中对应索引的位置被写入malloc返回的合法堆地址(而非越界的随机值),此时sub_400bea中*((long long *)&(&g_602060)[8 * idx])是合法地址,暂时不会触发段错误。通过add(0x10, pld(0,0x441 + 0x20*3 + 0x60))等操作,伪造了a0的值,让a0+15&~0xf恰好等于一个合法的堆地址(而非野指针),规避了free非法地址触发的崩溃。
Exp攻击如下:
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:由由学习吧 teacher李 teacher李《2026年天府杯pwn的0解题目WP》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论