본문으로 바로가기

가. 코드엔진(CodeEngn) 이란?

- 리버스 엔지니어링 정보를 공유하고 소프트웨어 보안에 대한 연구를 진행하는 국내 리버스 엔지니어링 커뮤니티이다.
- 리버스 엔지니어링과 관련된 컨퍼런스, 세미나, 워크숍 등을 운영하고 있으며 다양한 연습 문제도 제공하고 있다.
- 코드엔진(CodeEngn) 홈페이지는 아래의 링크를 통해 접속할 수 있다.

https://codeengn.com/

 

CodeEngn [코드엔진] 리버스엔지니어링 컨퍼런스

국내외 보안 세미나, 컨퍼런스, 워게임 일정을 제공합니다.

codeengn.com

 

나. 환경 세팅

1. 문제 다운로드

- 아래의 링크에 접속하여 RCE_Basic L01 문제를 다운로드한 뒤, 압축을 해제한다.

※ 파일의 암호는 "codeengn" 이다.

https://github.com/codeengn/codeengn-challenges/blob/main/RCE_Basic/01.7z

 

codeengn-challenges/RCE_Basic/01.7z at main · codeengn/codeengn-challenges

Challenge Files Provided by CodeEngn. Contribute to codeengn/codeengn-challenges development by creating an account on GitHub.

github.com

 

2. 필요 도구 다운로드

2-1. PEView

- PEView는 Windows PE(Portable Executable) 파일의 내부 구조를 분석할 수 있는 PE 파일 분석 도구이다.

- 아래의 사이트에 접속하여 PEview.zip 파일을 다운로드 및 압축해제한다.

http://wjradburn.com/software/

 

WJR Software - PEview (PE/COFF file viewer),...

Utilities (for use with Windows® XP operating system or later) PEview provides a quick and easy way to view the structure and content of 32-bit Portable Executable (PE) and Component Object File Format (COFF) files. This PE/COFF file viewer displays heade

wjradburn.com

 

2-2. OllyDbg

- OllyDbg는 Windows 환경에서 실행 파일을 어셈블리 레벨에서 분석하고 디버깅할 수 있는 무료 디버거(Debugger)이다.

- 아래의 링크를 클릭하여 OllyDbg를 다운로드 및 압축해제한다.

https://www.ollydbg.de/

 

OllyDbg v1.10

 

www.ollydbg.de

 

2-3. Frida Client

- Frida는 실행 중인 프로그램을 분석하고 조작(후킹, Hooking)할 수 있는 오픈소스 도구(Tool)이다.

- 아래의 게시글을 참고하여 프리다 클라이언트를 설치한다.

https://hagsig.tistory.com/276

 

안드로이드 프리다 서버/클라이언트 설치방법

Frida Server/Client 설치 방법 - Android(AOS) 가. 설치 프로그램 설명1. 아나콘다(Anaconda)- 오픈소스 라이브러리를 모아둔 개발 플랫폼이다.- 아나콘다를 사용하면 파이썬을 버전별로 설치해서 사용할 수

hagsig.tistory.com

 

다. 문제 풀이

1. 문제 의도 파악

- 01.exe 파일을 실행하면 "Make me think your HD is a CD-Rom."이라는 메시지 창이 출력된다.

- 확인 버튼 클릭 시 "Nah... This is not a CD-ROM Drive!" 이라는 오류 창이 출력된다.

- 해당 문제는 HDD를 CD-ROM으로 인식시키는 게 목표인 것 같다.

 

2. PEView를 통한 분석

- 디버깅 도구를 통한 본격적인 분석에 앞서 PEView를 통해 01.exe 파일의 구조 정보를 분석한다.

 

2-1. 분석 시작점 찾기

- 디버깅 도구에서 프로그램을 로드하면 자동으로 엔트리 포인트(Entry Point)BP(BreakPoint)가 설정되어 프로그램 실행이 시작되는 지점에서 분석을 시작할 수 있다. 하지만 악성코드나 난독화된 파일의 경우 엔트리 포인트가 숨겨져 있거나 BP가 정상적으로 작동하지 않을 수 있다.

- 이럴 때 PEView를 활용하여 정확한 엔트리 포인트를 파악하면 불필요한 시행착오 없이 바로 코드 분석을 진행할 수 있다.

*엔트리 포인트(Entry Point) : 프로그램이 실행될 때 가장 먼저 실행되는 코드의 시작 지점

 

- PE 파일의 IMAGE_OPTIONAL_HEADER 영역에는 실행 파일이 메모리에 로드되고 실행될 때 필요한 정보가 포함되어 있다. 특히, Address Of Entry Point와 Image Base 값을 통해 실제 엔트리 포인트를 확인할 수 있다.

*Address Of Entry Point : 실행이 시작되는 상대 가상 주소(RVA, Relative Virtual Address)

*Image Base : 실행 파일이 로드될 기본 메모리 주소

 

- PE 파일의 헤더에는 엔트리 포인트가 상대 가상 주소(RVA)로 저장되며, 프로그램이 로딩될 때 ImageBaseAddressOfEntryPoint 값을 더한 값이 실제 엔트리 포인트 주소가 된다.

- 점검 대상 파일은 AddressOfEntryPoint 값이 0x1000, ImageBase 값이 0x00400000 이므로, 실제 엔트리 포인트는 0x00401000 이 된다.

 

2-2. 외부 API 함수 호출 확인

- IMPORT Address Table(IAT) 영역에는 실행 파일이 외부 라이브러리에서 정적 호출하는 API 함수들의 주소가 저장되어 있다.

구분 특징
정적 IAT 호출 API를 직접 호출하며, IAT(Import Address Table)에 함수 주소가 기록됨
동적 API 호출 GetProcAddress()와 LoadLibrary()를 사용해 실행 중 API 주소를 가져옴 (IAT에 기록되지 않음)

 

- 점검 대 파일의 IAT 영역을 분석하니 GetDriveTypeAMessageBoxA 함수를 외부 라이브러리에서 호출해 오는 것을 알 수 있다.

*GetDriveTypeA : Windows API 함수로, 특정 드라이브의 유형(예: HDD, CD-ROM, 네트워크 드라이브 등)을 확인하는 데 사용된다.

*MessageBoxA : Windows API 함수 중 하나로, 메시지 박스를 생성한다.

 

3. OllyDbg를 통한 분석

- 올리디버거를 통해 점검 대상 프로그램을 로드한다.

- 전체적인 흐름을 보니 메시지 창 출력 후, GetDriveTypeA 함수를 통해 C드라이브의 유형을 검사하는 것 같다.

- GetDriveTypeA 함수 호출 다음 구문에 BP를 걸어보니, 함수의 반환값 3이 EAX 레지스터에 들어있는 것을 볼 수 있다.

 

- GetDriveTypeA 함수는 드라이브 유형에 대해 다음과 같은 값을 반환한다. C드라이브를 HDD로 인식하여 3을 반환한 것이다.

Return Code Value Description
DRIVE_UNKNOWN 0 알 수 없는 드라이브
DRIVE_NO_ROOT_DIR 1 루트 디렉터리를 찾을 수 없음
DRIVE_REMOVABLE 2 이동식 드라이브 (USB, 플로피 디스크 등)
DRIVE_FIXED 3 고정 디스크 (HDD, SSD)
DRIVE_REMOTE 4 네트워크 드라이브
DRIVE_CDROM 5 광학 드라이브 (CD/DVD)
DRIVE_RAMDISK 6 램 디스크

 

- 그 이후 흐름을 분석하니 현재 EAX 값과 ESI의 값이 일치하지 않아 실패 메시지가 출력되는 것으로 확인된다.

메모리 주소 어셈블리 명령어(Opcode) 설명 EAX 값 ESI 값
00401013 PUSH 01.00402094 검사할 드라이브 문자 주소 입력 00000001 00401000
00401018 CALL <JMP.&KERNEL32.GetDriveTypeA> GetDriveTypeA 호출 00000001 00401000
0040101D INC ESI ESI(엔트리포인트) 값 1증가 00000003 00401000
0040101E DEC EAX EAX(GetDriveTypeA 반환값) 1 감소 00000003 00401001
0040101F JMP SHORT 01.00401021   00000002 00401001
00401021 INC ESI ESI 값 1증가 00000002 00401001
00401022 INC ESI ESI 값 1증가 00000002 00401002
00401023 DEC EAX EAX 값 1감소 00000002 00401003
00401024 CMP EAX,ESI EAX와 ESI 값의 차이를 구함 00000001 00401003
00401026 JE SHORT 01.0040103D 두 값의 차이가 0이면 0040103D 으로 점프   00000001 00401003
00401028 실패 창 출력 로직
...  
0040103D 성공 창 출력 로직

 

4. 점검 로직 우회

- 성공 메시지가 출력되도록 하는 방법은 수도없이 많겠지만 본글에서는 아래와 같은 세 가지 방법으로 우회하였다.

 

4-1. 성공 로직으로 JMP

- 0x00401026 주소의 Opcode를 "JMP 0040103D"로 수정한다.

*JMP : 조건 없이 해당 주소로 이동

 

4-2. EAX 또는 ESI 값 수정

- CMP 명령어 수행 전에 EAXESI값을 동일하게 수정한다. 

 

4-3. 프라다 후킹을 통한 EAX 값 수정

- 아래의 사이트에 접속하면 Windows 프로그램에 대한 프리다 공식 후킹 소스코드를 확인할 수 있다.

https://frida.re/docs/examples/windows/

 

Windows

Observe and reprogram running programs on Windows, macOS, GNU/Linux, iOS, watchOS, tvOS, Android, FreeBSD, and QNX

frida.re

 

- 위 코드를 바탕으로 점검 대상 프로그램을 후킹 하여 EAX 값을 ESI값과 동일하게 변경, CD-ROM으로 인식하게 하였다.

#프리다 코드 실행 명령어
#01.exe 프로그램이 실행중이어야 함
python hagsig_hooking.py 01.exe
//CodeEngn RCE_Basic L01 01.exe Frida hooking script
import frida
import sys

def on_message(message, data):
    print("[%s] => %s" % (message, data))

def main(target_process):
    session = frida.attach(target_process)

    script = session.create_script("""

    Interceptor.attach(ptr('0x00401024'), {  // CMP EAX, ESI 명령어 주소
        onEnter: function (args) {
            var context = this.context;
            
            console.log("[*] CMP EAX, ESI 실행 전");
            console.log("    EAX: " + context.eax.toInt32());
            console.log("    ESI: " + context.esi.toInt32());

            // EAX 값을 ESI 값으로 강제 변경
            context.eax = context.esi;

            console.log("[*] 변조 후");
            console.log("    EAX: " + context.eax.toInt32());
        }
    });
""")
    script.on('message', on_message)
    script.load()
    print("[!] Ctrl+D on UNIX, Ctrl+Z on Windows/cmd.exe to detach from instrumented program.\n\n")
    sys.stdin.read()
    session.detach()

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: %s <process name or PID>" % __file__)
        sys.exit(1)

    try:
        target_process = int(sys.argv[1])
    except ValueError:
        target_process = sys.argv[1]
    main(target_process)

 

5. 풀이 결과 

- 점검 로직 우회 결과 HDD를 CD-Rom으로 인식, 오류 메시지 창이 아닌 성공 메시지가 출력되는 것을 확인할 수 있다.