[코드엔진] Basic RCE L10 풀이

비얌·2022년 3월 16일
1

어셈블리 게임

목록 보기
8/15
post-thumbnail
post-custom-banner

코드엔진 Basic RCE L10 문제를 풀어보았다.

문제는 다음과 같다. OEP를 구한 후 '등록성공'으로 가는 분기점의 OPCODE를 구하라는 문제이다.


파일을 실행시켜보면 문자 입력이 안되는 창이 뜬다.


DIE에 올려보았다. ASPack로 압축되어있다고 뜬다.


ASPack를 언패킹할 수 있는 unipacker로 압축을 풀어주려고 했지만, 해당 모듈에서는 패커를 감지하지 못해 실패했다.


따라서 패킹을 수동으로 푸는 방법밖에 없는 것 같다. 찾아보니 Aspack를 언패킹하는 방법에는 두 개가 있다고 한다.

첫 번째는 RET C를 찾아 OEP를 찾는 방법이고, 두 번째는 하드웨어 BP를 이용해서 언패킹하는 방법이다. 하나씩 알아보자.


1) RET C를 찾아 OEP를 찾는 방법

(1) RET C 찾기

DIE에 패킹된 파일을 올려보았을 때, EP는 0x456001이라고 뜬다.


x64dbg에서도 처음에 F9로 패킹된 파일의 EP로 이동할 수 있고, 여기서 PUSHAD명령어를 확인할 수 있다. 이부분에서 현재 레지스터(EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI)를 스택에 저장한다.

  • PUSHAD란?
    Push All General-Purpose Registers 를 의미하며, 현재 모든 범용레지스터의 저장값을 스택으로 push한다는 뜻이다. 따라서 추가적인 operand이용이 필요없으며, 명령어를 사용하기 위해서는 그냥 명령어만 적어주면 된다.

DIE의 PE를 눌러 들어가서 IMAGE_NT_HEADERS 안의 IMAGE_OPTIONAL_HEADER에 들어가면 AddressOfEntryPoint(메모리로부터의 상대주소 RVA)가 0x56001, ImageBase가 0x400000인 것을 볼 수 있다. 프로그램이 메모리에 로딩 시 ImageBase + AddressOfEntryPoint로 저장되므로, 진입점은 0x00056001 + 0x00400000 = 0x00456001(VA, 가상메모리의 절대주소)이다.


스크롤을 내리다보면 POPAD와 그 밑의 RET C를 확인할 수 있다.

  • POPAD란?
    Push All General-Purpose Registers를 의미하며, 스택에 저장된 모든 값들을 ESP를 기준으로 불러온다.

메모리 및 레지스터를 POPAD로 복구 후 OEP로 분기하기 위해 RET C 명령어를 수행한다.

  • RET 명령어는 pop rip와 같아 push한 값을 ret으로 빼낸다. 후에 push 0이 push OEP로 바뀌기 때문에 push한 OEP값을 ret으로 빼내서 OEP값으로 이동하는 뜻이다.

RET C 이후 push 0과 ret 명령어를 확인할 수 있는데, 두 opcode에 BP를 걸고 프로그램을 실행한다.

이때 push 0이 push 10.445834로 변경된 것을 확인할 수 있는데, 이는 OEP인 0x445834를 push하고, ret에서 해당 주소로 이동한다는 뜻이다. 따라서 이곳이 OEP이다.

-> 해당 파일의 OEP는 0x445834이다.

(2) '등록성공'으로 가는 분기점 찾기

여기까지 하면 모든 압축이 풀린 것을 확인할 수 있는데, 다음을찾기-모든모듈-문자열참조에서 “Registered... well done!” 문자열을 찾을 수 있는 것이 그 증거이다. (패킹된 상태에서는 찾을 수 없음)


문제에서 등록성공으로 가는 분기점의 OPCODE를 찾으라고 했는데, "Registred... well done!" 앞의 jne 10.44552B가 같을 경우 분기하지 않고 같지 않을 경우 분기하므로 이곳이 분기점이다. 따라서 등록성공으로 가는 분기점의 OPCODE는 7555이다.

→ 445834 + 7555 = 4458347555가 답이다!

2) 하드웨어 BP를 이용하는 방법 방법

(1) 하드웨어 BP 걸고 언패킹하기

먼저 패킹의 원리는 다음과 같다.

  1. PUSHAD 명령어를 통해 레지스터를 스택에 쌓는다
  2. 정상 코드를 메모리에 복구한다
  3. POPAD 명령어를 통해 레지스터 값들을 복구한다
  4. OEP로 분기한다.

따라서 1번을 실행한 후 스택에 저장된 레지스터에 하드웨어 브레이크포인터를 걸고 F9를 눌러 실행시킨다면, 해당 스택 값을 복원하려고 하는 3번 지점에서 BP가 걸릴 것이다.


PUSHAD를 실행했을 때, 스택에 레지스터들이 쌓인 것을 볼 수 있다.


그 중 한 개에 브레이크포인트를 걸고, F9를 눌러 실행한다.


POPAD 아래에서 BP가 걸려 멈추는 것을 알 수 있다. 그 아래에 있는 0x445834가 OEP이고, 이곳을 지나 ret하면 언패킹이 완료된다!


(2) 덤프 뜨기

언패킹 루틴을 지났고 함수의 프롤로그가 보이는 것으로 보아 언패킹이 완료된 것을 알 수 있다. 이 상태에서 덤프를 뜨는 방법을 알아보자.


메모리 덤프를 위해 x64dbg 메뉴의 Scaylla를 클릭한다.


Scaylla를 클릭해서
1. IAT Autosearch를 클릭한다
2. Get Imports를 클릭한다
3. Dump를 클릭하고 원하는 경로에 새로운 파일로 저장한다
4. Fix Dump를 클릭하고 저장한 파일을 Fix한다

  • 여기서 이미 덤프를 했는데 Fix Dump를 하는 이유는?
    Dump를 했을 때 저장된 결과물은 간단하게 덤프한 것으로서 IAT가 복구되지 않아서 실행되지 않는다. 따라서 Fix Dump까지 하여 고친 IAT까지 Rebuild하여 덤프하여야 한다.

최종적으로 덤프한 언패킹된 파일을 x64dbg에서 열어보았다. 성공적으로 덤프된 것을 확인할 수 있다.


(3) '등록성공'으로 가는 분기점 찾기

RET C를 찾고 OEP를 찾는 방법에서 '등록성공'으로 가는 분기점을 찾은 방법과 완전히 같다.




이렇게 이번 포스팅에서는 ASPack를 언패킹하는 두 가지 방법 (1) RET C 찾기, (2) 하드웨어 BP 걸기를 알아보고 추가적으로 x64dbg에서 덤프를 뜨는 방법도 알아보았다.

ASPack를 처음 접해봐서 RET C를 찾으면 언패킹할 수 있다는 건 어디서 알 수 있는 건지 궁금했는데, 검색해보니 관련 자료를 찾을 수 있었다. 아래에서 여러 패커들을 수동으로 패킹하는 방법들을 소개하고 있다.

profile
🐹강화하고 싶은 기억을 기록하고 공유하자🐹
post-custom-banner

0개의 댓글