1、pwn145
没钱了孩子们,只能本地玩玩了
继续学习堆,这道题的提示是:glibc的一种分配规则
首先查查glibc分配规则是什么:
glibc 堆内存整体架构
glibc 使用 ptmalloc2 管理堆,基于 bins + tcache(2.26+)
主要结构:
malloc → 从 tcache 拿 → fastbin → smallbin → unsortedbin → largebin → top chunk
free → 先 tcache → fastbin → unsortedbin → smallbin/largebin
各种 Bin 的作用与特点
tcache(2.26+ 新特性)
- 每个线程有一组 tcache bins
- 每个 bin 最多存 7 个 chunk
- 链表为 单链表
- free 时优先放入 tcache
- malloc 时优先从 tcache 拿
用途:加速频繁的小块分配
安全性:比 fastbin 更弱,是很多攻击(tcache poisoning)的重点。
fastbin
- chunk size 范围:0x20 ~ 0x70(不同版本略不同)
- 单链表
- FREE 时不合并(no consolidation)
- MALLOC 时从对应的 fastbin 链表取第一个
主要漏洞利用点:
- double free
- fastbin dup → 任意写
unsorted bin
- free 的大多数 chunk 最开始都会进入 unsorted bin
- 一个双向链表
- 在下一次 malloc 时会分割
- 经常用于 leak libc 地址(因为里面的 bk / fd 带着 libc 链表指针)
smallbin
- chunk size:固定大小(0x20 ~ 0x400 一些区间)
- 双向循环链表
- free 时放入 smallbin
- malloc 时必须 EXACT SIZE 匹配
largebin
- chunk 比 smallbin 大
- 按大小排序
- malloc 时会在 bin 中找“最合适块”(best fit)
这部分一般用于:
- unlink attack
- largebin attack
glibc malloc 的核心流程 :
MALLOC(size) 简化流程:
- tcache 有?直接取
- fastbin 有?取
- unsorted bin 找可用 chunk
- smallbin / largebin 查找
- 不够则从 top chunk 切割
- top chunk 不够 → 触发 brk 或 mmap
glibc free 流程 :
FREE(ptr) 流程:
- 目标 chunk 大小 ≤ tcache?
→ 放入 tcache - fastbin 大小?
→ 放入 fastbin - 否则:
→ 放入 unsorted bin
→ 可能和前后 chunk 合并(consolidation)
合并行为:
- fastbin chunk 不合并
- small/large 会合并
- 通常放入 unsorted bin 后会合并,或等待下一次 malloc 再处理
大概就记录这些,然后开始分析这个程序
checksec:

64位开了canary和NX,运行看看情况(自己在本地写了flag)
是一个演示过程,演示完输入sh就可以查看flag
faetong@faetong-virtual-machine:~/pwnit$ ./pwn145
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Why it can UAF(use after free) ?
* *************************************
演示glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x2a80b2a0
第二个 b = malloc(0x256) 在: 0x2a80b7c0
我们可以继续分配它
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
第一次申请的 0x2a80b2a0 指向 AAAAAAAA
接下来 free 掉第一个...
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x2a80b2a0
第三次 c = malloc(0x500) 在: 0x2a80b2a0
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中
第三次申请的 c 0x2a80b2a0 指向 CCCCCCCC
第一次申请的 a 0x2a80b2a0 指向 CCCCCCCC
可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 "CCCCCCCC"
sh
$ ls
flag pwn145
$ cat flag
flag{Inoue_Takina}
$
演示的是一种glibc分配规则,纯演示,直接下一道题吧
2、pwn146(为什么会产生UAF漏洞?)
题目提示:为什么会产生UAF漏洞?
UAF漏洞:
英文的话更好理解UAF是什么意思: Use-After-Free
概念:程序 已经通过 free/delete 释放了某块堆内存,但之后仍然通过 悬挂指针(dangling pointer) 对这块已释放的内存进行访问(读、写或执行)。
大白话讲就是: 内存已经被 free 掉了,程序却还继续把它当正常数据来用。
人机给了一个幽默的: 就像你把出租屋退租了,但你还偷偷拿着钥匙进去住一样(危险得很)。
通常出现的情况
free之后没有把指针清空
char *p = malloc(0x20);
free(p);
printf("%s\n", p); // p 还在用,炸!
//这里 p 指向的内存已经被标记为可重用,但程序还以为它有效。
一个对象被多出引用
Node* a = malloc(sizeof(Node));
Node* b = a;
free(a);
b->value = 123; // b 还在写,其实对象已经 free
接下来看看这个题吧
checksec:

开了canary和nx
运行一下

结合反汇编:
void __cdecl demo()
{
ptr *p1; // [rsp+0h] [rbp-20h]
ptr *p2; // [rsp+8h] [rbp-18h]
char c[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf(&format_);
p1 = (ptr *)malloc(0x20u);
printf(aP1, p1);
p1[1] = (ptr)Printf;
puts(&s_);
p1[1]("Hello CTFshow\n\n");
puts(aFree_0);
free(p1);
puts(&s__0);
p1[1]("Hello CTFshow again\n");
puts(&s__1);
p2 = (ptr *)malloc(0x20u);
printf(aP2, p2);
printf(aP1, p1);
puts(&s__2);
puts("Then get the flag && enjoy it !\n");
p2[1] = (ptr)demoflag;
putchar(36);
__isoc99_scanf("%2s", c);
p1[1](c);
}
开始是分配了堆空间->p1[1]=printf->让p1[1]输出Hello CTFshow->释放p1但并没有置零,其实后面应该置零之后仍然可以打印Hello CTFshow
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Why it can UAF(use after free) ?
* *************************************
申请0x20大小的内存p1 的地址: 0x13ba010
把p1[1]赋值为Printf函数,然后打印出"Hello CTFshow"
Hello CTFshow
free 掉 p1
因为并没有置为null,所以p1[1]仍然是Printf函数,仍然可以输出打印了"Hello CTFshow again"
Hello CTFshow again
接下来再去malloc一个p2,会把释放掉的p1给分配出来,可以看到他俩是同一地址的
p2 的地址: 0x13ba010
p1 的地址: 0x13ba010
然后把p2[1]给改成demoflag也就是system函数
Then get the flag && enjoy it !
调试看看,首先执行到malloc


我们创建的是那个大小为0x30的chunk,执行完call free


看到有一个free chunk
但是到这儿程序段错误了,只能看看师傅们的调试了
3、pwn147

提示是fastbin_dup
我们来看看checksec

然后运行一下程序
faetong@faetong-virtual-machine:~/pwnit$ nc pwn.challenge.ctf.show 28225
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Fastbin_dup -- Double free
* *************************************
演示 fastbin 的 double free
首先申请 3 个 chunk
第一个 malloc(8): 0xd08010
第二个 malloc(8): 0xd08030
第三个 malloc(8): 0xd08050
free 掉第一个
当我们再次 free 0xd08010 的时候, 程序将会崩溃因为 0xd08010 在 free 链表的第一个位置上
我们先 free 0xd08030.
现在我们就可以再次 free 0xd08010 了, 因为他现在不在 free 链表的第一个位置上
现在空闲链表是这样的 [ 0xd08010, 0xd08030, 0xd08010 ]. 如果我们 malloc 三次, 我们会得到两次 0xd08010
第一次 malloc(8): 0xd08010
第二次 malloc(8): 0xd08030
第三次 malloc(8): 0xd08010
$
演示的是fastbin的double free,其实之前遇到过,通过演示,double free就变得直观了。
sh
ls
cat flag

我们来试着调试一下,先看看代码
void __cdecl demo()
{
char *AAAAAAAA; // [rsp+0h] [rbp-30h]
char *b; // [rsp+8h] [rbp-28h]
char *c; // [rsp+10h] [rbp-20h]
char *d; // [rsp+18h] [rbp-18h]
char *e; // [rsp+20h] [rbp-10h]
char *f; // [rsp+28h] [rbp-8h]
fwrite(&ptr_, 1u, 0x1Fu, stderr);
fwrite(&ptr__0, 1u, 0x19u, stderr);
AAAAAAAA = (char *)malloc(8u);
strcpy(AAAAAAAA, "AAAAAAAA");
b = (char *)malloc(8u);
strcpy(b, "BBBBBBBB");
c = (char *)malloc(8u);
strcpy(c, "CCCCCCCC");
fprintf(stderr, &format_, AAAAAAAA);
fprintf(stderr, &format__0, b);
fprintf(stderr, &format__1, c);
fwrite(&ptr__1, 1u, 0x12u, stderr);
free(AAAAAAAA);
fprintf(stderr, &format__2, AAAAAAAA, AAAAAAAA);
fprintf(stderr, &format__3, b);
free(b);
fprintf(stderr, &format__4, AAAAAAAA);
free(AAAAAAAA);
fprintf(stderr, &format__5, AAAAAAAA, b, AAAAAAAA, AAAAAAAA);
d = (char *)malloc(8u);
e = (char *)malloc(8u);
f = (char *)malloc(8u);
strcpy(d, "DDDDDDDD");
strcpy(e, "EEEEEEEE");
strcpy(f, "FFFFFFFF");
fprintf(stderr, &format__6, d);
fprintf(stderr, &format__7, e);
fprintf(stderr, &format__8, f);
}
我们进入demo这个函数进行细调

我们运行到call malloc


创建完(下面换了libc-2.23.so和ld-2.23.so)


此时分配了3个chunk
接下来是free,注意观察fastbin
第一个free了chunk0

可以看到它被列入了fastbin
接下来free chunk1

现在我们可以看到bins里面的情况

chunk[0]-chunk[1]-chunk[0]
接下来又把chunk申请回来

现在我们要看看,我们写入的的东西是不是会出现在chunk[0]中



ok,就到这里
4、pwn148

fastbin_dup_into_stack,继续学习新东西,先看一下checkseck

设置了终端背景颜色和透明度hh,
然后我们看看演示
设置了终端背景颜色和透明度hh,
然后我们看看演示
faetong@faetong-virtual-machine:~/pwnit$ nc pwn.challenge.ctf.show 28163
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Fastbin_dup_into_stack -- Double free
* *************************************
通过欺骗 malloc 使得返回一个指向受控位置的指针(本例为栈上)
通过 malloc 申请到 0x7ffed3cf1770.
先申请3 个 chunk
chunk a: 0x2362010
chunk b: 0x2362030
chunk c: 0x2362050
free 掉 chunk a
如果还对 0x2362010 进行 free, 程序会崩溃。因为 0x2362010 现在是 fastbin 的第一个
先对 b 0x2362030 进行 free
接下来就可以对 0x2362010 再次进行 free 了, 现在已经不是它在 fastbin 的第一个了
现在 fastbin 的链表是 [ 0x2362010, 0x2362030, 0x2362010 ] 接下来通过修改 0x2362010 上的内容来进行攻击.
第一次 malloc(8): 0x2362010
第二次 malloc(8): 0x2362030
现在 fastbin 表中只剩 [ 0x2362010 ] 了
接下来往 0x2362010 栈上写一个假的 size,这样 malloc 会误以为那里有一个空闲的 chunk,从而申请到栈上去
现在覆盖 0x2362010 前面的 8 字节,修改 fd 指针指向 stack_var 前面 0x20 的位置
第三次 malloc(8): 0x2362010, 把栈地址放到 fastbin 链表中
这一次 malloc(8) 就申请到了栈上去: 0x7ffed3cf1770
还是运用了double free,然后往fastbin0x2362030的栈上写了一个假size欺骗了malloc
double free之后的fastbin:
main_arena->chunk[0]->chunk[1]->chunk[0]->0
现在针对0进行攻击,现在把chunk[0]和chunk[1]申请走
main_arena->chunk[0]->0
接下来的操作把fastbin变为:
main_arena->chunk[0]->stack_addr
然后把chunk[0]和stack_addr所在chunk申请走,获得了在栈上写的权限
这里的操作其实不是太懂,我们看看代码才能知道
void __cdecl demo()
{
__int64 v0; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 stack_var; // [rsp+8h] [rbp-48h]
char *AAAAAAAA; // [rsp+10h] [rbp-40h] BYREF
char *b; // [rsp+18h] [rbp-38h]
char *c; // [rsp+20h] [rbp-30h]
unsigned __int64 *d; // [rsp+28h] [rbp-28h]
char *e; // [rsp+30h] [rbp-20h]
char *f; // [rsp+38h] [rbp-18h]
char *g; // [rsp+40h] [rbp-10h]
unsigned __int64 v9; // [rsp+48h] [rbp-8h]
v9 = __readfsqword(0x28u);
fwrite(&ptr_, 1u, 0x57u, stderr);
fprintf(stderr, &format_, &AAAAAAAA);
fwrite(&ptr__0, 1u, 0x15u, stderr);
//创建chunk[0]
AAAAAAAA = (char *)malloc(8u);
strcpy(AAAAAAAA, "AAAAAAAA");
//创建chunk[1]
b = (char *)malloc(8u);
strcpy(b, "BBBBBBBB");
c = (char *)malloc(8u);
//创建chunk[2]
strcpy(c, "CCCCCCCC");
fprintf(stderr, "chunk a: %p\n", AAAAAAAA);
fprintf(stderr, "chunk b: %p\n", b);
fprintf(stderr, "chunk c: %p\n", c);
fwrite(&ptr__1, 1u, 0x11u, stderr);
//释放chunk[0]
free(AAAAAAAA);
fprintf(stderr, &format__0, AAAAAAAA, AAAAAAAA);
fprintf(stderr, &format__1, b);
//释放chunk[1]
free(b);
fprintf(stderr, &format__2, AAAAAAAA);
//再次释放chunk[0],glibc不会检查第二次free同一个指针
free(AAAAAAAA);
fprintf(stderr, &format__3, AAAAAAAA, b, AAAAAAAA, AAAAAAAA);
//申请回chunnk[0],d来控制malloc的返回值
d = (unsigned __int64 *)malloc(8u);
fprintf(stderr, &format__4, d);
//申请回chunk[1]
e = (char *)malloc(8u);
strcpy(e, "EEEEEEEE");
fprintf(stderr, &format__5, e);
fprintf(stderr, &format__6, AAAAAAAA);
fprintf(stderr, &format__7, AAAAAAAA);
stack_var = 32;
fprintf(stderr, &format__8, AAAAAAAA);
//下面写入了fake fd,是栈地址
*d = (unsigned __int64)&v0;
f = (char *)malloc(8u);
strcpy(f, "FFFFFFFF");
fprintf(stderr, &format__9, f);
g = (char *)malloc(8u);
strcpy(g, "GGGGGGGG");
fprintf(stderr, &format__10, g);
}
下面我们来试着调试看看这个过程
首先是三个对空间的分配

在三个malloc执行完以后我们看到chunk已经创建好了


接下来就是构成fastbin:main_arena->chunk[0]->chunk[1]->chunk[0]

这条链很明显:

接下来是申请回chunk[0]

main_arena->chunk[1]->chunk[0]
接下来申请回chunk[1]然后写入假fd

这个是写入F以后,马上要写入G


此时已经可以在栈上写入

5、pwn149(fastbin_dup_consolidate)

checksec

看看演示:
faetong@faetong-virtual-machine:~/pwnit$ nc pwn.challenge.ctf.show 28199
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Fastbin_dup_consolidate
* *************************************
申请两个 fastbin 范围内的 chunk: p1=0xcdd010 p2=0xcdd030
先 free p1
去申请 largebin 大小的 chunk,触发 malloc_consolidate(): p3=0xcdd050
因为 malloc_consolidate(), p1 会被放到 unsorted bin 中
这时候 p1 不在 fastbin 链表的头部了,所以可以再次 free p1 造成 double free
现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次都到 p1: 0xcdd010 0xcdd010
$
似乎也是去构造一个double free但是用的方法不同,先看看人机的讲解然后来调试一下程序
首先是Fastbin_dup_consolidate的目的
让同一个 fastbin chunk(例如 p1) 既出现在 fastbin 链表里,也出现在 unsorted bin 里,从而在后续 malloc() 时可以连续两次分配到同一块内存,实现 double malloc → double free → 任意写 / 堆布局控制。
现在这个chunk不仅仅出现在fastbin里,还出现在unsorted bin里,malloc之后将会连续两次分配到同一片内存
因为需要出发 malloc_consolidate() ,这是什么功能呢
当触发:
malloc()需要从 top chunk 拿空间但不够- 或者
malloc_trim - 或者申请一个 过大 chunk(触发整理)
会对 fastbin 进行合并(consolidate)
→ fastbin 里所有 chunk 会被取出并放进 unsorted bin。
ok,接下来看看代码是怎么实现的(带注释)
void __cdecl demo()
{
char *p1; // [rsp+8h] [rbp-28h]
char *p2; // [rsp+10h] [rbp-20h]
void *p3; // [rsp+18h] [rbp-18h]
_QWORD *p4; // [rsp+20h] [rbp-10h]
char *p5; // [rsp+28h] [rbp-8h]
//创建chunk[0]
p1 = (char *)malloc(0x10u);
strcpy(p1, "AAAAAAAA");
//创建chunk[1]
p2 = (char *)malloc(0x10u);
strcpy(p2, "BBBBBBBB");
fprintf(stderr, &format_, p1, p2);
fwrite(&ptr_, 1u, 0xCu, stderr);
//free掉chunk[0]
free(p1);
//申请一个更大的chunk[2],触发 malloc_consolidate()
// glibc 会把 fastbin 里的 chunk(包括 p1)全部搬到 unsorted bin
p3 = malloc(0x400u);
fprintf(stderr, &format__0, p3);
fwrite(&ptr__0, 1u, 0x3Eu, stderr);
// 此时 p1 已经在 unsorted bin 中,不在 fastbin 头部,
// 因此 free(p1) 不会触发 double free 检测!
// free(p1) 会把 p1 再次放入 fastbin
free(p1);
fwrite(&ptr__1, 1u, 0x5Fu, stderr);
//申请小块,返回p1
p4 = malloc(0x10u);
*p4 = 0x43434343434343LL;
//申请小块,同样返回p1
p5 = (char *)malloc(0x10u);
strcpy(p5, "DDDDDDDD");
fprintf(stderr, &format__1, p4, p5);
}
现在来调试程序
在demo打个断点run一下,我们把两个malloc执行完

接下来free chunk[0]



chunk[0]已经进入fastbin
接下来是申请一个更大的chunk[3]


奇怪,为什么被归在smallbin里面了

但是总归是不在fastbin里面的,所以继续free(p1)

虽然是在smollbin但是也是形成了double free

接下来就是申请回两个小块
首先是收回fastbin中的chunk[0]

下方可以看到被收回了

接下来是收回smollbin(原本是unstored bin)

可以发现已经被申请回来了


可以看到里面的内容也是被覆盖了
ok,拿个flag

好了,fastbin就到这儿,要考概率论了TAT











师傅题解写的挺好的,网站也不错|´・ω・)ノ