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