패킹 – 프로그램 코드 암호화

패킹은 요즘 안티 디버깅으로 각광받는 기술로 그 내용이 광범위 하여 따로 빼놓았다(원래 안티 디버깅 기법 중 하나이다).

프로그램의 코드를 분석하기 어렵도록 암호화하거나 정크 코드(불필요한 쓰레기 코드)를 삽입하는 행위를 패킹(Packing)이라 하는데, 이러한 패킹을 진행해주는 도구를 패커(Packer)라고 한다. 패킹 원리는 간단 한다. 프로그램을 특정 키를 이용하여 암호화 하고, 이를 해당 키를 이용해 복호화 해야 정상적으로 실행할 수 있다. 하지만, 강력한 패킹 도구는 분석을 어렵게 하기 위해 수십에서 수백 번 패킹을 하고, 부분적인 패킹뿐만 아니라 중간 중간에 안티 디버깅 기술도 함께 넣어 놓아 분석가의 안구 충혈을 일으키는 좋은 도구들도 많다. 따라서 요즘은 프로그램 보호의 가장 기본적인 단계가 바로 패킹이다(상용 패커들은 패킹 도구에 일반적인 안티 디버깅 기능도 함께 포함하여 패킹만 진행해도 일반적인 프로그램 디버깅 행위로부터 보호할 수 있다).

그럼 먼저 암호화 되는 방식을 설명하겠다. 암호화 방식은 3부에서 XOR 암호화를 진행하면서 이미 개념을 이해했다면 쉽게 이해가 될 것이다. 즉 프로그램은 처리를 위해 복호화가 가능하도록 만든 암호화 코드는 실제 어셈블리 상에서는 XOR 연산을 진행하게 된다.

[그림] 코드 암호화와 복호화

먼저 암호화 논리를 이해하기 위해 어셈블리를 이용하여 간단한 예제를 만들어 볼 예정이다. 즉 XOR를 이용하여 Code라는 문자열을 암호화 한 상태로 저장하고, 이를 해제하는 어셈블리 명령을 집어넣어 어떻게 진행되는지를 설명하도록 하겠다. 이 책에서 패커를 만드는 법에 대해서는 진행하지 않을 예정이다. 따라서 여기서는 헥사 파일을 직접 수정하여 어떻게 복호화 되는지 알아보자.

아래 프로그램은 XOR를 이용하여 2번 암호화 한 것이다. 예제에서는 XOR 연산이 바로바로 나오지만 이를 실제 적용할 때는 여러 보호 코드를 함께 삽입하여 진행하여야 XOR를 진행하는 암호화 코드를 보호할 수 있다.

packing.asm
.586

.model flat, stdcall

option casemap :none

;윈도우 함수를 호출할 수 있는 라이브러리를 포함한다.

include \masm32\include\windows.inc

include \masm32\include\kernel32.inc

include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib

includelib \masm32\lib\user32.lib

; 사용할 메세지를 데이터 영역을 지정하여 사용

.data

messagetitle db “Message”, 0

; 사전에 XOR 연산으로 계산해 놓은 값을 입력하여도 되지만, 정확성을 위해 헥사값을 직접 수정할 예정이다.

message db “Code”, 0

.code

start:

; message값을 EAX로 가져온다.

mov eax, dword ptr ds:[message]

;정크코드 삽입

push ecx

mov ecx, 3

pop ecx

push eax

xor eax, eax

add eax, 10

xchg eax, ecx

pop eax

; XOR를 진행한다. 역으로 87654321순으로 진행한다.

xor eax, 87654321h

xor eax, 12345678h

; 변경된 내용을 message 위치에 저장한다.

mov dword ptr ds:[message], eax

; MassageBoxA 함수를 호출한다.

invoke MessageBoxA, NULL, addr message, addr messagetitle, MB_OK

; 프로세스를 종료한다.

invoke ExitProcess, 0

end start

[예제] 패킹 예제

그럼 Code를 XOR를 이용하여 2번 암호화를 진행해보도록 하자. 예제에서는 2번 진행하였지만, 실제 구성 시에는 수십, 수백 번 진행할 수도 있다. 그리고 실제 동작 코드 중간 중간에 안티 디버깅 코드를 함께 넣으면, 분석도 사람이 진행하기 것이기 때문에 매우 효과적으로 분석에 혼란을 줄 수 있다. 이제 Code라는 값의 헥사값을 확인하고, 변경하기 위해, HxD와 XOR 연산에 도움을 줄 계산기를 이용하여 XOR를 진행하여 직접 변경해 보겠다.

먼저 위 어셈블리 예제를 2부 어셈블리 내용을 확인해 컴파일 한다. 그리고 다음과 같이 진행하자.

// HxD를 이용해 예제를 오픈 하고 Code의 문자열의 16진수 값은 확인하자.
436F6465

[그림] Code의 헥사 코드

// 인텔 계열은 리틀 인디언 방식을 취하기 때문에 우리가 원하는 결과를 얻기 위해서는 역순으로 XOR 하여야 한다. 따라서 XOR 결과를 역순으로 나열한 후 계산기를 이용한 XOR를 진행한다.

// 65646F43

// 65646f43h를 XOR 0x12345678를 진행한 결과 값은 다음과 같다.

// 7750393B

// 이 XOR 0x12345678을 진행한 결과에 다시 XOR 0x87654321을 진행한 결과는 아래와 같다. 이는 계산기를 이용하면 수월하게 확인 할 수 있을 것이다.

// F0357A1A

[그림] 계산기를 이용하면 각 진수들의 연산 값을 쉽게 확인할 수 있다

// 위 값을 이미 알듯이 리틀 인디안 방식으로 처음 EAX 레지스터에 복사될 때 역으로 입력되기 때문에, 헥사 에디터인 HxD를 이용하여 아래 그림과 같이 역으로 “1A 7A 35 F0″순으로 입력한다.


[그림 24-35] 역순으로 입력하여야 한다.

[실습] XOR를 이용한 데이터 암호화 진행

이제 더 이상 Ollydbg의 All referenced text strings으로 Code라는 파일상에서 찾아낼 수 없다.

그럼 Ollydbg를 이용해 코드를 보면서 분석을 진행해보자.

00401000 p>/$ A1 08304000 MOV EAX,DWORD PTR DS:[403008] ß 403008 위치의 값을 가져온다.
// 정크 코드

00401005 51 PUSH ECX

00401006 B9 03000000 MOV ECX,3

0040100B 59 POP ECX ; kernel32.772BED6C

0040100C 50 PUSH EAX ; kernel32.BaseThreadInitThunk

0040100D 33C0 XOR EAX,EAX ; kernel32.BaseThreadInitThunk

0040100F 83C0 0A ADD EAX,0A

00401012 91 XCHG EAX,ECX

00401013 58 POP EAX ; kernel32.772BED6C

00401005 |. 35 21436587 XOR EAX,87654321 ß 해당 값을 XOR 진행 결과 7750393B

0040100A |. 35 78563412 XOR EAX,12345678 ß 해당 값을 XOR 진행 결과 65646F43

0040100F |. A3 08304000 MOV DWORD PTR DS:[403008],EAX ß 변경된 값을 기존 위치에 복사한다.

00401014 |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL

00401016 |. 68 00304000 PUSH packing1.00403000 ; |Title = “Message”

0040101B |. 68 08304000 PUSH packing1.00403008 ; |Text = “\x1Az5? ß 메시지 “Code”를 출력하는 부분

00401020 |. 6A 00 PUSH 0 ; |hOwner = NULL

00401022 |. E8 0D000000 CALL <JMP.&user32.MessageBoxA>; \MessageBoxA

00401027 |. 6A 00 PUSH 0 ; /ExitCode = 0

00401029 \. E8 00000000 CALL <JMP.&kernel32.ExitProce>; \ExitProcess

0040102E .- FF25 00204000 JMP DWORD PTR DS:[<&kernel32.>; kernel32.ExitProcess

00401034 $- FF25 08204000 JMP DWORD PTR DS:[<&user32.Me>; user32.MessageBoxA

[실습] Ollydbg를 이용한 XOR 분석

XOR를 2번 거쳐야만 아래와 같이 정상적인 값으로 변환하여 결과값을 얻을 수 있다.

만약 좀더 보안을 강화한다면 XOR를 진행하는 값 역시 암호화 하는 방안도 있고, 사이사이 정크코드를 더 삽입하여 혼란스럽게 만드는 방법도 있을 것이다.

[그림] 암호화한 코드를 해제한 후 Code라는 정상 메시지를 출력

Facebook Comments

Leave A Reply

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다

This site uses Akismet to reduce spam. Learn how your comment data is processed.