상세 컨텐츠

본문 제목

리버싱 입문 5. 리버싱의 어려운 문제들

REVERSING/Reversing Study

by koharin 2021. 5. 11. 01:29

본문

728x90
반응형

1. 패킹과 언패킹


1.1 패킹과 언패킹의 개요

패킹(Packing)

  • 프로그램 코드 크기 줄이기 위해 압축(Compressing)하거나, 프로그램 분석 어렵게 하려고 암호화(Protecting)하는 것
  • 컴프레싱(Compressing): 단순 압축하는 것
    • 초기엔 메모리와 하드디스크 용량이 작아 프로그램의 크기를 줄이기 위해 Compressing 기술을 사용했다.
    • 실행 코드를 압축해서 PE 파일의 특정 섹션에 저장 후, 프로그램이 실행될 때 공간에 압축을 풀어 실행하는 구조
    • 요즘엔 파일 배포 시 전송 시간과 데이터 요금 줄이기 위해 압축 사용
  • 프로텍팅(Protecting): 실행파일을 암호화해서 분석을 어렵게 하는 기술
    • 실행 코드를 암호화된 상태로 배포하고, 실행 시점에서 복호화하여 동작을 수행한다.
    • 속도 저하의 단점

 

UPX(Ultimate Packer for eXecutables)

대표적으로 오픈소스 패킹 도구 UPX(Ultimate Packer for eXecutables)가 있다.

UPX로 패킹된 파일은 Unpacking Code(언패킹에 필요한 코드 영역), Packed Data(패킹된 데이터가 들어가 있는 영역), Empty Space(패킹된 데이터가 저장될 빈 공간)으로 구성된다.

UPX로 패킹된 실행파일 언패킹

  1. 운영체제의 Loader가 실행파일을 메모리에 로딩
  2. EntryPoint(진입점)으로부터 프로그램 실행. 패킹된 파일의 진입점은 언패킹 코드 영역에 들어있다.
  3. Unpacking Code는 패킹된 데이터를 하나씩 읽어 압축을 풀고, 공간에 원본 데이터를 저장한다. 모든 코드가 언패킹되면 Original Entry Point(OEP)에서부터 프로그램이 다시 시작된다.
    • OEP(Original Entry Point): 언패킹되기 이전 실행 파일 진입점

1.2 패킹 도구

Compressor

  • Petite, ASPack, MEW, FSG, UPX

UPX(Ultimate Packer for eXecutables)

  • 가장 많이 사용되는 실행 압축 프로그램
  • 오픈소스
  • OllyDbg에 UPX 언패킹 도구 내장되어있어 MUP(Manual Unpacking) 지원

ASPack

  • 고성능 compressor
  • 빠른 복호화 루틴으로 높은 성능
  • 32bit 윈도우 프로그램에 대해 70% 이상의 압축 효율

Protector

  • Themeda, Yoda, ASProtect, armadillo

Themida

  • 상용 protector
  • 적용된 옵션마다 언패킹 방법이 달라 풀기 어려운 강력한 프로텍터 중 하나
  • 많은 악성코드가 Themida로 프로텍팅되고, 국내 많은 보안 프로그램, 게임들이 실행파일을 Themida로 리버싱으로부터 보호
  • 가상머신 제공

ASProtect

  • ASPack 회사에서 만든 상용 Protector
  • UPX와 유사하게 패킹된 코드를 특정 영역에 넣고, 실행 시간에 언패킹해서 사용하는 방식
  • 가상머신을 제공하지 않아 수동으로 언패킹(MUP, Manual Unpacking) 하기 쉬운 단점

 

2. 대표적인 패커 UPX


2.1 UPX 개요

  • Ultimate Packer for Executables
  • 대표 오픈소스 실행 압축 파일
  • Window, Linux, Unix 등 다양한 운영체제 지원
  • 압축과 해제에 효율적인 알고리즘 제공
  • 압축 효율 높다.
  • UPX로 압축된 파일은 실행하려면 먼저 압축해제 필요한데, 효율적으로 압축 해제 과정을 처리하여 메모리 부하가 매우 낮다.

2.2 패킹

UPX

위 홈페이지에서 Download latest release를 누르면 Github release 페이지로 이동하는데, 각 운영체제에 맞는 파일을 다운받는다. 윈도우의 경우, upx-3.96-win64.zip을 다운받으면 된다. 압축해제 시 upx.exe 파일이 있어 이것을 그대로 사용하면 된다.

upx를 이용한 패킹

upx -o '패킹 후 파일' '패킹 전 파일'

위의 명령어로 압축 시 Ratio가 나오는데, 원본 파일에 대한 출력 파일의 압축 비율을 의미한다.

 

2.3 PE 파일 확인하기

PE(Portable Executable) 파일을 분석할 Detect It Easy 프로그램 설치하기

horsicq.github.io/

die_win64_portable_3.01.zip 을 다운받았다.

압축해제 후, die.exe를 실행하면 바로 사용가능하다.

PE 파일을 선택하면 진입점, 어떤 packer로 패킹되었는지 등의 정보를 확인할 수 있다.

 

  • PE 파일은 CODE, DATA, .idata, .reloc, .rsrc의 다섯 개의 섹션으로 구성된 파일이다.

 

  • Entropy: 데이터의 분포 정보를 보여준다.
  • 패킹되어 있으면 Entropy 분포가 불규칙하다.

 

패킹된 파일은 Delphi로 개발되었고, UPX로 패킹되었음을 알 수 있다.

  • 패킹된 PE 파일은 UPX1, .rsrc 세션으로 구성된 파일임을 알 수 있다.
  • 일반적으로 UPX0에 언패킹된 코드가 저장된다.

 

2.4 언패킹과 OEP

EntryPoint

  • 프로그램에 대한 제어권이 운영체제에서 사용자 코드로 넘어가는 지점
  • 프로세서가 메모리에 있는 Code Section으로 처음 들어가는 지점
  • EntryPoint 위치는 PE 헤더의 AddressOfEntryPoint에 저장된다. 이 위치는 상대 주소이므로, PE 헤더의 ImageBase 값과 합한 메모리 주소가 프로세서가 인식하는 실제 EntryPoint 주소이다.

  • 패킹된 프로그램 실행 시 실행이 완료된 지점에서 언패킹된 코드가 메모리 어딘가에 저장된다.
  • 패커에 따라 실행 완료 시 언패킹된 코드를 지우는 프로그램도 있다.
  • 실행 완료 시 모든 프로그램은 메모리 상에 언패킹된 상태로 저장된다.
  • 패킹 이전 프로그램의 실제 EntryPoint를 찾아야 한다. Memory Dump로 파일 저장 시 PE 헤더의 EntryPoint 값을 실제 EntryPoint 주소로 수정해야 프로그램을 정상적으로 실행할 수 있다.
  • OllyDbg에서는 자동으로 UPX을 언패킹하는 기능을 지원한다. 따라서 언패킹을 공부하려면 이 옵션을 꺼두도록 하자. (Options → SFX → Unpack SFX modules automatically' 체크 해제)

수동 언패킹(MUP, Manual Unpacking)

  • 리버싱 입문에서는 Ollydbg를 사용하지만, 필자는 x32dbg를 사용해서 MUP를 진행했다.

  • 먼저 F9로 패킹된 PE 파일의 EntryPoint를 찾는다.

  • 프로그램 주소 '00456001'에서 멈추는데, 이곳이 패킹된 파일에서의 EntryPoint로, Detect It Easy로 확인한 진입점과 동일하다.

  • PE를 눌러서 IMAGE_NT_HEADERS 하위의 IMAGE_OPTIONAL_HEADER로 가보면, AddressOfEntryPoint인 00056001ImageBase인 00400000을 확인할 수 있다.
  • 프로그램이 메모리에 로딩 시 ImageBase + AddressOfEntryPoint(상대주소)로 저장되므로, 진입점은 00400000 + 00056001 =00456001 이다.

PUSHAD

  • 패킹된 파일의 EntryPoint는 PUSHAD 명령어로 시작한다.
  • 모든 레지스터 값을 스택으로 백업하는 동작 수행

Unpacking Code

PUSHAD 다음으로 Unpacking Code가 온다.

압축된 데이터를 풀어 메모리 특정 영역에 저장한다.

POPAD

  • 언패킹 끝나면 POPAD 명령어로 스택에 저장된 레지스터 값을 복구한다.
  • 이후 OEP로 점프해서 프로그램 본래 기능을 수행한다.
  • OEP 아래에서 패킹 전 원본 코드를 확인할 수 있다.

 

2.5 메모리 덤프

OllyDumpEx 플러그인 설치

  • 메모리에 로딩된 PE 파일의 상태 그대로 PE파일로 다시 저장하는 기능 지원
  • OllyDbg에서 UPX를 언패킹한 상태 그대로 PE 파일로 저장할 수 있다.
  • 원본 파일 복구 가능

 

2.6 IAT 복구

  • 윈도우 응용 프로그램은 DLL 형태의 시스템 라이브러리를 사용하고, 프로그램 실행 시 필요한 DLL을 불러와서 사용한다.
  • PE 헤더의 IAT에 DLL에 따라 어떤 함수를 참조하는지에 대한 정보가 저장되어 있다.
  • 메모리 덤프 전 IAT 테이블의 별도 복구 과정이 필요하다.

Detect It Easy 프로그램에서 손상된 IAT 정보를 확인할 수 있다.

"가져오기"를 누르면, IAT 테이블 상태를 보여준다.

  • 손상된 IAT 파일은 PE 헤더 정보를 분석해서 사용하는 DLL과 함수에 대한 정보를 일일이 맞춰야 한다.
  • 정보가 손상됐어도 일정한 규칙을 가지므로 프로그램으로 복구할 수 있다.

 

LoadPE

  • IAT 복구에 많이 사용하는 프로그램

Download LordPE 1.31

  • 위의 링크에서 파일을 받아 압축해제만 하면, LordPE.EXE 파일을 실행해서 프로그램을 사용할 수 있다.
  1. LoadPE 실행 (LordPE.EXE 실행)
  2. Rebuild PE 클릭해서 PE 파일 선택

  • OK 클릭 시 IAT 재구성이 완료된다.
  • 덤프한 PE 파일을 열면 별도의 언패킹 과정 없이 디버깅할 수 있다.

 

3. 매뉴얼 언패킹 사례


3.1 ESP 레지스터를 활용한 OEP 찾기

MUP(Manual Unpacking)

  • MUP의 핵심은 OEP(Original Entry Point)를 찾는 것이다.
  • 다양한 패킹 도구와 기술에 따라 OEP를 찾는 방법이 다르다.

Lena 제 20강의 UnPackMe_EZIP1.0.exe 파일을 사용해서 테스트해본다. 아래 사이트에서 받을 수 있다.

Lenas Reversing for Newbies

re4lfl0w/lena_reversing

Chrome에서 다운로드 차단이 활성화되어있어서 다운로드가 안된다면

설정 → 개인정보 및 보안 → 보안 에서 보호되지 않음(권장되지 않음) 으로 설정 후 다운받으면 다시 원상복귀하도록 하자.

UnPackMe_EZIP1.0.exe은 EZIP 패커로 패킹된 파일이다.

스택 프레임을 언패킹 관점에서 살펴보면, 스택 프레임은 함수가 사용하는 독립적인 메모리 영역으로, 스택 프레임 안에 파라미터와 지역변수를 저장한다.

스택 프레임은 함수가 실행 중 존재하고 함수 실행이 끝나면 사라진다.

스택은 기준점을 중심으로 데이터를 참조한다.

EBP 레지스터

  • 스택 프레임의 기준점을 저장한다.
  • 스택 프레임은 EBP 레지스터를 기준으로 데이터를 참조한다.
  • 프레임 포인터
  • 하나의 루틴에서 서브루틴으로 이동하려면 EBP 레지스터를 따로 보관하고, 서브루틴 동작 수행 후 원래 루틴으로 복귀 시 따로 보관해둔 값을 EBP 레지스터로 복구한다.

ESP 레지스터

  • 현재 사용하는 스택의 맨 위 가리키는 레지스터
  • 스택 포인터

 

서브루틴의 시작: 함수 프로롤그

PUSH EBP

  • 현재 루틴의 EBP 값을 스택에 저장
  • ESP가 가리키는 주소에 현재 루틴의 EBP 저장된다.

MOV EBP, ESP

  • ESP에 있는 데이터(이전 루틴의 EBP 값 저장하는 스택 주소)가 EBP 레지스터에 저장된다.
  • 서브루틴의 EBP 레지스터에는 이전 루틴의 EBP 가리키는 스택 주소가 저장된다.

 

서브루틴의 종료: 함수 에필로그

MOV ESP, EBP

  • 이전 루틴의 EBP를 가져오기 위해 이전 루틴의 EBP가 저장된 스택 주소가 저장된 현재 루틴의 EBP 값을 ESP 레지스터에 저장한다.
  • ESP는 스택의 Top을 가리키므로, EBP 값이 스택 맨 위에 들어간다.

POP EBP

  • ESP에 저장된 데이터가 EBP 레지스터에 복사된다.
  • EBP 레지스터에는 서브루틴 시작 전 값이 복구된다.

 

  • 서브루틴이 언패킹하는 로직이라고 생각하면, 서브루틴 들어가기 전 ESP 레지스터에는 현재 루틴의 EBP 레지스터 값이 들어가고, 언패킹 로직이 종료되면 현재 루틴으로 복귀 시 다시 서브루틴에 저장한 EBP를 ESP에 저장한다.
  • 따라서 ESP 레지스터가 가리키는 값을 읽는 동작에 Break Point를 설정하면 언패킹 로직이 종료되는 시점을 파악할 수 있고, 패커가 언패킹 로직 종료 후 OEP로 점프하면 OEP로 갈 방법을 찾을 수 있다.

 

3.2 하드웨어 브레이크포인트

많이 사용하는 BreakPoint에는 Software Breakpoint와 Hardware Breakpoint가 있다.

Software Breakpoint

  • 개수 상관없이 필요한 만큼 만들 수 있다.
  • BP 설정한 명령어 맨 앞의 1바이트가 CC(INT 3)로 대체된다.
  • 프로세서가 명령어 수행하다가 명령어 맨 앞의 바이트가 CC이면 인터럽트를 발생시키고, 운영체제는 실행 제어권을 인터럽트 핸들러에 넘긴다.
  • 인터럽트 핸들러는 인터럽트 서브루틴을 호출해서 인터럽트 서브루틴이 수행되고, 수행이 끝나면 원래 루틴으로 복귀하고 프로세서가 명령어 수행을 계속한다.
  • Software Breakpoint는 실행 동작에 대해서만 설정 가능하다.

 

Hardware Breakpoint

  • 프로세서에서 제공하는 특정 영역에 Breakpoint에 대한 주소를 적는 방식
  • 프로세서의 디버거 레지스터(디버거 위해 제공하는 레지스터)에 breakpoint를 설정하고자 하는 주소를 입력한다.
  • 32bit 아키텍처에는 DR0 ~ DR7의 8개의 디버거 레지스터가 있는데, DR0 ~ DR4의 4개의 레지스터가 BP 위해 사용된다.
  • hardware breakpoint는 프로세서에 의해 직접 실행되는 명령어뿐만 아니라 실행되지 않고 메모리에 저장된 데이터에도 bp 설정이 가능하다.
  • 접근(Access), 쓰기(Write), 실행(Execute) 동작에 대해 BP 설정 가능
  • BP 설정하는 데이터 크기(byte, word(2byte), dword(4byte)) 지정 가능

 

3.3 OEP 찾기와 언패킹된 프로그램 저장

  • F9로 프로그램을 실행하면 '004650BE' 주소에서 실행이 멈추는데, EntryPoint에 해당한다.

 

  • 그리고 F7로 들어가면 함수 프롤로그를 만날 수 있다.

 

ESP 레지스터 값이 EBP 레지스터에 저장된다.

 

스택 top(ESP 레지스터가 가리키는 곳)에는 EBP 레지스터 값이 저장되어 있다. 현재 EBP 레지스터에는 EBP 값이 저장된 스택의 주소를 가진다.

언패킹이 끝나면 다시 이전 루틴의 EBP 값을 ESP 레지스터에 가져와서 EBP 레지스터에 저장해야 하므로, ESP 레지스터 값이 가리키는 메모리 주소에 BP를 설정하면 OEP로 이동하는 지점을 확인할 수 있다.

스택 top에 저장된 이전 루틴의 EBP 값에 hardware breakpoint를 설정한다.

 

x32dbg에서 이전 루틴의 EBP 값이 저장된 스택 top 주소 0019FF70에서 우클릭→ 덤프에서 따라가기 를 선택하면 덤프를 확인할 수 있다.

스택 top에 저장된 EBP 값에 우클릭 → 중단점 → Access(R/W) (데이터를 읽어야 하므로) → Dword (32bit에서 4byte 단위로 데이터 읽으므로)

 

  • 중단점 창에서 중단점이 설정된 것을 확인할 수 있다.

 

 

  • F9를 하면 jmp eax에서 멈추고, 이전에 mov esp, ebp와 pop ebp 명령어가 실행되어 EBP 레지스터에 기존 루틴의 EBP 레지스터가 복구되고 스택에서 19FF80이 pop 된 것을 확인할 수 있다.
  • JMP EAX로 EAX 레지스터에 원래 코드가 들어있는 것을 추측할 수 있다.

 

  • F8로 step over(건너서 단계 진행)을 하면, 0x004271B0으로 점프하고 스택 프레임을 생성하는 함수 프롤로그가 보인다.
  • 이것으로 004271B0이 OEP임을 알 수 있다.

 

3.4 MUP 결과 확인하기

메모리 덤프

EIP가 OEP(004271B0)를 가리키고 있을 때, 플러그인 ->  Scylla 선택한다.

따로 OEP 설정하지 않아도 EIP가 가리키는 값으로 자동 설정된다.

 

IAT Autosearch를 누르면 위와 같은 창이 뜨고 IAT Info가 업데이트된다.

그리고 Get Imports 버튼을 누른다.

 

 

이후 Dump를 누르면 덤프한 파일을 저장할 수 있다. 디폴트로 원본파일_dump.exe로 설정된다.

Fix Dump를 눌러서 저장한 dump 파일을 불러온다.

그럼 _SCY가 붙은 파일이 생성되고, 최종 실행파일에 해당한다.

 

이 프로그램의 경우, IAT 복구 없이 프로그램을 실행할 수 있다.

 

UnPackMe_EZIP1.0_dump.exe를 F9로 실행시키면, OEP인 004271B0에서 실행이 멈추고 EntryPoint라고 표시되는 것을 확인할 수 있다.

 

 

4. 코드 인젝션


4.2 코드 인젝션 개요

코드 인젝션(Code Injection)

  • PE 파일 빈 공간에 쉘코드 입력해서 실행하게 하는 기술
  • 쉘코드: 기계어로 만든 크기가 작은 코드. 운영체제의 명령 쉘을 이용해서 공격자가 원한느 동작 수행하기 위해 사용한다.

코드 인젝션 동작 과정

  1. 사용하지 않는 공간에 쉘코드 입력 후
  2. 하나의 명령어 선택해서 쉘코드가 있는 영역으로 점프하는 코드로 바꾼다.
    • 원래 명령어는 쉘코드 맨 위에 넣는다.
    • 실행 시 Instruction A → Instruction B → Jump code A → Instruction C → Shellcode → Jump code B → Instruction D 순서로 실행된다.

 

디버거를 활용해서 코드 인젝션 공격을 수행 방법

1) 파일 열어서 코드 영역에 공간이 있는지 확인한다.

  • 쉘코드는 실행 가능해야 하므로, 실행 가능한 메모리 영역에 넣는다.
  • 메모리 맵(memory map)에서 코드를 실행할 수 있는 메모리 영역을 확인한다.

2) 쉘코드

Offensive Security's Exploit Database Archive

Exploit Database 내 사용자(BroK3n)를 강제로 추가하는 기능을 가진 쉘코드가 있다.

\x는 16진수임을 나타내는 것으로, \x를 제거하고 쉘코드를 만든다.

 

4.2 쉘코드 입력

대부분 실행 가능한 주소 영역은 코드 영역과 일치한다.

코드 영역 위아래를 보면 사용할 수 있는 공간을 찾을 수 있다.

abexcm1.exe 파일에 쉘코드 입력하기

  • codeengn Basic L01 파일과 동일하다.

x32dbg로 열고, F9로 EntryPoint에서 멈춘다.

00401067 주소부터는 비어있는 공간이므로, 이 공간을 사용한다.

  1. PUSH 0을 JMP 00401067 로 변경한다.
  2. PUSH 0은 쉘코드 바로 위인 00401067에 넣고 (이렇게 해야 오류 발생 X), 00401069부터는 쉘코드를 넣는다.
  3. 쉘코드 입력 후, 마지막에 JMP 00401002로 원래 프로그램으로 돌아가도록 한다.
728x90
반응형

관련글 더보기