# WXN CTF [TOC] ## [Forensics] Corrupted PNG (264 pts, 20 solves) ### Descriptions Oops! Seems the picture is corrupted... I remember that the picture was square before. Author: CSY54 ### Solution 打開圖片時可以發現圖片已毀損,從題目給的敘述以及 EXIF 可以發現圖片高度被砍半以至於下半部無法顯示(有些環境連上半部都看不到)。 ```sh $ exiftool corrupted_png.png ... Image Width : 300 Image Height : 150 ... ``` 根據 `pngcheck` 的結果,我們可以試著從 IHDR chunk 修起。 ```sh $ pngcheck -v corrupted_png.png File: corrupted_png.png (8969 bytes) chunk IHDR at offset 0x0000c, length 13 300 x 150 image, 32-bit RGB+alpha, non-interlaced CRC error in chunk IHDR (computed 645bb5d2, expected 797d8e75) ERRORS DETECTED in corrupted_png.png ``` 根據 PNG Spec. IHDR chunk 的 [規範](https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR) : ``` The IHDR chunk must appear FIRST. It contains: Width: 4 bytes Height: 4 bytes Bit depth: 1 byte Color type: 1 byte Compression method: 1 byte Filter method: 1 byte Interlace method: 1 byte ``` 我們來看看此圖的數值: ```sh $ xxd corrupted_png.png | head -n 2 00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 00000010: 0000 012c 0000 0096 0806 0000 0079 7d8e ...,.........y}. ``` `IHDR` 後的兩個 4 bytes 依序為 0x12c 以及 0x96,即十進位的 300 以及 150。 我們可以將 0x96 改為 0x12c,接著就發現圖片可以正常顯示了 Flag `WXN{0ops_c0rrup73d_pn9}` ## [Forensics] LSB (268 pts, 19 solves) ### Descriptions Wo, wo, wo, wait a second, it's not LSD! Ever heard of LSB? Author: CSY54 ### Solution LSB 即 Least Significant Bit,是圖片隱寫術(Steganography)的一種,相關知識可以上 [維基百科](https://en.wikipedia.org/wiki/Steganography) 學習,此處不再贅述。 此題的 flag 藏於各像素的 LSB 中,順序在圖片中有提示是 BGR 了,如此可以寫出如下的 solve script ```python= from PIL import Image img = Image.open('LSB.png') W, H = img.size pix = img.load() res = '' for i in range(H): for j in range(W): R, G, B, A = pix[j, i] res += str(B & 1) res += str(G & 1) res += str(R & 1) flag = ''.join(chr(int(res[i:i+8], 2)) for i in range(0, len(res), 8)) print(flag) ``` Bonus: 在打 CTF 時,有些 Forensics 的題目會玩 LSB,那麼在不知道顏色順序的情況下該如何解題呢? 暴力跑過所有可能是一個好的做法,在這篇推薦一個好用的工具:[zsteg](https://github.com/zed-0xff/zsteg) 用 zsteg 解此題的作法如下: ```sh $ zsteg LSB.png b1,b,msb,xy .. text: "H|@jHBxh" b1,bgr,lsb,xy .. text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Faucibus et molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Dui vivamus arcu felis bibendum ut tristique et. Gravida c" b3,bgr,lsb,xy .. file: AIX core file fulldump 32-bit b4,g,msb,xy .. file: MPEG ADTS, layer I, v2, 112 kbps, Monaural b4,abgr,msb,xy .. file: RDI Acoustic Doppler Current Profiler (ADCP) $ zsteg LSB.png b1,bgr,lsb,xy --limit 10000 b1,bgr,lsb,xy .. text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Faucibus et molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Dui vivamus arcu felis bibendum ut tristique et. Gravida cum sociis natoque penatibus. Fringilla est ullamcorper eget nulla. Convallis a cras semper auctor. Pharetra diam sit amet nisl suscipit adipiscing bibendum est. Fermentum odio eu feugiat pretium nibh ipsum consequat. Sit amet luctus venenatis lectus. Orci nulla pellentesque dignissim enim. Id eu nisl nunc mi ipsum faucibus vitae aliquet. Ipsum consequat nisl vel pretium lectus quam id leo in. Cras sed felis eget velit aliquet sagittis id consectetur. Quis commodo odio aenean sed. Ut eu sem integer vitae justo eget magna fermentum. Dolor sed viverra ipsum nunc. Massa ultricies mi quis hendrerit dolor magna eget est. Tortor condimentum lacinia quis vel. Aliquam purus sit amet luctus venenatis. WXN{lorem_ipsum_dolor}" ``` Flag `WXN{lorem_ipsum_dolor}` ## [Forensics] Mayor (164 pts, 38 solves) ### Descriptions 高雄沒有市長 沒有 市長 mayor 市長 Author: CSY54 ### Solution 此題也是在玩 LSB,有別於上一題是將文字藏於圖片的 LSB,此題是將圖片藏於另一圖的 LSB。 有一個工具叫做 Stegsolve,可以在 [這裡](https://www.caesum.com/handbook/Stegsolve.jar) 下載,這裡只會介紹一個功能,其他功能如果有興趣可以上網查。 此工具有個功能是將 RGBA 所有 channel 的每個 bit 都 filter 出來,將此題圖片載入後稍微換一下 pane 就會發現 flag 了。 Flag `WXN{by3_by3}` ## [Forensics] Disk Destroyer (260 pts, 21 solves) ### Descriptions Author: CSY54 ### Solution `binwalk` 後會發現看似一張的 PNG 其實後面還接了一張: ```sh $ binwalk disk_destroyer.png DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 500 x 300, 8-bit/color RGBA, non-interlaced 59 0x3B Zlib compressed data, best compression 8116 0x1FB4 PNG image, 500 x 300, 8-bit/color RGBA, non-interlaced ``` 有幾種方式可以將後面的圖片分離出來: 1. dd ```sh $ dd if=disk_destroyer.png of=res.png bs=1 skip=8116 ``` 2. foremost ```sh $ foremost disk_destroyer.png ``` 3. 手動 Flag `WXN{0v3r_h3r3}` ## [Misc] Fortune (288 pts, 12 solves) ### Descriptions This might be a good relief for those extremely superstitious people. `nc final.ctf.bitx.tw 30000` Author: CSY54 ### Solution 先來看一下 nc 上去的畫面: ```sh $ nc final.ctf.bitx.tw 30000 -------------------- | Fortune Teller | -------------------- What's your name? > hello _________________________________________ / hello: There is no TRUTH. There is no \ | REALITY. There is no CONSISTENCY. There | | are no ABSOLUTE STATEMENTS. I'm very | \ probably wrong. / ----------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ``` 玩過 Linux 的應該有聽過 cowsay 以及 fortune 這兩個指令,可以想到此題可能是將輸入的名字丟進 shell command 然後直接執行,如此,我們可以嘗試「inject」他。 Shell 中有個叫做 command substitution 的便利用法,允許將一個指令的結果當作另一個指令的參數,此方法有兩種表示法:`` `command` `` 、`$(command)`。 ```sh $ echo "Hello, `whoami`" Hello, root $ echo "Hello, $(whoami | rev)" Hello, toor ``` 在此題我們可以嘗試在 name 處填入 `` `ls` ``,他果然噴了 `bin chall.py dev etc flag.txt home lib media mnt opt proc root run sbin srv sys tmp usr var` 回來,接著只要 `` `cat flag.txt` `` 即可。 Flag `WXN{s1mpl3_c0mm4nd_1nj3ct10n}` ## [Misc] JSFuck (275 pts, 17 solves) ### Descriptions I love JavaScript Author: CSY54 ### Solution 在 https://www.jsfuck.com 上玩一下應該可以發現不管輸入 `W`、 `WX`、 `WXN` 產出來的前綴都會一樣,接著只要照產出來的去取代回來就行了。 Flag `WXN{l00k_h0w_b34u71ful_j4v45cr1p7_15}` ## [Misc] One Line Python (272 pts, 18 solves) ### Descriptions `open('output','w').write('\n'.join(str(_) for _ in [(lambda s:(int(__import__('hashlib').sha224(s).hexdigest(),16)^int(__import__('hashlib').sha256(s).hexdigest(),16)^int(__import__('hashlib').sha384(s).hexdigest(),16)^int(__import__('hashlib').sha512(s).hexdigest(),16))&0xFFFFFFFF)(flag[i:i+2]) for i in range(0,len(flag),2)]))` Author: CSY54 ### Solution 經過整理可以寫成如下的程式: ```python= from hashlib import sha224, sha256, sha384, sha512 def calc(s): a = int(sha224(s).hexdigest(), 16) b = int(sha256(s).hexdigest(), 16) c = int(sha384(s).hexdigest(), 16) d = int(sha512(s).hexdigest(), 16) return (a ^ b ^ c ^ d) & 0xFFFFFFFF output = [] for i in range(0, len(flag), 2): s = flag[i:i + 2] output.append(calc(s)) print(output) ``` 一個最簡單的解題策略是去暴力跑過所有長度為 2 的字串然後建表查詢: ```python= from hashlib import sha224, sha256, sha384, sha512 from string import ascii_letters, digits res = [376458635L, 2233597867L, 354029087L, 1915420169L, 3254692394L, 2815960165L, 4110859573L, 3306584243L, 1848860968L] charset = ascii_letters + digits + '{}_' def calc(s): a = int(sha224(s).hexdigest(), 16) b = int(sha256(s).hexdigest(), 16) c = int(sha384(s).hexdigest(), 16) d = int(sha512(s).hexdigest(), 16) return (a ^ b ^ c ^ d) & 0xFFFFFFFF m = dict() for i in charset: for j in charset: m[calc(i + j)] = i + j flag = '' for i in res: flag += m[i] print(flag) ``` Flag `WXN{h45h_m4g1c14n}` ## [PPC] Nonono (289 pts, 13 solves) ### Descriptions This is a [nonogram](https://en.wikipedia.org/wiki/Nonogram) task. There are two tasks: easy and hard. Each file is in the format below: The first line contains two integers, `H`, `W`, represents the height and the width of the picture respectively. The following `H` lines contain a series of numbers `r_i`, represent the continuous block of the `i`-th rows. The following `W` lines contain a series of numbers `c_i`, represent the continuous block of the `i`-th columns. sample input: ``` 3 3 1 1 2 3 3 2 1 1 ``` sample output: ``` # # ## ### ``` Author: CSY54 ### Solution 聽說太小,手算就可以拿到 flag... > Solver 在下一題 Flag `WXN{N0n0gr4m_15_g00d_70_p14y}` ## [PPC] Nonono Revenge (400 pts, 1 solves) ### Descriptions The previous one is too small, take this! Author: CSY54 ### Solution 解釋就讓我跳過吧@@ ```python= from PIL import Image from z3 import * from time import clock black = [] white = [] def parse(filename): f = open(filename, 'r') H, W = map(int, f.readline().split(' ')) row = [map(int, f.readline().split()) for i in range(H)] col = [map(int, f.readline().split()) for i in range(W)] assert(len(row) == H) assert(len(col) == W) return (H, W, row, col) def parse_hint(): global black, white img = Image.open('hint.png') W, H = img.size pix = img.load() for i in range(H): for j in range(W): if pix[j, i] == (0x00, 0x00, 0x00, 0xff): # is black black.append((j, i)) elif pix[j, i] == (0xff, 0xff, 0xff, 0xff): white.append((j, i)) def to_pic(W, H, res, filename): img = Image.new('1', (W, H)) pix = img.load() for i in range(H): for j in range(W): pix[j, i] = res[i][j] img.save('{}_solved.png'.format(filename)) def solve(filename): print('[+] Parsing file') start_time = clock() H, W, row, col = parse('{}.txt'.format(filename)) print('...... {}'.format(clock() - start_time)) s = Solver() x = [[Bool('x_{}_{}'.format(i, j)) for j in range(W)] for i in range(H)] r = [[Int('r_{}_{}'.format(i, j)) for j in range(len(row[i]))] for i in range(H)] c = [[Int('c_{}_{}'.format(i, j)) for j in range(len(col[i]))] for i in range(W)] print('[+] r') start_time = clock() for i in range(H): for j in range(len(row[i])): s.add(0 <= r[i][j], r[i][j] <= W - row[i][j]) if j == len(row[i]) - 1: continue s.add(r[i][j] + row[i][j] < r[i][j + 1]) print('...... {}'.format(clock() - start_time)) print('[+] c') start_time = clock() for i in range(W): for j in range(len(col[i])): s.add(0 <= c[i][j], c[i][j] <= H - col[i][j]) if j == len(col[i]) - 1: continue s.add(c[i][j] + col[i][j] < c[i][j + 1]) print('...... {}'.format(clock() - start_time)) print('[+] continuous') start_time = clock() for i in range(H): for j in range(W): r_con = [And(r[i][k] <= j, j < r[i][k] + row[i][k]) for k in range(len(row[i]))] c_con = [And(c[j][k] <= i, i < c[j][k] + col[j][k]) for k in range(len(col[j]))] s.add(x[i][j] == Or(r_con)) s.add(x[i][j] == Or(c_con)) print('...... {}'.format(clock() - start_time)) print('[+] hint') start_time = clock() for pos in black: j, i = pos s.add(x[i][j] == False) for pos in white: j, i = pos s.add(x[i][j] == True) print('...... {}'.format(clock() - start_time)) print('solving...') start_time = clock() if s.check() == sat: print('solvable!') m = s.model() res = [[is_true(m[x[i][j]]) for j in range(W)] for i in range(H)] to_pic(W, H, res, filename) else: print('unsolvable!') print('...... {}'.format(clock() - start_time)) parse_hint() solve('nonono_revenge') ``` Flag `WXN{s34d0g007_meow_orz}` ## [Reverse] Easy Reverse (256 pts, 22 solves) ### Descriptions A simple reverse task. Author: CSY54 ### Solution 此題有兩個階段,第一階段要在執行時就傳 flag 進去,第二階段是詢問時才輸入。 ```cpp= int __cdecl main(int argc, const char **argv, const char **envp) { signed int i; // [rsp+18h] [rbp-8h] signed int j; // [rsp+1Ch] [rbp-4h] if ( argc <= 1 ) { puts("Usage: ./rev <flag>"); exit(1); } strncpy(first_part, argv[1], 0xBuLL); for ( i = 0; i <= 10; ++i ) { if ( (char)(first_part[i] ^ 1) != *((_DWORD *)&first + i) ) { puts("Level 1 failed!"); exit(1); } } puts("Level 1 passed!\n\nHere comes level 2:"); printf("Password: ", argv); __isoc99_scanf("%11s", second_part); byte_2010B3 = 0; for ( j = 0; j <= 10; ++j ) { if ( (((char)(second_part[j] ^ 0x20) - 10) ^ 0x41) != second[j] ) { puts("Level 2 failed!"); exit(1); } } puts("Level 2 passed!\n\nHere is your flag:"); printf("%s%s\n", first_part, second_part); return 0; } ``` Solve script: ```python= first_part = [0x56, 0x59, 0x4f, 0x7a, 0x32, 0x60, 0x34, 0x78, 0x5e, 0x73, 0x32] flag = ''.join(chr(i ^ 1) for i in first_part) second_part = [0x0D, 0x48, 0x09, 0x08, 0x48, 0x34, 0x78, 0x7F, 0x4B, 0x03, 0x12] flag += ''.join(chr(((i ^ 0x41) + 10) ^ 0x20) for i in second_part) print(flag) ``` Flag `WXN{3a5y_r3v3rs3_ch4l}` ## [Reverse] Easy Reverse 2 (290 pts, 11 solves) ### Descriptions A simple reverse task. Author: CSY54 ### Solution 解這題有一個先備知識:seed 如果一樣會產出一樣的結果。 也就是說我們只要把題目提供的 seed 丟進去 `random.seed()` 裡,然後照跑一次就可以拿到 flag 了。 ```python= import time, random f = open('out.txt', 'r') seed = int(f.readline().split('=')[1]) random.seed(seed) def rand(): rnd = int(random.random() * (10 ** 5)) while rnd: random.random() rnd -= 1 return int(random.random() * 10 ** 5) & ((1 << 16) - 1) def calc(ch, rnd): high = ch >> 4 low = ch & 0xF _00 = (low << 4) | low _01 = (low << 4) | high _10 = (high << 4) | low _11 = (high << 4) | high val = 0 val |= _00 << 12 val |= _11 << 8 val |= _01 << 4 val |= _10 << 0 res = 0 res |= 0b0000111100001111 & (val ^ rnd) res |= 0b1111000011110000 & (val ^ rnd) res |= 0b0101010101010101 & (val ^ rnd) res |= 0b1010101010101010 & (val ^ rnd) return bin(res).lstrip('0b').zfill(16) flag = '' for id, line in enumerate(f): res = line.strip() rnd = rand() for ch in range(0x100): if calc(ch, rnd) == res: flag += chr(ch) assert(len(flag) == 18) print(flag) ``` Flag `WXN{s33d_kn0wn_QQ}` ## [Reverse] Baby Reverse (210 pts, 30 solves) ### Descriptions Ah, ha! Author: CSY54 ### Solution 這題應該不難? Source code: ```cpp= #include <stdio.h> #include <string.h> #define LEN 20 char input[LEN]; char secret[] = "my_s3cr37_p4ssw0rd"; char flag[] = "T[Mxa7a4\\q0u~"; int main() { printf("You need to be authenticated to view the secret.\n"); printf("What's the password?\n"); printf("> "); scanf("%18s", input); if (!strcmp(input, secret)) { for (int i = 0; i < sizeof(flag); i++) { flag[i] ^= 0x3; } printf("%s\n", flag); } else { printf("Hacker, hacker, go away!\n"); } return 0; } ``` Flag `WXN{b4b7_r3v}` ## [Web] Depreciated Page (238 pts, 26 solves) ### Descriptions I'm pretty sure one of the admin deleted the page right before the CTF started! Can you find it back for me? https://final.ctf.bitx.tw/49a525ebd3494e661e0a16c2dfbee7d1 Author: CSY54 ### Solution Wayback Machine 是 CTF 常見的題目,簡單來說只要去 [Wayback Machine](https://archive.org/web/) 的網站把題目給的連結丟上去然後按下 Browse History 就好了。 Flag `WXN{4ll_7h3_w4y_b4ck_t0_7h3_p4st}` ## [Web] Type Juggling (70 pts, 49 solves) ### Descriptions https://final.ctf.bitx.tw:30001/ Author: CSY54 ### Solution 此題要你傳一個名為 secret 的參數進去,其值不能與 `240610708` 相等,但 md5 hash 的值必須與 `240610708` 的相等。 有兩個想法: 1. Collision:但是找不到相關的結果 2. 弱型別: PHP 是一個弱型別的語言,在比較的時候 `==` 與 `===` 有可能會產生不同的結果,在這邊就是這麼回事:`md5('240610708')` 的值是 `0e462097431906509019562988736854` 恰好是科學記號的表示法,也就是說我們只要找到一個不同於 `240610708` 且 md5 的值也是 0e 開頭的字串就好,例如:`QNKCDZO`。 Flag `WXN{PHP_1s_7h3_b3s7_l4ngu4g3_1n_7h3_w0r1d}` ## [Web] Weak Credentials (260 pts, 21 solves) ### Descriptions This credential is kinda weak... You can login with `test` / `test` https://final.ctf.bitx.tw:30002/ Author: CSY54 ### Solution 用 `test` / `test` 登入後並按下 Show flag 的按鈕會向後端傳送 session 出去,其值為 `c81e728d9d4c2f636f067f89cc14862c`,應該不難發現是 2 的 md5 將 session 改為 1 的 md5 後再請求 flag 就可以了 Flag `WXN{47_13457_5417_17}`