---
# System prepended metadata

title: Hack.lu CTF 2019 - Futuretran
tags: [CTF]

---

---
title: Hack.lu CTF 2019 - Futuretran
description: Rev
tags: CTF
lang: zh_tw
---

# `Hack.lu` CTF 2019 - Futuretran
[TOC]

## Description
### Readme
Complex, unsafe languages are the past. We've built a new programming language for the future that uses only a fraction of the instructions of RISC. This should be easy, right?

### Analysis
題目給了 [flag.ftb](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/flag.ftb) 跟 [interpreter.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/interpreter.py)
看了一下 [interpreter.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/interpreter.py) 後，發現重點是 run 這個 function

簡單講一些用到的資料:
- code 為一個分數的 list，被當作是指令 cur_ins
    分子為 cur_ins\[0\]，分母為 cur_ins\[1\]
- idx 為現在執行到哪個指令

主要行為如下:
- state *= 分子
- 若 state 能被分母整除，則 state 為除分母後的商數，並且指令重頭開始跑
- 整個指令都跑完才 return state

main call 完 run 後，若能被 out 整除，就是錯誤。

### Prime
這題用到一個質數的特性，就是質數基本定義
`質數為只有自己本身跟1能整除自己本身的數字`

比如說 2 * 3 * 5，只能被 2^n * 3^n * 5^n, n <= 1 這些數字整除。

且任何數都能換成 `質數A`^`次方` * `質數B`^`次方` ... 的形式
e.g. 30 = 2 ^ 1 * 3 ^ 1 * 5 ^ 1
以下先稱之為`展開形式`

那 120 能被 7 整除嗎? 不行。Why?
可以這樣想
120 的展開形式為 2 ^ 3 * 3 ^ 1 * 5 ^ 1
可以想成，120 有**3個2，1個3，1個5**
而 7 的展開形式為 7 ^ 1，也就是**1個7**
120 交不出**1個7**，所以不能整除。

那 120 能被 12 整除嗎? 可以。Why?
可以這樣想
12 的展開形式為**2個2，1個3**
120 交得出**2個2，1個3**
所以能整除，整除後的數字就是**交出2個2、1個3後剩下的數**
也就是**1個2，1個5**，也就是10

為啥要講廢話，因為這題就是在用這個概念。

### Reversing - phase 1
看看[interpreter.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/interpreter.py)的部分 code
```python
def run(state, code):
    size = len(code)
    idx = 0
    while idx < size:
        cur_ins = code[idx]
        idx += 1
        tmp = state * cur_ins[0]
        if tmp % cur_ins[1] == 0:
            state = tmp // cur_ins[1]
            idx = 0

    return state
```
想像一下，state 為 120，分子(cur_ins[0])為7，分母為(cur_ins[1])為12
tmp 先讓 state 乘上分子
- tmp 有**3個2，1個3，1個5，1個7**
- 若 tmp 能交出分母，也就是**2個2，1個3**
    - state 等於 tmp // 分母，也就是tmp交出分母後剩下的數，也就是**1個2，1個5，1個7**
- 若 tmp 交不出分母，則 state 不會變化

就好像每一個指令、每一個分數、都像是交易過程，state 交出分母的數，來取得分子的數
以上的狀況就是 state 交得出 12，就交出 12 換 7，state = (120 // 12) * 7
那如果 state 交不出分母的數，就不會進行這次交易

再進一步想，每個質數就像是一種貨品，state 就好像你現在有的各種貨品總數
每個指令都是一個分數，就像是一個交易選項，若你現在剛好有符合分母的貨，就會交出這些貨，並換回分子的貨，一個以物易物的概念。

理解好概念後，來看看我做了什麼事情。

首先我寫了一個 [dump.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/dump.py)，改編自 [interpreter.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/interpreter.py)
做的事情是
- 檢查所有 primes (flag.ftb 的第一行所有數字)是否真的是 prime
- 將所有分數的分子分母化為展開形式

就是把每個指令、每個交易內容顯示出來

而剛剛提到的概念，就能用來實作 if 邏輯、xor 邏輯之類的

怎麼說呢?
現在來看看執行 [dump.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/dump.py) 的部分輸出

```
17: [{337: 1, 347: 1}, {331: 1}]
18: [{1}, {167: 1, 211: 1, 337: 1}]
19: [{167: 1}, {211: 1, 337: 1}]
20: [{1}, {337: 1}]
21: [{349: 1, 353: 1}, {347: 1}]
22: [{1}, {173: 1, 223: 1, 349: 1}]
23: [{173: 1}, {223: 1, 349: 1}]
24: [{1}, {349: 1}]
```

- line 17 若 state 交得出 331 就交出 331 換回 337、347
- line 18 若 state 交得出 167、221、337，就交出並換回 nothing
- line 19 若 state 交得出 211、337，就交出並換回 167

這邊可以小小做一個統整，若 state 原本只有**1個331**
那麼經過 line 17，state 會變成**1個337，1個347**(331被換掉了)

這裡再更改原本假設，state 現在多了**1個211**
則到了 line 18、19，將兩行交易合併一起解釋則為
有 167 就換掉 **1個167，1個211，1個337** 換回 **1個1**
不然就換掉 **1個211，1個337** 換回 **1個167**

注意，每次若成功交易了，則會回到第一筆交易開始檢查能否交易

**331**就好像flag，若 state 中有 331，就會換到 337，有 337，就有機會執行 line 18、19 的交易
若 state 根本沒有 **331** 則 line 18、19 的交易根本不可行

所以我說他像 flag，這 flag 為 true 則就會嘗試執行 line 18、19
為 false 則根本不會進 line 18、19

而 **211** 也像一個flag，為 true 則要嘗試執行 line 18、19
為 false 也根本不會進 line 18、19

就好像
```c
if(v331)
{
    if(v211)
    {
        if(v167)
        {
            v167 -= 1
        }
        else
        {
            v167 += 1
        }
    }
}
```

這樣不就能完成 if 了嗎 ?

### Reversing - phase 2
現在可以大概知道說能用以上提到的 trick 實作一些簡單的邏輯。
那來完整的 reverse 這題目的邏輯吧

首先 main 做的事情是將 sys.argv\[2\]，也就是 flag 的部分
一一變成各個「貨品」的數量
比如說 flag 為 flag{........}
會將 state 設定為 **ord('f')個2，ord('l')個3，ord('a')個5，ord('g')個7 ...**

再來，寫一個觀察 state 的動態的腳本 [approach.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/approach.py)
他會在交易成功時，將以下資訊輸出
- 是第幾行的交易
- tmp，也就是 state 乘上分子
- 交易完畢的 state

然後可以發現第92行的交易一直被執行
來看一下
`92: [{257: 1, 571: 1}, {2: 1, 587: 1}]`
原來是 2 一直被換掉，
並且發現 257 數量會一直往上疊加，但 571 不會
表示 571 後續還有被換掉

來到 2 被換完後
還會執行第93、94、89的交易
分別為
```
93: [{593: 1}, {587: 1}]
94: [{223: 1, 227: 1, 239: 1, 241: 1, 569: 1, 599: 1}, {593: 1}]
89: [{277: 1, 331: 1, 433: 1, 503: 1}, {569: 1}]
```
第 92 行一直有在被執行，所以大膽假設，每次 loop 時，總會有 587
當 2 換到沒了，無法執行第 92 行的交易
才會來到第 93 行交易，將 587 換為 593
有 593 才能再換第 94 行交易
有了從第 94 行交易換來的 569
才能再從 89 行換來 277、331、433、503
所以統整一下現在 state 會多有哪些東西
- 從第 94 行交易換到的 223、227、239、241、599
- 從第 89 行交易換到的 277、331、433、503

再來看看繼續執行了什麼交易
發現後續執行第2、3、5、8、10 ... 這些交易
這邊把整段 dump 拉出來看
```
1: [{199: 1, 281: 1}, {257: 128, 277: 1}]
2: [{281: 1}, {277: 1}]
3: [{197: 1, 283: 1}, {257: 64, 281: 1}]
4: [{283: 1}, {281: 1}]
5: [{193: 1, 293: 1}, {257: 32, 283: 1}]
6: [{293: 1}, {283: 1}]
7: [{191: 1, 307: 1}, {257: 16, 293: 1}]
8: [{307: 1}, {293: 1}]
9: [{181: 1, 311: 1}, {257: 8, 307: 1}]
10: [{311: 1}, {307: 1}]
11: [{179: 1, 313: 1}, {257: 4, 311: 1}]
12: [{313: 1}, {311: 1}]
13: [{173: 1, 317: 1}, {257: 2, 313: 1}]
14: [{317: 1}, {313: 1}]
15: [{167: 1}, {257: 1, 317: 1}]
16: [{1}, {317: 1}]
```
這邊可以看到，進來執行的條件為要有 277
對吧，沒有 277 不會有 281，沒 281 就沒 283 就沒 293 ... ，就不會有後續連鎖的執行了
而從 89 行有拿到 277，有機會進來執行
那 257 就是剛剛 2 被拿去 1 比 1 換的貨物
這邊講結論，這裡是一個 decimal to 8-bits binary 的 function
這 8 bits 由高到低為
199,197,193,191,181,179,173,167

比如說我輸入的 flag 第一個字是 f，最後這8個貨物的貨品數為
| 199| 197|193|191|181|179|173|167
|----|----|---|---|---|---|---|-----
|  0 |  1 |  1|   0|   0|   1|   1|   0
就是 `bin(ord('f'))`

執行完第 16 交易後，會繼續執行第 17、20、21、22、25... 這些交易
一樣把一大段拿出來
```
17: [{337: 1, 347: 1}, {331: 1}]
18: [{1}, {167: 1, 211: 1, 337: 1}]
19: [{167: 1}, {211: 1, 337: 1}]
20: [{1}, {337: 1}]
21: [{349: 1, 353: 1}, {347: 1}]
22: [{1}, {173: 1, 223: 1, 349: 1}]
23: [{173: 1}, {223: 1, 349: 1}]
24: [{1}, {349: 1}]
25: [{359: 1, 367: 1}, {353: 1}]
26: [{1}, {179: 1, 227: 1, 359: 1}]
27: [{179: 1}, {227: 1, 359: 1}]
28: [{1}, {359: 1}]
29: [{373: 1, 379: 1}, {367: 1}]
30: [{1}, {181: 1, 229: 1, 373: 1}]
31: [{181: 1}, {229: 1, 373: 1}]
32: [{1}, {373: 1}]
33: [{383: 1, 389: 1}, {379: 1}]
34: [{1}, {191: 1, 233: 1, 383: 1}]
35: [{191: 1}, {233: 1, 383: 1}]
36: [{1}, {383: 1}]
37: [{397: 1, 401: 1}, {389: 1}]
38: [{1}, {193: 1, 239: 1, 397: 1}]
39: [{193: 1}, {239: 1, 397: 1}]
40: [{1}, {397: 1}]
41: [{409: 1, 419: 1}, {401: 1}]
42: [{1}, {197: 1, 241: 1, 409: 1}]
43: [{197: 1}, {241: 1, 409: 1}]
44: [{1}, {409: 1}]
45: [{421: 1, 431: 1}, {419: 1}]
46: [{1}, {199: 1, 251: 1, 421: 1}]
47: [{199: 1}, {251: 1, 421: 1}]
48: [{1}, {421: 1}]
```
331 就像是這一大段邏輯的入口
可以看到每四行就一個類似的結構，
331 換 
- 337 -> 用來進入後續的兩行交易，用完就丟
- 347 -> 用來進入下一個結構 

line 21 也是類似這樣，就不贅述了

這邊在幹嘛?直接說就是
將以下兩列的值做 xor
| 199| 197|193|191|181|179|173|167
|----|----|---|---|---|---|---|-----
|  0 |  1 |  1|   0|   0|   1|   1|   0

| 251| 241|239|233|229|227|223|211
|----|----|---|---|---|---|---|-----
|  0 |  1 |  1|   0|   0|   1|   1|   0

第一個序列為 `bin(ord('f'))`
第二個序列是從第 94 行交易換到的 223、227、239、241

這邊突然意識到，2 換完後，會在換一行 key，這 key 要等於 `bin(ord(第0個字元))`

大膽假設，3 換完後也會換一行 key，這 key 就是 `bin(ord(flag 的第二個字元))`
找到就表示，這題可能就這樣了

來到 2 換完的邏輯附近

```
92: [{257: 1, 571: 1}, {2: 1, 587: 1}]
93: [{593: 1}, {587: 1}]
94: [{223: 1, 227: 1, 239: 1, 241: 1, 569: 1, 599: 1}, {593: 1}]
95: [{601: 1}, {599: 1}]
96: [{257: 1, 599: 1}, {3: 1, 601: 1}]
97: [{607: 1}, {601: 1}]
98: [{227: 1, 229: 1, 239: 1, 241: 1, 569: 1, 613: 1}, {607: 1}]
99: [{617: 1}, {613: 1}]
100: [{257: 1, 613: 1}, {5: 1, 617: 1}]
101: [{619: 1}, {617: 1}]
102: [{211: 1, 239: 1, 241: 1, 569: 1, 631: 1}, {619: 1}]
103: [{641: 1}, {631: 1}]
104: [{257: 1, 631: 1}, {7: 1, 641: 1}]
105: [{643: 1}, {641: 1}]
106: [{211: 1, 223: 1, 227: 1, 239: 1, 241: 1, 569: 1, 647: 1}, {643: 1}]
107: [{653: 1}, {647: 1}]
```
3 如果換完了，會換 607，再換 227 229 239 241
| 251| 241|239|233|229|227|223|211
|----|----|---|---|---|---|---|-----
|  0 |  1 |  1|   0|   1|   1|   0|   0
=> chr(0b01101100) = 'l'

如果到這邊還看得懂，那後續的事情我也不用贅述了

[solve.py](https://github.com/LJP-TW/CTF/blob/master/hackluCTF-2019/rev/Futuretran/temp/public/solve.py)

## Reference
No reference.