먼저 문제를 살펴보면 StolenByte를 찾아야한다는 것을 알 수 있다.

여기서 StolenByte란 말그대로 훔친코드로, 패커가 이동시킨 코드의 윗부분(보통 OEP로부터 몇 개의 명령어)이라고 한다.

이는 언패킹을 해도 정상 실행이 되지 않으며 숨겨진 코드를 제자리에 가져도 놓고 제대로 복구를 하면 정상 실행이 된다.

또한, UPX에서는 마지막 JMP 전 POPAD 이후 일정 바이트의 코드를 의미한다.

 

 

먼저 주어진 실행파일을 Immunity Debugger로 실행시켜봤더니 위와 같이 PUSHAD 명령어가 보였다.

저번에 풀었던 문제처럼 언패킹을 해야하나? 라는 생각이 들었다.

 

 

먼저 Exeinfo PE 툴로 확인을 해봤더니 UPX라는 것을 알 수 있었다.

 

 

그래서 저번처럼 cmd 창에서 upx -d 파일이름 명령어를 입력해준 후 언패킹을 해주었다.

 

 

언패킹한 파일을 Immunity Debugger로 열어봤더니 제일 위에 위와 같이 NOP 코드가 써있는 것을 확인할 수 있었다.

 

 

이를 실행시켜보니 위와 같이 알 수 없는 글자들로 깨져 오류가 생기는 것을 볼 수 있었다.

 

 

힌트를 보고 언패킹 전에 패킹된 상태로 실행했던 코드를 잘 살펴봐야겠다는 생각을 했고

다시 처음 다운로드 받았던 파일을 열어봤더니 위와 같이 POPAD를 확인할 수 있었다.

 

패킹된 프로그램을 실행하면 PUSHAD부터 POPAD까지 원본 프로그램의 코드를 복구하는 작업을 할 것이고

POPAD부터 OEP까지의 코드에 어떤 것이 있나 확인해주는 작업이 필요하다.

 

 

위에서 본 POPAD 뒤의 코드를 하나씩 실행시켜보면 위와 같이 스택에 어떤 값들이 저장되는 것을 확인할 수 있다.

 

 

 

다시 언패킹해주었던 실행파일을 열고 NOP코드 부분에 아까 패킹된 파일에서 POPAD 뒤에 값을 PUSH해주었던 부분의 HEX 코드를 위와 같이 입력해주었다.

 

 

그랬더니 위와 같이 NOP으로 있었던 코드들이 딱 맞게 채워지는 것을 확인할 수 있었다.

 

 

이후에 계속 코드를 실행시켜보면 언패킹된 파일에서도 위와 같이 메세지가 깨지지 않고 잘 출력되는 것을 확인할 수 있다.

 

 

따라서 이 문제의 답은 StolenByte인 6A0068002040006812204000이고

구글링했던 StolenByte의 개념과 일치하다는 것을 확인할 수 있었다.

 

먼저 문제를 보면 디버거를 탐지하는 함수의 이름을 찾는 것이 목표였다.

 

 

주어진 파일을 다운받은 후 실행시켜보면 위와 같이 정상이 일정한 시간마다 출력되는 것을 확인할 수 있다.

 

 

이 파일을 Immunity Debugger로 실행시켜보면 위와 같이 디버깅 당함이라는 문자가 출력되는 것을 확인할 수 있다.

 

 

우선 실행 시켰을 때 제일 먼저 나오는 코드 부분이다.

 

 

계속 F8키를 눌러 진행시키다보면 위와 같은 코드를 볼 수 있고 여기서 계속 F8키를 눌러주면 실행창에 뭔가 출력되는 것을 보고 breakpoint를 눌러주었다.

 

 

재시작 후 위에서 발견한 00408454에서 F7키를 누르면 위와 같은 코드로 이동한다.

여기서 IsDebuggerPresent라는 함수를 발견할 수 있다.

 

 

위에서 발견한 의심되는 함수 즉, 0040105E 부분에서 계속 진행시켜보면 위와 같은 코드를 볼 수 있다.

 

 

코드를 진행시켜 보면 EAX 레지스터 값이 1로 설정되는 것을 확인할 수 있다.

TEST 명령어는 두 개의 값을 AND 연산한 결과값을 세팅하여 분기문에 영향을 준다.

 

 

위와 같이 00431024에 값을 PUSH 하는 것을 볼 수 있고 여기서 CALL 명령어를 실행하면 디버깅 당함이라는 값이 출력되는 것을 확인할 수 있다.

 

 

이때 Hex dump 창에서 00431024 부분으로 이동해보면 위와 같은 값을 확인할 수 있다.

 

 

정상 메세지가 출력되도록 하기 위해서는 아까 TEST 명령어로 값을 분기해주는 부분에서 JE를 만났을 때 넘어가도록 해주어야 하기 때문에 위와 같이 EAX 레지스터의 값을 0으로 세팅해주었다.

 

 

그랬더니 위와 같이 분기문에 걸려 0040107E 부분으로 넘어갔고

 

 

위와 같이 CALL 함수를 만나 정상이라는 문구가 뜬 것을 확인할 수 있다.

 

 

해당 주소 부분으로 가보면 위와 같은 아스키코드 값을 확인할 수 있다.

 

 

따라서 이 문제에서 원했던 디버거를 탐지하는 함수의 이름은 IsDebuggerPresent이다.

 

먼저 문제를 보면 손상된 실행파일의 패스워드를 알아내는 문제였다.

 

 

문제 파일을 다운로드 한 후 Immunity Debugger로 열어봤는데 위와 같이 실행할 수 없다는 에러가 뜨는 것을 볼 수 있었다.

 

 

실행파일을 실행하지 않고 분석할 수 있는 툴이 뭐가 있을지 찾아보다가 PEview로 파일을 열어보았더니 위와 같이 exe 파일 헤더(MZ)로 시작하는 데이터들을 확인할 수 있었다.

 

 

데이터가 많지 않길래 스크롤 해보면서 아스키코드 부분을 확인해보다가 위와 같은 부분을 발견할 수 있었다.

비밀번호의 정답 여부를 확인해주는 문구 같았고 혹시 Yeah, you did it! 뒷부분에 JK3FJZh가 정답 아닐까?라는 생각이 들어 인증해봤더니 맞았다.

 

안티 디버깅(Anti-Debugging)

 

1) 정의

 

- 디버깅을 방해하여 분석을 어렵게 하는 기술

- 안티 디버깅을 적용한 프로그램이 실행 중에 있을 때 디버깅을 당한다면 디버깅을 하지 못하도록 에러를 발생시키거나 디버거 프로그램을 종료시키는 등 여러가지 방법을 사용하여 분석을 방해

 

2) 목적

 

- 디버깅을 방지하기 위해 사용

- 각종 안티 디버깅 기법들의 동작 원리를 파악한 후 회피하기 위해 사용

 

 

Static Anti-Debugging

 

1) 정의

 

- 프로그램의 첫 실행에서 디버거를 탐지하는 것

- 안티 디버깅을 수행하는 함수가 호출되는 시점에서만 디버거의 존재를 확인하고 그 뒤에는 탐지하지 않음

- Static 기법이 적용된 파일들은 디버거에서 실행조차 되지 않음

 

2) Static Anti-Debugging의 해제 방법

 

호출되는 API에 후킹을 걸어 원래 API의 루틴이 아닌 다른 루틴을 수행하도록 하는 방법

플러그인을 사용하여 자동으로 해제하는 방법

수동으로 해당 안티디버깅을 풀어내는 방법

 

3) 디버깅 기법

 

PEB(Process Environment Block)

 - 현재 프로세스의 디버깅 여부를 판단하기 위해 이용

NtQueryInformationProcess()

- ntdll!NtQueryInformationProcess() API를 이용하여 프로세스의 디버깅 관련 정보를 얻을 수 있음

NtQuerySystemInformation()

- 디버깅 환경을 체크하는 기법

- 현재 OSDebug Mode로 부팅되었는지 판단

NTQueryObject()

- 시스템에서 어떤 디버거가 다른 프로세스를 디버깅 중일 때 DebugObject타입의 커널 객체가 생성되는데, DebugObject의 존재를 확인하는 것

- ntdll!NtQueryObject() API를 이용하여 시스템의 다양한 종류의 커널 객체 정보를 얻을 수 있음

ZwSetInformationThread()

- ZwSetInformationThread() API를 사용하면 자신을 디버깅하고 있는 디버거를 떼어낼 수 있음

- ZwSetInformationThread() 함수는 스레드에게 정보를 세팅하는 System Native API

TLS 콜백 함수

- 프로그램의 Entry Point 코드보다 먼저 실행되기 때문에 유용하게 사용

- IsDebuggerPresent() 함수로 디버깅 여부를 판별하여 프로그램을 계속 실행할지 결정

 

 

Dynamic Anti-Debugging

 

1) 정의

 

- 프로그램이 실행되는 중간중간 계속하여 디버거를 탐지하는 것

- 프로그램의 실행과 함께 계속하여 실행되며 중간중간 디버거의 유무를 확인하기 때문에 지속적으로 해제가 필요

- 디버거 트레이싱을 방해하여 원본 프로그램의 코드와 데이터를 확인할 수 없게 만드는 것

디버거 트레이싱 : 디버깅 당하는 쪽의 내부 구조를 하나씩(한줄씩) 실행하면서 레지스터, 메모리 등을 실시간으로 확인하는 것

 

2) Dynamic Anti-Debugging의 해제 방법

 

호출되는 API에 후킹을 거는 방법

플러그인을 사용하여 자동으로 해제하는 방법

프로그램을 이용하여 자동적으로 풀어내거나 디버거로 설정하는 방법

수동으로 해당 안티디버깅을 풀어내는 방법

 

3) 디버깅 기법

 

예외(Exception)

- 정상적으로 실행된 프로세스에서 예외가 발생하면 SEH(Structured Exception Handling) 메커니즘에 의해 OS에서 예외를 받아서 프로세스에 등록된 SEH를 호출하지만, 디버깅 당하는 경우에 예외가 발생하면 디버거에서 예외처리를 담당

    정상 실행되는 경우와 디버깅 당하는 경우를 판별하여 서로 다른 동작 수행

Timing Check

- 디버거 트레이싱을 할 때 발생하는 실행 시간의 차이를 측정하여 디버깅 여부를 판별

0xCC Detection

- 일반적으로 프로그램을 디버깅할 때 Software BP(BreakPoing)를 많이 설치하는데, BPx86 Instruction'0xCC’

    → 이 값이 정확히 발견된다면 디버깅 여부를 판별 가능

 

 

 

안티 디버깅 종류

 

출처 : http://www.openrce.org/reference_library/anti_reversing

 

 

 

 

일단 문제를 보면 언패킹을 한 후 serial 값을 찾아내는 것이다.

 

 

먼저 실행파일을 그냥 실행시킨 후 아무 값이나 입력해봤더니 위와 같이 에러 메시지창이 뜨는 것을 확인할 수 있었다.

 

 

Immunity Debugger로 해당 파일을 열어 보았더니 위와 같은 코드로 시작되었다.

여기서 PUSHAD라는 명령어는 EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP 레지스터의 값을 스택에 PUSH해주는 명령어로 레지스터들의 값을 보관해야할 때 사용한다.

즉, 레지스터의 값을 백업하는 용도로 사용하며 스택의 데이터를 범용 레지스터에 채운다.

이 문제에서 PUSHAD 명령어의 의미는 패킹되어있음을 의미한다.

즉, 프로그램을 실행할 때 정상적으로 동작하기 위해 패킹을 풀어야 하기때문에 레지스터의 값을 스택에 저장해놓고 복구해주는 알고리즘이라고 생각하면 될 것 같다.

 

 

패킹을 풀기 전에 Exeinfo PE라는 툴을 이용해서 해당 파일을 열어봤더니 UPX라는 것을 확인할 수 있다.

 

 

언패킹을 해주는 툴을 다운로드 한 후 위와 같이 cmd 창에서 언패킹을 해주는 명령어 upx -d 실행파일을 입력해주었더니 파일을 언패킹할 수 있었다.

 

 

다시 Immunity Debugger로 파일을 열어보았더니 위와 같이 아까와는 다르게 시작하는 것을 확인할 수 있다.

이때 코드가 처음 시작되는 주소가 00401360이므로 이 값이 OEP라는 것을 알 수 있다.

OEP는 Original Entry Point로 암호화되거나 압축되어 있는 실행파일을 다시 원본 바이너리 상태로 되돌린 후 실행된 첫번째 명령어의 주소이다.

 

 

단축키를 이용하여 코드를 진행시켜보다가 위와 같이 두 개의 값을 비교하여 다른 메세지창을 띄워 주는 분기문을 발견할 수 있었고, 이때 주석 부분을 통해 시리얼 값으로 예상되는 AD46DFS547을 볼 수 있었다.

 

 

그래서 위와 같이 시리얼키를 입력해보았더니 성공 메세지 창이 뜨는 것을 확인할 수 있었다.

1. 패킹(Packing)

 

1) 정의

 

- 실행압축

- 실행(PE) 파일을 대상으로 파일 내부에 압축해제코드를 포함하고 있어 실행 시 메모리에서 압축을 해제 시킨 후 실행시키는 기술

- 코드 분석을 통한 프로그램의 악용을 막는 목적으로 활용

 

2) 종류

 

Compressor

- 파일의 사이즈를 줄여 배포를 쉽게 하기 위해 사용하는 packer

- 악성코드에서 많이 활용

- Petite, ASPack, MEW, FSG, UPX

 

Protector

- 원본 코드를 보호하기 위해 사용하는 packer

- 주로 상용 프로그램이나 온라인 게임 파일을 패킹할 때 많이 활용

- 코드 가상화 기법과 코드 난독화 기법을 많이 활용

- Themida, Yoda, ASProtect, armadilo

 

3) 패킹을 하는 이유

 

- 데이터 압축

- 악성코드에서는 작은 용량으로 빠르고 많이 퍼지게 하며 분석이 불가능하게 하기 위한 목적

- 데이터 보호

- 취약하게 나타날 수 있는 중요정보를 포함한 어플리케이션에 대한 노출 최소화

 

4) 패킹된 파일의 특징

 

- Section Name이 일반적이지 않거나 패커의 이름을 가짐

- Code Section은 보통 첫번째 Section이지만, 패킹 시 첫째가 아닐 수 있음

- Code Section은 보통 EP를 가리키므로 MEM_Excute권한을 가지지만, 패킹 시 그렇지 않을 수 있음

- Unpacking하면 Packing된 데이터가 저장되어 있던 세션이 비어있거나 Raw SizeVirtual Size의 차이가 지나치게 큰 세션이 존재

 

5) 패킹 방법

 

- 원본코드를 복원시켜주기 위한 Unpacking Routine이 먼저 동작을 해야 하기 때문에 EntryPointUnpacking Routine을 가리키고 Unpacking Routine부터 실행

- Unpacking Routine을 통해서 Decompressing/Decoding 작업을 수행하여 원본파일의 세션에 맞게 저장

원본파일이 가지고 있던 API를 원활하게 호출할 수 있게 수정

- 원본코드의 EntryPoint(OEP)로 점프

 

 

2. 언패킹(Unpacking)

 

1) 정의

 

- 패킹했던 파일을 푸는 것

- 압축이 해제된 상태의 코드를 덤프뜨는 것

- 강력한 패커일수록 언패킹이 어려움

 

2) 언패킹 방법

 

- Unpacking이 끝나고 나면 원본 코드를 동작시키기 위해 OEP로 분기하기 때문에 OEP로 분기하는 지점의 코드를 찾아야 함

- Unpacking된 데이터와 코드를 메모리에서 덤프하게 되면 파일로 저장할 수 있음

- 메모리 상에서 덤프를 하게 되면 IAT Table이 바인딩된 상태로 파일이 저장되므로 이것을 수정해주는 작업을 해야 함

- 언패킹을 했음에도 파일이 제대로 실행되지 않는 경우 패커가 패킹과정에서 엔트리 포인트 부분의 코드를 다른 부분으로 옮겨두기 때문에 이때 옮겨진 코드를 복구해야 함

 

'2020 WINTER STUDY > CTF Study' 카테고리의 다른 글

[Reversing] Day 6_Anti-Debugging  (0) 2021.02.22
[Reversing] Day 5_CodeEngn Basic RCE L06  (0) 2021.02.19
[Reversing] Day 4_abex' crackme #2  (0) 2021.02.18
[Reversing] Day 3_suninatas 9  (0) 2021.02.18
[Reversing] Day 3_xcz.kr prob 9  (0) 2021.02.18

+ Recent posts