본문 바로가기

[PROGRAMMING]

코드분석 도구 개발하기(C언어)

코드분석/악성코드분석/코드 분석/악성 코드 분석/리버싱/바이러스 분석/CODE 분석/익스플로잇 분석

코드를 분석할때 복잡한 코드를 현재 내가 진행중인 위치와 코드의 얼만큼을 분석했고 얼만큼의 함수를 더 분석해야 하는지 파악하며, 코드의 전체적인 흐름과 빠른 동적분석 진행을 위해 제작하게 되었습니다.

 

<동작 구성도 및 동작원리>

 

 

 

동작 원리에 대해 간단히 설명하겠습니다.

우선 디버깅 할 대상의 프로세스를 CreateProcess 함수에 DEBUG_PROCESS를 인자로 호출하게 되면 프로세스가 생성될때 디버깅 이벤트를 발생할 수 있는 프로세스로 생성이 됩니다.

이때 디버깅 루프를 돌면서 각 이벤트에 맞춰 프로그래밍 해주면 됩니다.

디버깅 루프코드만 다음을 참조했습니다.(http://blogs.msdn.com/b/reiley/archive/2011/07/21/data-breakpoint.aspx)

 

 

 

 

 

처음 발생되는 이벤트들은 DLL이벤트 입니다.

한개의 프로세스(프로그램)가 실행이 되면 해당 프로세스에서 필요한 DLL을 호출하게 됩니다.

이때 디버깅 이벤트는 DEBUG_EVENT 구조체를 사용하여 현재 발생된 디버깅 이벤트와 호출된 DLL 핸들 및 ImageBase 또는 발생된 DebugEventCode들의 정보를 담고있습니다.

 

 

 

DLL의 디버깅 이벤트가 발생하면 우선 제가 만든 함수인 PeParse함수에 디버깅 이벤트가 발생된 DLL의 핸들과 ImageBase 등의 기타 정보들을 인자로 호출합니다.

 

 

 

 

PeParse의 함수구조에대해 간단히 설명하겠습니다.

우선 함수에 "DLL", "EXE"문자열을 대상으로 DLL이벤트인지 EXE이벤트인지 IF문으로 분류하여 분석을 합니다.

그 이유는 EXE인경우 IAT테이블 대상으로 PE 구조를 파싱하고 DLL인경우 EAT테이블을 대상으로 파싱하기 위함입니다.

처음 함수에 진입하면 VirtualAlloc 함수로 65525의 크기 만큼의 동적메모리를 생성합니다.

여기서 동적메모리를 생성하는 이유는 현재 호출된 DLL또는 EXE파일의 바이너리 데이터를 그대로 메모리에 올리고 이 상태에서 PE구조 값과 .TEXT섹션의 OPCODE의 데이터들을 얻기 위해서입니다.

위에서 말씀드린대로 호출된 DLL또는 EXE핸들로 CreateFileMapping함수를 호출하고 MapViewOfFile을 호출하게되면 현재 호출된 DLL/EXE파일이 그대로 메모리에 올라가게 됩니다.

위의 그림에서 확인해보면 LPVOID Mapview 변수에 MapViewOfFile이 호출되면 메모리의 시작주소를 반환하게 되는데 직접 메모리를 확인해보면 호출된 DLL파일의 바이너리가 메모리에 올라가 있는것을 확인 할 수있습니다.

이 메모리의 시작주소를 대상으로 각각의 PE구조를 파싱하게 됩니다.

 

 

그리고 호출된 DLL의 이름을 찾는 방법에 대해 설명드리겠습니다.

실행파일의 이름을 찾는 방법은 간단하게 GetModuleFileName함수를 사용하는 방법 그리고 로드된 PE를 가상메모리에 올린후 직접 OFFSET을 계산하여 이름을 알아내는 방법이 있습니다.

GetModuleFileName의 함수는 프로세스의 핸들(IMAGE BASE)을 첫번째 인자로 전달해주면 호출된 파일의 경로와 이름을 쉽게 알아낼 수 있습니다.

저는 PE구조에 대해 공부하기 위해 직접 OFFSET을 계산하여 이름을 얻을수 있도록 구현했습니다. 

 

 

우선 IMAGE_IMPORT_DATA_DIRECTORY를 보면 EXPORT Table의 값이 있는지 없는지 체크합니다.

그리고 값이 있을경우 RVA이기 때문에 FILE OFFSET으로 변경해 줘야합니다.

(상세 계산법은 시중에 책이나 인터넷에 많이 알려져있으니 참고하시기 바랍니다.)

첫번째 작업으로 EXPORT Table의 주소값이 포함된 영역의 섹션을 찾습니다.

찾은경우 해당 섹션의 RVA값과 Pointer to Raw Data값을 뺍니다.

ntdll.dll에서 해당 값을 연산했을때 결과는 0x1000 - 0x400h = 0xC00 가 됩니다.

그럼 이제 여기서 EXPORT Table의 RVA값과 섹션에서의 연산값 0xC00를 다시 빼줍니다.

결과는 0x361B0 - 0xC00 = 0x355B0 값이 됩니다.

  

 

 

그림에서 확인해보면 IMAGE_EXPORT_DIRECTORY(IMAGE_EXPORT_DESCRIPTOR)의 시작주소 인것으로 확인할 수 있습니다.

이상태에서 Name RVA값을 얻고 해당 값 역시 RVA이기 때문에 OFFSET값을 구해 줘야합니다.

 

 

 

 

위에서 설명한 방식대로 구하게 되면 0x3AF6E - 0xC00 = 0X3A36E값이 되고 해당 파일 OFFSET으로 가보면 실제 ntdll.dll 문자열이 있는것을 확인 할 수있습니다.

처음에 MapViewOfFile함수로 로드되는 모듈의 핸들을 인자로 넘겨 호출된 모듈의 PE를 가상메모리에 로드 했었는데요 실제 이 주소로 가보면 PE첫 시작임을 확인 할 수 있었습니다.

해당 가상메모리의 주소는 0x6400000 였습니다. (할당되는 가상메모리의 주소는 유동적인 주소입니다.)

이 값에다 ntdll.dll의 OFFSET값 0x0X3A36E을 더하면 해당 가상메모리상태에서의 OFFSET값을 구할 수 있게됩니다.

 

다시한번 확인해보겠습니다.

이번 테스트의 경우  MapViewOfFile함수로 메모리에 로드했을때의 주소값은 0x2610000값 입니다. 

 


실행파일이 실제 로더에의해 실행되어질때 메모리에 매핑되는기준이 PE포맷의 IMAGE BASE주소값입니다.

MapViewOfFile 함수로 가상메모리에 매핑된 주소는 0x2610000 값입니다.

즉 MapViewOfFile로 리턴되는 가상메모리의 주소값이 IMAGE BASE의 값으로 보면됩니다.



이제 가상메모리주소값(IMAGE BASE값) 0x2610000 에다 RVA(Relative Virtual Address)을 더해주기만 하면됩니다.

RVA(Relative Virtual Address) 상대 가상 주소 로 해석할 수 있습니다.

즉 메모리에 매핑된 주소값으로부터 얼마만큼 떨어져 있느냐? 다시해석해서 IMAGE BASE에서 얼마만큼 떨어져 있는 주소값이냐?

이것을 공식으로 만든것이 IMAGE BASE + RVA = VA 가 되는것입니다.



이제 해당 0x2610000 + 0x3A36E 더한값을 포인터변수에 저장하고 출력해주기만하면 호출된 모듈의 이름을 구할 수 있습니다.

이런식으로 로드된 모듈의 핸들로 가상메모리에 매핑하고 코드를 작성해 나가면 됩니다.

물론 코드분석 대상의 프로세스 핸들(EX)notepad.exe)로 코드를 작성해야합니다.

 

<바운딩 처리된 PE에서의 함수호출 처리>

처음 코드를 작성하면서 E8과 FF15 부분에대한 OPCODE값을 계산해서 IAT테이블에서 해당되는 함수를 찾도록 구현했을때 두가지 처리방식에 대한 문제점이 발생했습니다.

첫번째로 바운딩 처리되었을때와 레지스터를 이용하여 함수호출을 하는경우입니다.

그 첫번째로 바운딩 처리된 PE구조를 확인해보겠습니다.

 

 c:\windows\system32\notepad.exe를 PEVIEW로 오픈해보면 다음과 같이 섹션헤더 아래에 BOUND IMPORT Directory Table, BOUND IMPORT DLL Names 섹션이 추가되어있는것을 확인할 수 있습니다.

 

 

IMPORT ADDRESS TABLE을 확인해보면 주소값이 RVA가 아닌 VA로 되어있는것을 확인 할 수 있습니다.

이것은 로딩시간을 단축하기위해 IAT에 메모리에 로드될 주소들을 고정시켜 버린것입니다.

 

 

실제 notepad.exe를 디버거에 올려서 확인해보겠습니다.

CALL GetStartupInfoA 부분의 OPCODE를 확인해보면 FF15 FC100C00이고 이것은 함수를 호출하는 코드입니다.

이것을 Big Endian으로 바꿔주면 FF15 000C10FC 가 됩니다. 즉 호출하는 주소는 0xC10FC가되는데 이 주소는 바운딩된 PE의 IAT테이블에서 찾을 수 없습니다.

 

 

그렇다면 일단 함수 내부로 진입해 보겠습니다.

GetStartInfoA함수의 첫 시작주소는 0x75BD1E10입니다. 참고로 GetStartInfoA함수는 KERNEL32.DLL에 있는 함수입니다.

 

 

현재 로드된 SYSTEM DLL들의 IMAGE BASE값을 확인해보겠습니다.

로드된 Kernel32.dll의 IMAGE BASE값은 0x75BD0000값입니다. 이 IMAGE BASE값은 운영체제가 재부팅될때마다 주소가 바뀝니다.

그 이유는 WINDOWS VISTA이후로 보안차원에서 WINDOWS시스템에서 ASLR(Address Space Layout Randomization)적용으로 메모리에 로딩되는 주소(IMAGE BASE)를 유동적으로 변경하여 사용하도록 했습니다.

그리고 실제 Visual Studio에서 컴파일옵션으로 설정해서 컴파일이 가능합니다.(ASLR이 적용된 PE는 재부팅이 될때마다 변경되는것이 아닌 프로세스가 재 실행될때마다 IMAGE BASE주소값이 변경되도록 컴파일이 가능합니다.)

이어서 함수의 진입 주소값과 로드된 IMAGE BASE값을 빼보겠습니다.

0x75BD1E10 -  0x75BD0000 = 0x1E10

 

 

PEVIEW로 Kernel32.dll을 로드시킨후 EXPORT ADDRESS TABLE에서 해당값을 찾아보면 GetStartupInfoA함수의 RVA주소 라는 것을 확인 할 수있습니다.

정리하면 호출된 함수 내부로 들어가 함수의 시작주소를 구하고 해당 시작주소의 IMAGE BASE를 빼주면 실제 로드된 모듈의 RVA값입니다.

이것을 코드로 옮기면 됩니다.


<레지스터로 호출하는 함수 처리>

 

 

테스트를 위해서 c:\windows\system32\calc.exe를 대상으로 디버거에 로드했습니다.

빨간밑줄의 OPCODE는 FFD7입니다. 이 OPCODE도 함수를 호출할때 사용하는 코드입니다.

근데 기존 호출과 다른점이 레지스터를 이용하여 함수를 호출합니다.

레지스터를 이용하여 호출하는경우 OPCODE는 FFD0 ~ FFD7까지 사용합니다. 각각 D0 = EAX 레지스터를 뜻하며 순차적으로 나열하면 다음과 같습니다.

D0 = EAX / D1 = ECX / D2 = EDX / D3 = EBX / D4 = ESP / D5 = EBP / D6 = ESI / D7 = EDI 

그럼 FFD7 이므로 호출되는 레지스터는 EDI가 될것이고 EDI가 가르키는 주소값은0x75C1CD5C입니다.

이또한 calc.exe가 갖고있는 IAT의 주소값이 아닙니다. 이값또한 Kernel32.dll에서 로드된 IMAGE BASE값과 EXPORT ADDRESS TABLE의 RVA값을 더한 값입니다.

 

 

실제 함수내부로 들어가면 0x75C1CD5C가 함수 시작주소인것을 확인 할 수 있습니다.

 

 

IMAGE BASE주소를 확인하고 연산하면0x75C1CD5C - 0x75BD0000 = 0x4CD5C가 됩니다.

 

 

확인결과 정확히 일치합니다.

정리하면 레지스터를 사용하여 호출하는 함수인경우 bounding 처리된 PE에서 처리된 방식과 같이 함수내부로 진입하여 처음 함수 시작주소를 구하고 로드된 모듈에서 IMAGE BASE를 빼고 연산결과로 나온 RVA값을 로드된 모듈의 EAT테이블에서 찾는다. 로 정리할 수 있겠네요

바운딩 처리되었을때 함수호출과 레지스터를 사용하여 호출하는 함수에 대한 처리는 이와 같은 방법으로 해결 했습니다.

 

<로드되는 모듈(DLL) 관리>

 

위의 방법으로 각각의 상황을 처리하려면 우선 로드되는 DLL마다 각각의 PE의 정보들을 파싱해서 동적메모리를 생성한 후 저장해야 합니다.

처음 Kernel32.dll, User32.dll, ntdll.dll등의 로드되는 모듈의 파싱된 정보가 들어있는 구조체의 주소를 담기위한 2차원배열의 동적메모리를 할당합니다.

그리고 이 동적메모리는 모듈이 로드될때마다 이 코드가 포함된 함수를 호출하여 메모리를 할당합니다.

함수가 호출될때마다 기존에 있던 주소들이 새로생성된 주소로 변경됩니다. 하지만 이렇게 주소가 변경되었다 하더라도 이전에 있던 주소들은 해제를 하지 않았기 때문에 유효한 상태가됩니다.

그래서 이전에 생성 됬던 주소값들을 관리하기위한 변수가 위에서 선언된 2차원 동적메모리 배열변수입니다.

 

 

첫번째 그림에서 ExportAddressFunctionManagerArr 2차원 동적메모리 변수에 주소값들을 관리하기 위한 변수를 생성했다면, 다음으로 모듈의 PE정보를 갖고있는 구조체의 주소를 담기위한 1차원 동적메모리 변수 ExportAddressFunction선언 합니다.

그리고 ExportAddressFunctionManagerArr[0] = ExportAddressFunction으로 연결시킵니다.

그리고 제일 밑에 ExportAddressFunctionManagerArr을 의주소를 관리하기 위한 목적으로 _PDLLPtrBridge구조체의 PTR_ExportAddressFunctionManagerArr변수에 연결시킵니다.

이렇게 연결시키는 이유는 함수의 인자값으로 넘겨줄때 ExportAddressFunctionManagerArr,ExportOrdinalTableManagerArr,ExportNameTableManagerARR을 각각 넘겨줄 필요없이 _PDllPtrBridge 구조체 하나만 넘겨주고 필요한 정보는 또 이구조체에 넣어서 사용하면 편리하기 때문에 이렇게 여러번 연결시켜서 사용했습니다.

 

이렇게 각 정보들을 연결시켜서 최종적으로 완성된 _PDllPtrBridge 구조체는 아래와 같습니다.

 

이 구조체가 담고있는 정보는 함수이름을 파싱하기위한 핵심정보들로 구성되어있습니다.

맨위의 DllHandle,LoadDllImageBase,DllName은 로드된 dll의 핸들정보 dll의 IMAGEBASE정보 그리고 dll의 이름정보를 저장하는 멤버변수입니다.

이변수도 동적메모리 주소공간에 순차적으로 각 값들이 저장되며 멤버변수가 가르키는 주소값은 각값들이 저장되어있는 메모리주소의 시작번지 주소를 담고있습니다.

그리고 아래로 내려오면 SaveCount라는 멤버변수가 있는데 이 함수의 역할은 현재 로드된 dll의 정보를 저장한 횟수를 카운트하는 멤버변수입니다.

예로 ntdll.dll,Kernel32.dll,User32.dll,KERNELBASE.dll이 로드되서 각값들이 이 구조체에 저장이 됬다고 하면 SaveCount의 값은 4가 됩니다.

이렇게 카운트를 하는이유는 추후에 이러한 데이터들을 대상으로 PDllPtrBridge구조체를 배열형식으로 사용해서 값을 찾기 위함입니다.

그리고 이외의 로드된 모듈의 EXPORT DESCRIPTION TABLE 정보, Ordinal Talbe정보, Name Table정보,Address Function Table정보등등의 많은 정보를 담고있습니다.

 

 

<PDllPtrBridge 구조체에서 함수이름 탐색>

 

 

 

내용을 정리하면 위의 그림과 같습니다.

우선 로드되는 모듈에 따라 각각의 PE정보를 파싱하는데 DLL의 EXPORT DESCRIPTION TABLE의 정보를 기반으로 각각의 값을 파싱하며, 테이블에서 파싱되는 값은 다음과 같습니다.

 

Name Rva                          000B8C54        Kernel32.dll

Ordinal Base                      00000001        

Number of Function             00000554         

Number of Name                 00000554

Address Table RVA             B579C

Address Name Table RVA    B6C5C

Ordinal Table RVA               B81AC

 

실제 함수의 진입점 포인터를 저장하기위해 위에서 1차원 동적메모리 배열 ExportAddressFunction을 선언했었고 선언할때 EXPORT DESCRIPTION TABLE의  Number of Function 값만큼 할당을 하고 저장을 합니다.

그리고 나머지 ExportOrdinalTable 함수와 ExportNameTalbe 함수는 EXPORT DESCRIPTION TABLE의 Number of Name 값으로 세팅합니다.

ordinal과 name table의 크기를 같게 정하는 이유는 두개 테이블안의 데이터 갯수는 서로 동일하기 때문입니다.

하지만 Number of Function값은 오직 Address table의 함수갯수와 같은점을 고려해서 해당테이블의 특성에 맞게 사이즈를 할당해서 메모리를 할당하면 됩니다.

 

 

 

그리고 로드된 DLL의 PE데이터들을 관리적 편의를 위해 PDllPtrBridge구조체를 선언해서 관리를 해줍니다.

PDllPtrBridge 구조체의 구성변수들은 위에서 설명한대로 동적할당한 메모리 주소포인터, IMAGE BASE주소,로드된 모듈의 이름, EXPORT Table 정보등을 저장하는 역할을 합니다.

이런식으로 디버깅 루프를 돌면서 DLL 모듈을 호출하는 이벤트가 발생될 경우 PEPARSE함수 내부로 진입하여 동적메모리를 할당하고 PDllPtrBridge 구조체에 관련정보들로 채우게 됩니다.

즉 디버깅 대상의 소프트웨어 브레이크포인트(0xCC) 이벤트가 발생되기 전까지 PDllPtrBridge 구조체에는 ntdll.dll, Kernel32.dll, KERNELBASE.dll, COMDLG32.dll,msvcrt.dll 등의 프로세스가 실행될때 필요한 dll들의 각 PE정보들로 채우게 되는것 입니다.

 

 

<PDllPtrBridge 구조체에서 함수이름을 찾는 과정>

 

처음단계에서는 디버깅당하는 대상의 프로세스에서 소프트웨어 브레이크포인트(0xCC)가 발생되기 전까지 로드되는 DLL 모듈의 PE정보들을 수집하게되고 이 수집된 데이터들이 있는 주소값 및 정보들을 PDllPtrBridge 구조체에 저장하여 관리를 한다고 했습니다.

그럼 이제 실제 함수가 호출되고 해당함수의 DLL에서 함수이름을 찾는 과정에대해 설명하겠습니다.

 

위에서 그림을 가져왔습니다 그림 넘버링 예정

 

 

다시 위의 몇번 그림을 가지고 설명하겠습니다.

우선 현재의 EIP가 0x000C36A6 이고 OPCODE FF15 FC100C00입니다. 하지만 이 주소값은 디버깅 되고있는 프로세스 IAT테이블에서 찾을수 없습니다.

그럼 일단 EIP를 한단게 더진행하여 GetStartupInfoA함수 내부로 진입합니다.

 

 

진입하면 EIP는 함수 시작주소인 0x75BD1E10으로 세팅이 됩니다.

여기서 처리되는게 가장중요한데. 함수 내부로 진입시 내부가 시스템 DLL영역인 경우가 있고 다음과 같이 시스템DLL영역이 아닌경우가 있습니다.

 

시스템 DLL이 아닌경우 함수가 호출되기 전과 호출되고 난 후의 주소값의 거리가 비슷합니다.

그러나 위의 그림에서와 같이 시스템 DLL인 경우 주소영역대가 0x7xxxxxxxx 번대로 할당되어 사용되어집니다

다시 설명드리지만 메모리에 로드되는 주소값(VA)은 IMAGEBASE + AddressFunctionRVA 값으로 구성되어있습니다.

 

그럼 다시 로드된 IMAGE BASE의 주소를 확인해 보겠습니다.

msvcrt.dll, Kernel32.dll, OLEAUT32.dll 모두 시스템 DLL들이며 주소영역대가 0x7xxxxxxxx대인것을 확인할 수 있습니다.

구글에서 이와관련된 자료를 겨우 찾았는데 다음과 같은 정보를 찾았습니다.

 

The system DLLs for Windows are currently based in memory from 0x70000000 to 0x78000000 on the Intel processors and from 0x68000000 to 0x78000000 on the MIPS processors.(http://www.codeproject.com/Articles/35829/Modify-the-Base-Addresses-for-a-DLL-Files-Series)

 

intel processor에서 사용되는 시스템 DLL의 영역은 0x70000000 to 0x78000000 이라고 하네요.

ASLR이 적용이 된다 하더라도 저 범위안에서 주소값이 결정되는것으로 생각이 됩니다.

하지만 이러한 정보들 대부분이 작성된시기가 2006년정도라서 신뢰되는 정보인지 의심이 되지만 그냥 믿고 저대로 코드를 작성했습니다.

현재의 EIP가 0x70000000 to 0x78000000 이면 Context 구조체에서 ESP레지스터 함수시작주소에 진입하는 순간 ESP는 리턴되는 주소로 세팅되어집니다.

그래서 ESP주소에 브레이크포인터(0xCC)를 걸고 탈출하게끔 작성했더니 윈도우7기준으로 아무이상 없이 함수를 잘 찾아줬습니다.

그러나 윈도우XP에서 문제가 발견되었는데 함수를 찾지 못하는 현상이 발견되었습니다.

그이유는 윈도우 XP에서 시스템 DLL이 로드되는 주소가 0x7Cxxxxxx 번대로 로드가 되는겁니다.

제가 예외처리한 영역의 주소는 0x70000000 to 0x78000000 이기때문에 시스템 DLL이라고 하더라도 저 주소 영역에 포함되지 않기때문에 그냥 진행해서 시스템DLL의 내부에서 호출되는 함수들까지 다 처리를 해버리는 경우가 발생되었습니다.

그래서 저부분을 그냥 0x70000000 to 0x80000000 번대로 바꿔주니 간단히 해결되긴했는데 조금더 테스트를 해봐야 정상적으로 작동하는지 정확히 알수 있을듯 싶습니다.

 

 

자 그럼 이제 0xC36A6 GetStartupInfoA 의 함수 내부로 들어와서 함수의 시작주소인 0x75BD1E10로 왔다고 가정합니다.

그럼 0x75BD1E10이 0x70000000 to 0x80000000 내에 포함되니까 if문으로 들어오게 되고 CONTEXT구조체의 ESP가 가르키는 주소에 리턴주소가 들어있기 때문에 해당 주소에 브레이크 포인터를 세팅합니다.

그리고나서 ParsingLoopDllExportName이라는 함수에 진입하게 되는데 이 함수의 역할은 루프를 돌면서 PDllPtrBridge 구조체에서 0x75BD1E10대상의 ExportAddressTable의 주소값과 비교하고 비교후 해당값에 맞는 데이터를 ExportOrdinalTable값에서 조사후 ExportNameTable에서 해당주소값의 함수이름을 찾아주는 역할을 합니다.

 

이 과정들을 좀 더 자세히 보겠습니다.

우선 GetStartupInfoA  함수의 진입 주소가 0x75BD1E10 이며 EIP의 주소값도 동일한 주소값으로 세팅된 상태입니다.

 

 

 

SaveCount의 값으로 로드된 dll의 갯수만큼 루프를 돌면서 LoadDllImageBase(0x75BD0000)라고 가정하고 0x75BD1E10 - LoadDllImageBase(0x75BD0000) = 0x1E10 값을 빼줍니다.

그리고 다시 루프내의 루프를 돌면서 PTR_ExportAddressFunctionManageArr[DllList][EATLocation] 저장된 모든 DLL들의 ExportAddressFunction RVA값과 비교를합니다.

 

 

 

루프를 돌면서 동일한 RVA값을 찾고 0x1E10까지 카운트한 값은 263입니다.

PEVIEW에는 264라고 표기되어있는데 PEVIEW값과 다른 이유는 PEVIEW에서는 COUNT를 1부터시작해서 0x1E10이 있는곳까지의 카운터가 264가된것이고 제가만든 프로그램에서는 0부터 카운터 했기때문에 264 - 1 = 263이 되는것입니다.

 

 

이제 263 값을 ExportOrdinalTable에서 0부터 1씩증가하면서 263이란 Ordinal 값을 갖고있는 녀석을 찾습니다.

Ordinal값 263이 있는곳까지 0부터 1씩증가해서 최종적으로 카운트된 값은 610 값입니다.

 

 

 

 

Export Ordinal Table에서 카운트된 610의 값을 그대로 ExportNameTable에 매치시키면 해당 함수명을 구할 수 있습니다.

이게 가능한 이유는 위에서 설명했었는데 ExportDescriptorTable에는 Address Table이 위치하는 주소값, Ordinal Table이 위치하는 주소값,NameTable이 위치하는 주소값들을 갖고있습니다.

그리고 각 값들이 몇개가 있는지 명시하는 부분이 있었는데요.

그부분이 NumberOfFunction과 NumberOName값이었습니다.

위에서도 언급한대로 NumberOfFunction의 갯수는 AddressTable의 RVA갯수와 같고 NumberOfName의값은 OrdinalTable과 NameTable의 RVA갯수로 사용합니다.

즉 NumberOfName = 8 이면 NameTable의 RVA갯수가 8개라는 얘기고 동시에 OrdinalTable의 RVA갯수도 8이라는 뜻이됩니다.

이러한 방식으로 DLL에서 함수네임을 파싱해서 출력해줍니다.

 

 

 

<프로그램 테스트 분석결과>

 

 

C:>dbg.exe -auto -debugreg c:\windows\system32\notepad.exe 커맨드로 노트패드 프로그램을 분석한 결과입니다.

노트패드 및 계산기,지뢰찾기 등등의 GUI 프로그램은 이벤트 드리븐 방식으로 동작하기 때문에 노트패드 프로그램 영역에 마우스를 움직이거나 클릭하거나 글을쓰거나 할때마다 이벤트가 발생되며 관련된 함수들을 호출하며 루프형식으로 이벤트를 처리합니다.

이렇게 실시간으로 발생되는 이벤트들도 AnalysisReport.txt에 함수가 호출된 순서대로 실시간 로그를 남깁니다.

 

 

 

C:>dbg.exe -auto -debugreg c:\windows\system32\winmine.exe를 커맨드로 지뢰찾기 프로그램을 분석한 결과입니다.

 

 

마찬가지로 C:>dbg.exe -auto -debugreg c:\windows\system32\mspaint.exe를 분석한 결과 입니다.

 

 

 

위와같이 분석이 진행되어 함수가 호출될때마다 실시간으로 txt파일에에 로그를 기록하도록 만들었습니다.

텍스트파일이 생성되는 파일명 규칙을 설명드리겠습니다.

우선 프로그램을 실행시켰을때 첫번째로 파일이 생성된 요일/월/시/분/초/년도/분석대상 실행파일이름/분석모드/부분분석 순으로 저장이 됩니다.

ex)notepad_auto.txt 인경우

dbg.exe -auto c:\windows\system32\notepade.exe로 명령어를 입력하여 분석시 로그파일이름은 날짜_notepad_auto.txt와 같이 생성이되며

부분분석인경우(특정 메모리주소의 시작과 끝을 정하여 분석하는 방법 자세한 내용은 뒤에 설명드리겠습니다.)

dbg.exe -auto 0x400345d 0x400348d c:\windows\system32\notepade.exe로 명령어를 입력하여 부분분석을 진행하여 분석시 로그파일 이름은 _notepad_auto_part.txt 로 분류되어 저장이됩니다.

 

 

AnalysisReport.txt의 내용을 확인해보면 호출된 함수의 EIP주소 그리고 함수명을 확인 할 수 있습니다.

결과 내용을 살펴보면 루프를 돌면서 반복적인 패턴으로 함수들을 중복호출하여 사용하는것을 확인할 수 있습니다.

로그 내용에서 the called funtion name is found in the EXE IAT table과 the called function name is found in the DLL EAT table 두가지 로그로 확인할 수 있는데요

이차이점을 간략히 말씀드리면 우선 함수 호출에 해당하는 OPCODE를 분석하고 LittleEndian을 BigEndian으로 변경해주면 VA주소값이 되는데 이값을 RVA값으로 변경하여 디버깅중인 프로세스의 IAT 함수를 뒤저서 해당되는 값의 함수 이름을 찾게 됩니다.

여기서 MapviewofFile 함수로 메모리에 올려진 PE의 바이너리 데이터들은 로더가 올린 프로세스가 아니기 때문에 IAT의 주소값을 계산해보면 INT를 가르키는 것을 확인 할 수있게 됩니다.

이런점을 이용하여 IAT테이블의 값을 가르켜 사용되는 함수 이름을 가저올수 있게 했습니다.

그리고 두번째 the called function name is found in the DLL EAT table 는 IAT테이블에서 함수이름을 찾지 못하는 경우가 간혹있습니다.

그런경우는 Bounding 처리된 PE구조(IAT주소를 지정된 주소로 고정시키는방식)을 사용할때 Step in 함수 내부로 들어갈경우 RAW파일에서의 IAT테이블에 고정된 주소값과 로더에의해 실행 되어질때의 주소값이 서로 다르게 처리되어 OPCODE를 분석후 IAT테이블에서 함수주소를 찾기에 어려움이 있었습니다.

그리고 또한가지의 어려움은 레지스터를 사용하여 CALL 요청을 하는 부분 EX)CALL EDI or CALL EAX 레지스터를 이용하여 함수를 호출하는 경우 OPCODE자체가 FFD1 ~ FFD7을 사용하게됩니다.

여기서 D1~D7은 각각 D1 = EAX, D2 = ECX, D3 = EDX.. 와 같이 레지스터와 1:1매칭되어 사용되어 지는데 OPCODE자체가 FF15 38130004 인경우 FF15 40001338로 VA이기 때문에 IAT테이블에서 값을 찾을수있지만 FFD1 같은 OPCODE인경우 RVA값이 아니기 때문에 IAT테이블에서 값을 찾을수 없습니다.

그래서 프로그램을 대폭 수정하였습니다.

먼저 디버깅 대상프로세스가 로드되면서 프로세스에서 사용될 DLL들이 호출되는데 호출되는 각 DLL들마다 PE구조를 분석하고 메모리를 할당하여 저장합니다.

예로 KERNEL32.DLL/NTDLL.DLL/USER32.DLL 등등 3개인경우 각각의 PE를 분석하는데 EXPORT ADDRESS TABLE/EXPORT NAME TABLE/ EXPORT ORDINAL 테이블을 모두 저장하도록 해놓고 만약 FFD1인 경우 Step in 함수내부로 진입하여 진입된 EIP주소값과 로드된 DLL들의 PE의 Image Base 값들을 순차적으로 빼고 RVA를 구하여 각각 순차적으로 EXPORT ADDRESS TABLE/EXPORT NAME TABLE/ EXPORT ORDINAL TABLE 들을 뒤저서 함수 이름을 찾도록 구현했습니다.

 

 

<기능 및 옵션 설명>

 

1.help 기능

 

 

 

프로그램을 사용하면서 분석대상 실행파일 위치를 잘못지정 했거나 옵션을 잘못지정 했을경우 메뉴를 확인할 수 있게 만들었습니다.

한글로 되어있기 때문에 한국인?이라면 쉽게 사용법을 확인하실수 있습니다.

 

2. auto mode

 

auto mode는 말그대로 프로그램의 시작부터 끝까지 자동으로 분석을 하고 그 결과를 리포트 하는 기능입니다.

사용 예)c:\> xxx.exe -auto c:\windows\system32\notepad.exe

 

<Auto mode 분석 예시>

 

3.Step mode

 

 

Step Mode는 제가 디버깅 하기위한 목적으로 만들었습니다.

Step Mode Original Entry Point에 브레이크 포인트를 걸고 순차적으로 디버깅을 진행합니다.(그냥 보통 디버거의 기능이라고 보시면됩니다.)

수동으로 직접 Enter를 입력하여 한라인씩 분석을 해야하므로 제가 개발한목적인 함수의 흐름을 파악하기에는 그닥 좋은 기능이 아닙니다.

즉 이 기능은  분석시간이 오래걸립니다.

사용 예)c:\> xxx.exe -step c:\windows\system32\notepad.exe


4.Manual Mode

 

 

Manual Mode모드는 함수가 호출되는 시점에 브레이크포인트를 걸어서 분석을 진행하게 됩니다.

OEP와 함수호출이 아닌 어셈코드를 모두 건너뛰고 함수를 호출하는 부분만 분석을 해서 결과를 리포트해 주는 기능입니다.

Console로 리포트해주는 결과는 EXE PE상의 IAT에 있는 함수이름을 호출해주는 결과와 함수내부로 진입하여 주소값을 계산하고 주고값이 시스템 DLL인경우 해당 DLL의 EXPORT TABLE값에서 함수를 찾기때문에 두가지의결과 FOUND IN DLL IMPORT/ FOUND IN DLL EXPORT 두가지로 나보여주게 됩니다.

 사용 예)c:\> xxx.exe -manual c:\windows\system32\notepad.exe


5.Partial Analysis Mode 




Partial Analysis Mode 는 분석대상의 메모리주소를 지정하면 지정된 영역만 분석을 진행하고 그 결과를 리포트 해주는 기능입니다.

예로 코드를 분석중 전체 분석이 필요없는 부분인 경우나 분석중에 특정영역에서 호출되는 함수를 파악하고 싶을때 사용할 수 있는 기능입니다.



부분분석을 진행한 결과입니다.

테스트시 주소를 지정한 영역은 0x34308d ~ 0x34309d 까지입니다. 첫번째 0x0으로 나오는 이유는 버그수정을 해야하는데..... 넘흐 귀찮네요 .. 뭐사용하는데 지장은 없으니 나중에 정 불편하면 그때 고처야겠습니다. ㅋ

사용 예)c:\> xxx.exe -auto 0x34308d 0x34309d c:\windows\system32\notepad.exe

사용 예)c:\> xxx.exe -step 0x34308d 0x34309d c:\windows\system32\notepad.exe

사용 예)c:\> xxx.exe -manual 0x34308d 0x34309d c:\windows\system32\notepad.exe

사용 예)c:\> xxx.exe -auto -debugreg 0x34308d 0x34309d c:\windows\system32\notepad.exe

사용 예)c:\> xxx.exe -step -debugreg  0x34308d 0x34309d c:\windows\system32\notepad.exe

사용 예)c:\> xxx.exe -manual -debugreg 0x34308d 0x34309d c:\windows\system32\notepad.exe


6. DebugRegisterView Mode



DebugRegisterView Mode는 명령어 두번째 옵션중 -debugreg 옵션을 주게 되면 사용할 수 있습니다.

해당 옵션의 기능은 그림에서와같이 현재 EIP의 레지스터와 스택상태를 보여주는 옵션입니다.

이 기능 또한 제가 디버깅용으로 만든거기 때문에 수동으로 디버깅하지 않는이상 안쓰시는게 분석속도를 향상시키는데 도움이 됩니다.

 

 

<악성코드 샘플분석 결과>

 

요즘 제가 K사교육을 듣고있는데 거기서 수집한 악성코드 샘플입니다. 실제 악성코드라고 하네요..

아직 수업중에는 분석하지 않았는데 어떤 악성코드인지 궁금하네요 그래서 겸사겸사 프로그램도 테스트해볼겸 한번 돌려 보겠습니다.

 

우선 분석대상은 malware.exe이름의 악성코드입니다.

 

 

 

 

virustotal 확인결과입니다.

총 3개의 섹션으로 구성되어있구요. PE확인결과 패킹은 되어있지 않았습니다.

 

 

역시 VirusTotal 에서 확인한 결과입니다.

PE IMPORT 정보입니다.

해당 파일에서 사용되는 함수들 리스트 입니다.

PE IMPORT정보만 보면 대략 어떤행위를 하는지 예측할 수는있지만 저 리스트가 순차적으로 실행되는게 아니기 때문에 어떤함수를 호출하는지 알수는 없습니다.

그래서 저 함수들을 실행하는 순서대로 리스트화 해주는게 제가 제작한 프로그램의 목적입니다.

그럼 이제 돌려보도록 하겠습니다(두근두근ㅋㅋ)

 

 

 

옵션은 간단히 -auto 로해서 OEP에서부터 프로그램의 끝까지 자동으로 돌렸습니다.

한 1분정도 뒤에 종료가 되네요.. 결과를 확인해보겠습니다.

 

 

흠.....@_@ 결과를 뽑는건 만족하지만 리포트가 조금 그렇네요..(좀 많이 그런가..ㅋㅋ)

Cuckoo샌드박스 보고서처럼 만들어 주면 쓸만할것 같은데......

 

 

GetModuleFileName으로 호출된 모듈의 파일이름을 얻어오는 함수인데요.

이 함수 써서 만들려다가 어떤 헤더파일 선언해서 써야하는데 msdn찾아보니 뭐또 설치하라고해서 그냥 제가 만들어서 썼습니다.

대략 저 함수는 호출된 모듈의 핸들을 인자로 넘겨주면 호출된 모듈의 파일이름 ex)Kernel.dll, Ntdll.dll등 의 이름을 가저오는 역활을합니다.

이 함수 호출이후 중앙에 보면 0x402943 에서 402180함수를 반복적으로 호출해서 사용하는것을 확인할 수 있습니다.

이 부분은 루프문으로 보시면 됩니다.

그리고 하단에 보면 3개의 블록이 있는데 이 역시 4025CB, 4025D0, 402609, 40345C를 순차적으로 호출하고 3번 반복하는것으로 볼수 있습니다.

여기서 4개의 함수(4025CB, 4025D0, 402609, 40345C)가 한세트 인것으로 볼수 있습니다.

아마도 이 4개의 함수 호출이후 분기문에 의해 호출되는 함수가 틀려지는것으로 예상됩니다.

 

 

그리고 여기서부터 함수호출은 파일관련해서 조작하는것으로 볼 수 있습니다.

우선 GetWindowsDirecotryA함수로 윈도우 디렉토리경로를 얻고 GetModuleFileName으로 파일이름을 얻고난 후에 CreateFileA함수 파일을 읽던지 쓰기던지 하기 위해 파일스트림을 열고

SetFilePointer로 파일포인터를 세팅한 다음 그 부분부터 ReadFile함수로 데이터를 읽고있는것을 확인할 수 있습니다.

ReadFile함수부터는 루프문으로 반복적으로 특정데이터의 값을 읽고 있는것을 확인할 수 있습니다.

 

다음으로 다시 SetFilePointer함수로 파일포인터를 세팅하고 ReadFile 파일을 읽고 WriteFile 파일을쓰고를 반복하는것을 확인할 수 있습니다.

 

 

이 부분은 RegCreateKeyExA,RegSetvalueExA함수를 사용하는것으로보아 레스트리에 특정값을 쓰는것으로 추측할 수 있습니다.

뭐 그리고 GetSystemDirectoryA함수로 윈도우 시스템디렉토리 경로를 얻고  또 파일을  WriteFile로 데이터를 쓰고 있습니다.

 

 

여기서는 CreateToolhelp32Snapshot 함수로 프로세스의 상태정보를 얻는?(아마도 프로세스 관련 구조체를 여기에 넣고 이 함수 호출해서 Process32First로 처음 프로세스찾고 Process32Next로 다음 프로세스 찾는걸로 알고있는데 다시한번 봐야겠네요 오래되서 가물가물) 아무튼 Process32First,Process32Next함수로 특정 프로세스를 찾는듯 합니다.

 

여기는 OpenProcess가 호출되는거보니 위에서 Process32Next함수를 반복 호출해서 프로세스를 탐색해서 찾은것 같습니다.

여기는 일단 OpenProcess로 찾은 프로세스를 여는것같네요 그리고 VirtualAllocEx로 동적메모리를 생성하고 그 영역에 WriteProcessMemory로 값을 쓰는것 같습니다.

 

 

계속해서 프로세스를 찾아서 VirtualAllocEx함수와 WriteProcessMemory,GetmoduleHandleA,GetProceAddress,CreateRemoteThread,WaitForSingleObject를 사용하는것으로보아 코드를 인젝션하는것으로 예상되네요.

프로세스를 탐색하고 해당 프로세스영역에 가상메모리를 할당하고 그 영역에 특정 데이터를 쓰는것같은데요

상세한건 OLLYDBG나 IMMUNITY DBG와같은 전용 디버거로 분석하면 단번에 알수 있겠네요

이상 허접한 개발후기였습니다.


 

혹시 사용해 보고싶은 신분들은 비밀댓글로 이메일주소 남겨주시면 보내드립니다으리!!

 

'[PROGRAMMING]' 카테고리의 다른 글

#1 DLL(묵시적 링킹)  (0) 2012.09.20
웹크롤러 설계문서 구현(중..)  (0) 2012.09.11