[그림.1]

Cuckoo SandBox동적분석 결과

[그림.2]

 

0x00411CA 주소에서 CALL rnasomew.0043EA30 함수를 호출하는데 이 함수의 내부로 들어가서 분석을 해보면 Heap을 생성하고,

ransomware.exe PE의 .rdata섹션의 데이터를 생성된 Heap주소공간에 복사를 합니다.

 

[그림.3]

PE의 .rdata seciotn의 데이터를 Heap에 복사하는 과정입니다.

[그림.4]

 

그리고 나서 복사된 원본 데이터를 1E51FCC값을 더하여 디코딩을 시작하는데 이중 루프문을 사용하여 총 11463Ah크기만큼 반복하고 44h회 만큼 더합니다.

결국 1163Ah*44h=49E768h(4843368)만큼 루프를 돌면서 디코딩을 하게 됩니다.

 

[그림.5]

 

그리고 디코딩된 힙영역을 VirtualProtect함수로 PAGE_EXECUTE_READWRITE의 실행 권한으로 변경합니다.

 

[그림.6]

 

실행권한을 READWRITE로 변경하고 바로 "JMP EDI"로 점프합니다.

EDI 레지스터가 가르키는 주소는 지금까지 힙을 생성하고 디코딩하고 실행권한을 주었던 힙영역입니다.

이부분을 실행하게되면 Local PE(ransomware.exe)의 EIP레지스터 값이 생성된 힙의 첫번째 주소로 세팅됩니다.

#분석을 하다보면 항상 모든게 새롭고 너~무 낯서네요.. 악성코드 제작자에게 뭔가 하나씩 배운다는 느낌? 이있는 반면 끝까지 분석을 못할것 같은 두려움이 공존하네요..ㅠㅠ

 

[그림.7]

 

점프를 해보면 그림.7과 같이 EIP주소가 힙주소의 첫번재 영역으로 변경되었습니다.

이부분의 코드를 보면 CB0B9615의 값의 DL값 15를 시작으로 하여 복잡한 연산을 통해 XOR BYTE PTR DS:[EAX],DL을 하여 디코딩을 진행합니다.

여기서 EAX값의 레지스터는 힙영역의 주소 즉 자기자신의 주소로 세팅되어집니다.

0x012D3B38에서 0x012D3B64까지 루프를 돌며 디코딩을 하여 그림.7에서 ???로 보여지는 알 수 없는 명령어들이 실행가능한 어셈명령으로 복원 됩니다.

 

[그림.8]

 

코드복원이 완료되면 Virtualalloc 함수로 0x012E0000에 Heap을 생성 하고 첫바이트부터 ransomeware.exe PE의 .rdata섹션을 복사합니다.

 

[그림.9]

그리고 복사된 부분을 다시 디코딩을 통해 복구합니다.

 

[그림.10]

 

다시 Virtualalloc 함수로 힙을 할당하는데 할당된 주소는 0x01420000입니다.(반복적인 부분이라 함수 호출은 생략합니다.)

그리고 다시 그림.9의 과정을 반복 하는데 이번에는 Local PE의 .text섹션의 특정부분을 힙영역에 복사를 하고 디코딩을 합니다. 

헥사덤프 영역을 확인해보면 4D705A로 실행파일 구조인 것을 확인할 수 있습니다.

즉 원본 PE파일의 .text section의 특정영역을 힙영역으로 복사하고 디코딩을 통해 복원하는 것으로 볼 수 있습니다.

 

 

[그림.11]

 

0x012D4240주소부분의 명령을 보면 PUSH ESI를 합니다. 이때 ESI값이 0x014498AC인데 이부분에 복잡한 연산을 통해 0x1420000 ("MPZ...PE..") 바이너리를 정상적인 PE구조로 재구성 합니다.

그리고 복사가 완료되면 0x012D422C REP STOS BYTE PTR ES:[EDI] 명령으로 원본PE 바이너리를 전부 0으로 변경합니다.#(하 당황스럽네요..)

 

 

[그림.12]

 

그리고 복원한 PE 바이너리를 각각 00으로 초기화한 Local PE(Ransomware.exe)의 영역(0x004000000)에 PE Header/ .text section / .data section / .idata section / .reloc section을 각각 복사합니다.

 

[그림.13]

 

그리고 PE재구성이 완료되면 사용한 Heap을 초기화하고 새로생성된 PE의 Entry Pointer로 점프합니다.

점프하면 위의 그림.13과 같이 ransomware.exe의 원본 코드가 새로운 코드로 변경된 것을 확인 할 수 있습니다.

분석하기전 간략히 코드의 흐름을 파악하기 위해서 먼저 프로그램이 종료되기 전까지 분석해본 결과 크게 2개의 함수를 사용하고 그 함수내의 여러개의 서브함수로 구성되어 있습니다.

 

[그림.14]

첫번째 함수 내부로 들어오면 그림.14와같이 되어있습니다.

여기서는 InitialCriticalSection함수로 동기화를 사용하기 위해 0x00440B74영역을 초기화 합니다.

이부분의 설명은 뒷부분에 이어서 하겠습니다.

그리고 다시 함수 내부로 진입합니다.

 

[그림.15]

 

0x004E943를 호출하는 함수와 RtlAllocateHeap을 호출하는 함수로 구성되어 있습니다.

첫번째 함수 0x004E943 의내부로 진입한 하면 그림.16과 같이 되어있는데 HeapCreate 함수를 호출해서 힙영역을 할당합니다.

HeapCreate 함수 호출만으로 메모리에 힙이 생성되는줄 알았는데 그게 아니라고 하네요 HeapCreate함수를 호출하고 RtlAllocateHeap을 호출해야 Heap영역에 메모리가 할당이 되는거라고 합니다.


좋은 설명 ex)http://baeg.tistory.com/38

 

[그림.16]

 

그림.16에 들어가기전 동기화에 대해 간략히 알아보겠습니다.

동기화는 thread 또는 process를 순차적으로 규칙적이게 사용할 수 있도록 제어하는 함수를 말합니다. 즉 도로의 신호등 역할이라고 하는것으로 보면 됩니다..

그중 동기화 기법에 사용되는 대표적인 함수는 CriticalSection, Mutex, Semapore, Event 등이 있습니다.

각 함수마다 사용법과 개념은 비슷하나 차이점이 있습니다. 예로 CriticalSection같은 경우 한프로세스 내의 스레드를 동기화 하기 위해 사용되며 프로세스 간의 동기화는 불가능 하고 그외의 동기화 함수들은 스레드 및 프로세스 간의 동기화를 지원합니다.

그림.16에서 동기화 구간은 0x0042E953(EnterCriticalSection) ~ 0x0042E97D(LeaveCriticalSection)까지 입니다.

동기화된 구간에 진입을 하기 위해서는 CriticalSection이 신호상태가 되어야 하며, 비신호 상태인 경우 다른 스레드가 해당영역에 접근을 하지 못하고 대기 상태가 됩니다.

신호와 비신호 상태를 간략히 정의하면 다음과 같습니다.

 

Signal(신호 상태)  FFFFFFFF      : 스레드가 실행 될 수 있는 상태
Nosignal (비신호 상태) 0000001   : 스레드가 대기하게 되는 상태

 

CriticalSection을 사용하기위해서 처음 InitializeCriticalSection으로 초기화를 해야합니다.

초기화를 하면 그림.16의 하단과 같이 FFFFFFFF 신호상태로 되며, 해당 EnterCriticalSection함수 호출과 동시에 동기화 영역에 접근하게 되면 0000001로 비신호 상태로 변경됩니다.

그리고 LeaveCriticalSection함수 호출로 동기화 영역을 빠저나오게 되면 다시 FFFFFFFF 신호상태로 대기하던 스레드가 해당영역을 접근 가능한 상태로 변경이 됩니다.

 

이제 본격적으로 분석에 들어가면 동기화영역에 0x42E985부분의 함수호출이 있습니다.

함수 내부로 진입하면 Heap을 생성하는데 80000h만큼의 공간을 HeapCreate함수로 호출하게 됩니다.

제가 분석하는 시스템에서의 생성된 Heap메모리 주소는 0x15F0001 입니다.(이주소는 유동적이라 같지 않습니다.)

 

 

 

 

[그림.17]

 

 .text section의 특정데이터를 연산을 통해 ntdll.dll 스트링 문자열을 만들고 스택에 저장합니다.

 

[그림.18]

 

그리고 IsWow64Process, Kernell32.dll 스트링값을 각각 EDX,ECX 레지스터에 저장하고 0x0041C1BE 함수에서 GetModuleHandle 함수를 호출하여 Kernel32.dll의 ImageBase를 얻고 GetProcAdress로 Kernel32.dll의 API함수의 주소들을 가저옵니다.

그리고나서 0x0041C1D7의 IsWow64Process함수를 호출하는데 리턴값이 1이면 32비트 0이면 64비트입니니다.

그림 18번의 리턴값인 EAX레지스터를 확인해보면 1로 32비트의 시스템인것을 확인 할 수 있습니다.

 

[그림.19]

 

 

그림.19를 분석하기위해서는 ACL(Aceess Control List)에 대해서 이해를 해야합니다.(하.. 이번 요놈은 정말 너므 어렵네요 ㅠ)

IT용어사전을 참조하여 알아보면 "객체에 대한 접근이 허가된 주체들과 이들 주체가 허가받은 접근 종류들이 기록된 목록. 개개의 사용자들이 디렉터리나 파일과 같은 특정 시스템 객체에 접근할 수 있는 권한을 컴퓨터의 운영 체계에 알리기 위하여 설정해 놓은 목록이라고 할 수 있다"(참조:http://terms.naver.com/entry.nhn?docId=848707&cid=391&categoryId=391)

그리고 이 ACL에서 Security descriptor를 설정하여 파라미터로 넘겨야하는데 이부분도 IT용어사전을 참조하면 다음과 같다.

"객체의 보안 정보를 포함하고 있는 구조체 및 관련 데이터, 객체의 소쥬자 및 1차 그룹을 식별하는 기능을 제공하며, 또한 임의 접근 제어 목록(DACL:Discretionary Access Control List)과 시스템 접근 제어 목록(SACL:System Acess Control List)를 포함할수 있다."(참조:http://terms.naver.com/entry.nhn?docId=863496&cid=2956&categoryId=2956)

MSDN의 문서를 보면 Security Descriptor는 다음과 같은 구성으로 되어있다는것을 확인 할 수 있습니다.

 

O:owner_sid

G:group_sid

D:dacl_flags(string_ace1)(string_ace2)....(string_acen)

S:sacl_flags(string_ace1)(string_ace2)....(string_acen)

 

여기서 O:owner_sid는 개체의 소유자를 식별하는 SID 문자열이고 G:group_sid는 개체의 주 그룹을 식별하는 SID 문자열 입니다.

그리고 dacl_flags는 Acess가 허가되거나 거부된 사용자의 목록이며 ACE(Access Control Entry) String으로 사용합니다.

마지막으로 sacl_flags는 개체 Access를 감사하는 방법을 제어하며, 감사된 작업은 보안 로그에 이벤트가 기록됩니다.

DACL과 SACL의 Flags는 같은 Control Bit 를 사용합니다.

 

그림.19의 분석을 진행하면 처음 InitializeScurityDescriptor를 호출합니다.

함수명 그대로 Security Descriptor를 초기화 하고 SetSecurityDescriptorDacl함수를 호출하여 dacl을 세팅합니다.

그리고 0x0041FE14에서 UNICODE "S:(ML;;NRNWNX;;;LW)" UNICODE의 문자열을 인자로 ConvertStringSecurityDescriptortoSecurityDescriptor함수를 호출하는데 스트링포맷의 "S:(ML;;NRNWNX;;;LW)"를 SecurityDescriptor로 변환합니다.

스트링포맷에대해 MSDN을 참조하니 Sddl.h헤더파일을 참조하라고 안내합니다.

해당내용을 sddl.h내용의 일부를 악성코드에서 사용하는 코드만 모아서 정리해보았습니다.

 

#define SDDL_SACL                                       TEXT("S")       // SACL tag

#define SDDL_MANDATORY_LABEL                 TEXT("ML")    // Integrity label

#define SDDL_NO_READ_UP                            TEXT("NR")

#define SDDL_NO_WRITE_UP                           TEXT("NW")

#define SDDL_NO_EXECUTE_UP                      TEXT("NX")

#define SDDL_ML_LOW                                   TEXT("LW")   // Low mandatory level

 

하지만 해당 악성코드샘플에서는 SetSecurityDesciptorDacl 에서부터 파라미터로 넘겨주는 값들이 전부 0으로 세팅되어 함수호출 실패상태가되고 이어서 ConvertStringSecurityDescriptortoSecurityDescriptor 함수호출 역시 실패되어 ACL세팅을 하지못하고 분기문을통해  0x0041FE21에서 점프하여 해당 로직을 빠저나가게 됩니다.

 

<참조>

http://blog.daum.net/network_goshen60/6278682

http://msdn.microsoft.com/en-us/library/windows/desktop/aa379570(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/aa374928(v=vs.85).aspx

 

[그림.20]

 

분기하여 SHGetFolderPath함수로 특수폴더의 경로를 얻어오고 PathRemoveBackslash 함수로 얻어온 경로의 마지막 백슬래쉬를 제거합니다.

 

[그림.21]

 

그리고 GetModuleFilename으로 현재 실행중인 프로세스의 이름을 얻어옵니다.

 

[그림.22]

 

Heap을 생성하고 GetModuleFilename으로 얻어온 경로를 memcpy를 이용하여 메모리에 복사합니다.

 

[그림.23]

 

이어서 현재 실행중인 프로세스 ID값을 구합니다.

 

[그림.24]

 

그리고 현재 실행중인 파일의 ImageBase(00400000)값과 3C를더해 PE시그니쳐 위치를 EAX레지스터에 저장해놓고 이 위치를 기준으로 EAX레지스터에 각각 14와 6을 더해서 PE의 Size of optional header(000000E0)값과 Number of section(00000004)값을 얻어옵니다.

그리고 0x00417E0D의 메모리 주소를 보면 ESI레지스터에 값을 저장하는데 EDI+EAX+9C를 더한 값이 가르키는 곳의 데이터를 저장합니다.

이것을 풀이하면 PE + E0 + 0C(EDI+EAX+9C)와 같이 볼 수 있습니다.

해당 값이 가르키는 곳의 데이터를 확인하면 PE의 .reloc section의 RVA값 00045000를 가르키는것을 확인할 수 있습니다.

그리고 .reloc section을 파라미터값으로 isBadReadPtr함수를 호출하는데 해당 함수의 역할은 해당영역을 읽을 수 있는지 여부를 체크하기 위한 함수입니다.

즉 .reloc section의 읽기 권한이 있는지 체크하는것 같습니다.

저 섹션에 읽기 권한이 있다면 False를 리턴하고 읽기권한이 없다면 True를 리턴합니다.

 

 

[그림.25]

 

그리고 PE 구조가 변경되기전의 원래의 파일을 Read_only(읽기전용) 상태로 CreateFile함수를 호출하고 GetFileSizeEx함수로 해당파일의 크기를 구합니다.

 

[그림.26]

 

그림.25에서 OPEN한 PE구조가 변경되기전의 PE파일의 .text section의 상태입니다.

[그림.27]

 

그림.27은 현재 실행중인 변경된 PE파일의 .text section의 상태입니다. 그림.26과 비교해볼때 서로 다른것을 확인할 수 있습니다.

 

[그림.28]

 

그리고 가상메모리를 할당하는데 PAGE_READWRITE권한으로 변경전PE파일 사이즈만큼 VirtualAlloc함수를 사용하여 가상메모리를 생성하고 생성된 메모리의 번지주소를 리턴하는데

제가 분석중인 대상의 할당받은 메모리주소는 0x1670000입니다.

 

[그림.29]

0x1670000 주소에 복사된 원본PE의 바이너리 입니다.

 

[그림.30]

 

0x004293D9에서 CALL함수를 호출하는데 함수 내부로 들어가면 복사된 원본PE바이너리 데이터의 특정바이트를 스택메모리주소에 복사합니다.

그리고 0x4293E2에 CMP,EAX,45564144 의비교문에서 복사된 값과 45564144 값을 비교하여 같을때까지 memcpy함수를 이용하여 45564144 찾습니다.

 

[그림.31]

 

다음은 할당된 Heap메모리영역의 원본PE데이터의 일부를 스택메모리 주소에 복사하는 과정입니다.

이와같이 CMP의 비교문이 같을때까지 반복을 진행합니다.

 

 

[그림.32]

 

만약 반복조건이 성립될 경우 그림.32와 그림.33의 함수로 진입하게 됩니다.

그림.32는 CryptDestroyHash 함수로 해쉬개체를 없애는 함수인데 지금까지 해쉬를 생성한적이 없으므로 해당 함수호출은 하지않고 return문으로 건너뛰게 됩니다.

 

[그림.33]

 

그리고 CriticalSection영역으로의 진입도 하지 못한고 리턴하게됩니다.

해당 CriticalSection영역의 0x42AE52에서 CALL로 함수호출을 하게되는데 내용을 간략히 살펴보면 반복문을 통해 특정값과 연산후 EAX레지스터를 세팅하고 세팅된 EAX값을

MOV DWORD PTR DS:[ECX*4+440C70],EAX 와 같이 ECX*4+440C70 더한값이 가르키는 메모리주소에 데이터를 저장하게 됩니다.

즉 변형된PE(현재 실행중인PE)의 .rdata section 특정위치의 연산결과로 얻은 값을 EAX에 저장하는 연산입니다.

하지만 해당 함수도 조건에 맞지않아 호출하지못하고 Return 하게 됩니다.

[그림.34]

그리고 VirtualFree함수로 원본PE의 바이너리를 복사했던 Heap영역을 초기화 시킵니다.

 

[그림.35]

 

그리고 StringFromGUID2함수 호출로 GUID를 "Global\{431ECAFD-78C3-D5E8-0845-524E38E8715C}"문자열형식으로 변환 합니다.

 

[그림.36]

 

그리고 CreateMutex를 이용해 동기화를 사용하기 위해 뮤텍스 개체를 생성하고 WaitForSingleObject로 뮤텍스를 획득한다.(뮤텍스가 스레드에의해 획득가능할때 Signal, 뮤텍스가 스레드에의해 획득되어 있을때 Non-Signal)

 

[그림.37]

 

그리고 OpenMutex로 GUID값으로 뮤텍스의 핸들을 얻으려는 시도를 합니다.

실패시 다시 그림.35과정을 반복하여 GUID를 생성하고 그림.37과정을 진행합니다.

이와 같이 반복적인 과정을 진행하고 함수를 빠저나옵니다. (이부분은 재확인 필요)

 

[그림.38]

 

0x0043431E영역(.text section)을 PAGE_EXECUTE_READWRITE 인자값으로 호출 합니다.

즉 0x0043431E영역부터 SIZE값 152만큼 코드영역 부분을 읽기/쓰기 권한으로 변경합니다.(원래 코드영역은 읽기 전용입니다. 쓰기불가)

 

[그림.39]

 

그리고 0x445000의 영역(.rdata section) 값으로 연산을 통해 0x0043435D영역(.text section)의 바이너리 데이터값에 EAX값과 덧셈연산을 진행합니다.

만약 이 EAX값이 0이 아닌경우 코드섹션의 바이너리 데이터는 변경이 될것입니다.

하지만 그림.39의 00427C4C의 ADD DWORD PTR DS:[ECX],EAX 명령의 EAX값은 항상 0으로 세팅되어 코드를 변경하지 않습니다.

즉 위의 코드를 분석해보면 분석을 방해하기위한 쓰레기 코드 인것 같습니다.

[그림.40]

 

그림.39의 과정을 빠저나오게되면 본격적인 코드변형이 실행됩니다.

그림.40의 0x42947A 실행주소를 보면 XOR BYTE PTR DS:[EDX+EDI],AL 와같은 명령어를 사용하는데 EDX+EDI를 더한값이 가르키는 주소가 0x0043431E(.text section)영역입니다.

이 영역의 1바이트 부터 AL에 저장된 값과 XOR연산을 통해 순차적으로 코드영역의 데이터를 변경합니다.

[그림.41]

 

메모리영역의 주소값을 확인하면 0x0043431E(.text section)이 값이 0x00401000 ~ 0x00445000 사이의 값으로 .text section의 값임을 확인할 수 있습니다.

 

[그림.42]

 

변형된 코드 영역의 Assembler code영역을 확힌하면 그림.42처럼 되어있습니다.

코드가 변형되어 디버거에서 분석되지 않은 상태로 되어 있는 것입니다.

 

[그림.43]

 

분석되지 않은 코드를 Analyze code 옵션으로 분석가능하도록 변경합니다.

0x43431E부터 스택프레임을 할당 하는것으로 보아 디코딩된 코드영역의 데이터는 함수인것으로 확인할 수 있습니다.

 

[그림.44]

 

그림.38에서 VirtualProtect로 0x43431E(.text section)을 읽기/쓰기 권한을 주었었습니다.

다시 이 권한을 VirtualProtect함수로 0x43431E(.text section) 읽기 전용으로 변경합니다.

 

[그림.45]

 

그리고 함수를 빠저나와서 확인해보면 바로 아래 0x411B486에서 CALL명령으로 0x43431E(.text section)함수를 호출합니다.

 

 

[그림.46]

 

0x005056C0 .text section의 데이터를 0x0012E22C 스택영역의 메모리 주소에 저장합니다.

그리고 그림.46의 0x0041AD87 ~ 0x0041AD8A의 명령루틴을보면 .rdata section의 데이터를 AL레지스터로 이동하고 스택영역에 복사된 .text section의 데이터와 XOR 연산을 진행합니다.

 

[그림.47]

 

그림.46과 그림.47을보면 .text section의 데이터를 .rdata section의 특정바이트와 XOR연산을 하여 디코딩하는것처럼 보이지만

실제결과는 memset함수로 디코딩한 영역을 모두 0으로 변경시켜 분석을 지연시키려는 쓰레기코드로 유추할 수 있습니다.

 

[그림.48]

 

그리고 RegQueryValueEx함수로 DigitalProductId레지스트리 값을 가저옵니다.

 

[그림.49]

 

그리고 여기서부터는 특정데이터를 MD5 해쉬값으로 만듭니다.

암호화를 하기위해 우선 CryptAcquireContext 함수로 CSP(cryptographic service provider )내의 특정 키 컨테이너의 핸들을 얻습니다.

(CSP)는 인증, 인코딩 및 암호화를위한 암호화 알고리즘을 수행하는 독립 소프트웨어 모듈입니다.

 

[그림.50]

 

CryptCreateHash 함수의 첫번째 인자로 CryptAcquireContext의 핸들을 입력하고 두번째 인자로 00008003(CALG_MD5)값으로 MSDN을 참조하면 해당 값이 MD5를 사용하기 위해 할당하는 값인것을 확인 할 수 있습니다.

그리고 마지막 인자값 PUSH EBP 에서 12E348(수정해야함)주소값을 스택에 저장합니다.

이부분이 CryptCreateHash 함수를 호출하면 새로운 Hash Object를 생성하는데 이 마지막 파라미터주소의 번지값에 해쉬객체를 저장합니다.

ㅊ함수는 함수호출 성공시 True, 실패시 False로 나타내며 해쉬객체의 핸들을 PUSH EBP(HCRYPTHASH *phHash) 마지막 파라미터에 입력합니다..

정리하면 암호화 알고리즘을 세팅하고 해쉬객체를 생성하기 위한 단계로 볼수 있습니다.

그리고 CrypyHashData 함수로 생성된 해쉬객제 내에 데이터를 추가합니다.

CrypyHashData의 첫번째 인자로 CrypyHashData함수로 생성된 해쉬객체의 핸들을 파라미터로 넘기고 두번째 인자로 해시 개체에 추가되는 데이터가 포함 된 버퍼의 포인터 주소를 입력합니다. 그리고 두번째 인자값으로 추가되는 데이터의 길이를 세번째 파라미터 값으로 입력합니다.

그림.50에서 위 과정을 총 3번 반복하게 되는데 각각 해쉬객체에 추가하는 데이터는 Service pack3,4D187CDC,DigitalProductId레지스트리 값을 해쉬객체에 추가합니다.

 

[그림.51]

 

마지막으로 CryptGetHashParam 함수를 호출합니다.

해당함수를 호출하면 최종적으로 MD5 HASH DATA가 버퍼에 저장이됩니다.

CryptGetHashParam함수의 첫번째 인자로 해쉬객체의핸들이 입력되고, 두번째로 PUSH 2가 들어갑니다. MSDN을 참조하면 HP_ALGID/HP_HASHSIZE/HP_HASHVAL를 두번째 파라미터의 값으로 사용한다고 되어있습니다.

각각의 값들의 사용하는 정수값을 확인하기 위해서는 WinCrypt.h 헤더파일에서 확인을 해야합니다. HP_HASHVAL(0x0002 //Hash value)값으로 사용되는것을 확인 할 수 있습니다.

그리고 3번째 인자값으로 MD5로 암호화된 데이터를 저장할 버퍼를 넘겨주고 마지막 4번째로는 3번째 데이터를 저장할 버퍼의 길이를 저장할 주소를 넘겨줍니다.

그림.51의 HEX DATA창을 보면 이러한 과정을 거처 MD5값을 완성합니다. 

[그림.52]

 

그림.51에 HEX DATA창에 총 16바이트(32비트)로 MD5값이 생성이 되었는데요 그림.52의 로직을 통해 1바이트 Hex 값(6C)의 값을 각각 6,C로 나누어 총 32바이트 공간에 해쉬값으로 데이터를 만듭니다.

 

 

[그림.53]

 

완성된 MD5값을 0x0012E7D4 버퍼에 _vsnwprintf 함수로 복사합니다.

 

[그림.54]

 

그리고 해쉬객체를 초기화 시킵니다.

 

 

[그림.55]

 

다음으로 GetVolumeNameForVolumeMountPoint 함수로 C:\WINDOWS 함수에 대한 GUID를 얻으려고 시도를하는데 리턴값이 False로 실패하고 다시 C:\문자열로 변경후 시도하여 리턴값을 True로 얻고 C:\에대한 GUID를 얻는데 성공합니다.

 

[그림.56]

 

그리고 생성된 C:\의 GUID값을 CLSIDFromString함수로 SID값으로 만듭니다.

 

[그림.57]

 

"C:\Documents and Settings\dklee\Local Settings\Application Data" 문자열을 파라미터로 입력하여 GetFilleAttributesW함수를 호출하여 Application Data 디렉토리의 속성값을 얻습니다.

리턴된 값인 EAX레지스터를 보면 00000012값을 확인하려면 WinNT.h 헤더파일을 참조하면 되며 리턴값  00000012은 OR로 결합된 값입니다.

헤더파일 에서 확인하면 각각 FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_DIRECTORY 임을 확인할 수 있습니다.

 

[그림.58]

그리고 현재 실행중인 코드의 일부분인 0x4046C0(.rdata section)값을 0x0012E228(Stack Buffer) 52Ch(1324)만큼 복사합니다.

 

[그림.59]

 

스택영역에 복사된 .rdata section의 값을 현재 실행중인 코드의 IMAGE_BASE값인 0x00400000(현재 메모리에 매핑된 ransomware의 시작주소)이 가르키는 주소값의 데이터들 즉 PE의 "MZ" 문자열로 시작하여 스택에 복사된 .rdata section값들을 XOR 연산하여 디코딩을 진행합니다.

 

[그림.60]

 

그리고 읽기 전용으로 CreateFile 함수로 코드가 변경되지 않은 "ransomeware.exe" 파일을 오픈합니다.

현재 분석중인 코드는 ransomeware.exe 파일을 가상메모리에 전부 복사하고 복사된 바이너리를 디코딩을 통해 코드변형이 일어난 코드입니다.

 

[그림.61]

 

여기서부터 분석하기 전 MMF(Memory Mapped File)에 대해 간략히 알아보겠습니다.

MMF를 간단히 설명하면 CreateFile,ReadFile,WriteFile등의 함수로 특정파일을 읽고 쓰게되면 디스크에 있던 데이터를 특정버퍼에 저장하게 되고 이 버퍼는 PageFile.sys라는 페이징 파일과 매핑이 됩니다.

이때 메모리는 이 페이징파일과 스와핑을 통해서 데이터를 메모리에 올려 읽고 쓰게 됩니다.

즉 파일을 PageFile.sys 페이징 파일(가상메모리)과 같이 만들어 사용할 수 있게끔 해주는 방법입니다.

간략히 예를들면 ransomeware.exe파일의 내용을 변형하고 싶다고할때 ReadFile함수로 파일을 읽고 그 데이터를 변형하여 다시 WriteFile함수로 저장할 수 있습니다.

반면 MMF를 사용하여 바로 메모리와 파일을 매핑시켜버릴경우 파일은 메모리에 올려집니다.

이때 메모리자체의 데이터값을 수정하게 되면 바로 원본데이터 파일의 값도 동시에 수정이 가능 하게 됩니다.

그림.61에서는 MMF(Memory Mapped File)기법을 사용하기 위해 ransomeware.exe 파일을 오픈시켰던 핸들을 인자로 CreateFileMapping 함수로 MMF객체를 생성합니다.

 

 

[그림.62]

 

MapViewOfFile함수로 ransomeware.exe파일을 메모리에 매핑시킵니다.

이 함수는 파일을 메모리에 매핑 시켜주는 역할을 합니다.

그림.62를 보면 0x01670000영역에 ransomeware.exe파일이 올라온것을 확인 할 수 있습니다.

 

[그림.63]

 

그리고 매핑된 파일의 0x016C50E2부터 스택에 저장된 데이터들로 메모리 카피를 하여 데이터 변형이 일어나고 0x016C50E2영역의 PE상 구조를 확인한 결과 .data section의 값임을 확인 할 수 있었습니다.

여기서 0x016C50E2 의 3바이트"BE 94 60"를 시작으로 변형이 일어났고 이부분을 확인하기위해 원본데이터 파일을 확인했으나 데이터 변경은 일어나지 않았습니다.

해당 원인을 알아본결과 답은 그림.61과 그림.62에 있었습니다.

각각 CreateFileMapping, MapViewOfFile을 보면 파라미터로 각각 PAGE_WRITE_COPY, FILE_MAP_COPY를 넘겨줍니다.

이파라미터는 COW기법(Copy-On-Write)를 사용하기 위해 쓰여집니다.

COW기법은 메모리를 절약하기 위해 사용되어 집니다.

예로 원본데이터가 있고 여러 스레드가 있을경우 이 스레드들이 원본데이터를 독립적으로 복사하여 사용한다고 가정합니다.

이와같이 데이터가 변경되지 않고 원본데이터를 복사하여 사용한다는 것은 원본데이터를 참조한다는 뜻입니다.

간단히 참조만 이뤄지는 경우인데 각각 스레드들이 복사를해 사용한다면 메모리의 낭비일수 밖에 없습니다.

이것을 해결하기 위해 COW기법을 사용합니다.

스레드를 생성할 때마다 원본데이터를 복사해서 할당하지 않고 모든 쓰레드들이 하나의 원본데이터를 공유하도록 프로그램이 디자인 되어있습니다.

다만 테이블의 데이터를 변경하고자 하는 쓰레드가 등장하면 원본데이터를 복사해서 해당 쓰레드에게 할당한 다음 복사본을 변경하게 합니다.

이 이후부터는 복사본을 이용하여 데이터를 참조합니다.

이것이 원본데이터가 변경되지 않은 이유입니다.

 

참조:뇌를 자극하는 윈도우즈 시스템 프로그래밍 P711

 

 

[그림.64]

 

그림.65에서 처럼 데이터 변형이 진행되고 매핑된 영역을 WriteFile함수로 c:\Documents and Settings/dklee\Application Data\Uqmu\feut.exe 에 쓰게됩니다.

 

 

[그림.65]

c:\Documents and Settings/dklee\Application Data\Uqmu\feut.exe 경로로 이동해보면 feut.exe파일이 생성되었습니다.

여기까지 분석 상황을 볼때 드롭퍼 역할을 하는것으로 확인 할 수있습니다.

 

 

[그림.66]


PathisDirectory 함수로 feaut.exe 실파일이 디렉토리인지 체크하고 디렉토리가 아닌경우 아래의 CreateFile함수를 호출합니다.

 

[그림.67]


그리고 크리티컬섹션 영역으로 이동하하여 0x0042A72E,0x0042A739 주소의 코드를 보면 0x00411A5C와 AL레지스터에 있는값을 각각
440BBC와 440BB8이 가리키는 주소에 저장을 합니다.
이주소는 .text section으로 코드영역의 특정부분에 0x00411A5C를 저장하는것 입니다.
코드영역에서 해당부분을 확인하면 RETN값임을 확인 할 수 있습니다.

 

[그림.68]


그리고 feut.exe파일의 시간정보를 2010년 12월 29일로 변경합니다.

 

[그림.69]


feut.exe의 상위 디렉토리인 Uqmu 디렉토리도 시간정보를 2010년 12월 29일로 변경합니다.

[그림.70]


그리고 스택영역의 주소 0x0012E7D0의 값을 0x0043FAEC에 memcpy로 복사를 진행하게 되는데 대상이되는주소 0x0043FAEC 역시 .text section영역
입니다.
0x0043FAEC 주소로 이동해보면 디버거에서 코드가 분석이되지 않은상태인데 이상태에서 Analaysis code를 하면 해당 코드가 분석되어 보여집니다.
그림.70이 분석된 결과입니다.
이런식으로 현재 실행중인 자기자신의 코드를 수시로 변경합니다.

 

[그림.71]


그리고 힙을생성하는데 생성된 힙의 주소는 0x015F2010입니다.
이영역에 wvnsprintf함수로 feut.exe의 절대경로를 복사합니다.

 

[그림.72]


그림.72에서는 이전에 생성한 실행파일인 feut.exe를 CreateProcess 함수로 실행시킵니다.
feut.exe를 별도로 분석해봐야 알겠지만 지금상태로는 feut.exe는 실행되자마자 바로죽습니다.
그렇기 때문에 CreateProcess의 6번째 파라미터의값 creationFlags를 0x00000004(CREATE_SUSPENDED)값으로 변경해줘야 합니다.
해당값으로 변경하고 함수를 호출하게 되면 feut.exe는 실행과 동시에 정지상태로 프로세스가 생성이됩니다.

 

[그림.73]


Process Explorer를 확인하면 ransomeware.exe의 자식프로세스로 feut.exe가 생성되었고 CPU의 상태를보면 Suspended된것을 확인 할 수 있습니다.
그리고 다시 디버거의 HANDLE을 보면 feut.exe의 핸들 0x0000011C가 생성된 것을 확인 할 수있습니다.

 

[그림.74]


그림.74에는 나와있지 않지만, 이후 분석을 진행하면 .text section의 0x004046C0 주소가 가리키는 값의 데이터들을 스택영역인 0x0012E890에 Memcpy합니다.
그리고 이 주소값이 그림.74에 나와있는 ESI의 값으로 세팅 되어있습니다.
그림.74의 코드를 정리하여 분석하면 0x00445000 .rdata section의 값과 .text section의 값을 서로 XOR로 연산하여 스택영역인 0x0012E890에 저장을 하게 됩니다.

즉 디코딩을 하게됩니다.

 

[그림.75]


다음으로 0x0012E778의 주소값에 .text seciotn의 데이터 0x0043FADC와 스택영역의 데이터 0x0012EC63이 가리키는 데이터 값을 저장하게 됩니다.

 

[그림.76]


그리고 0x0012E778의 주소가 가리키는 데이터들을 다시한번 디코딩 하여 데이터를 변형시킵니다.

 

[그림.77]


그리고 디코딩된 데이터의 시작주소 0x0012E778의 값중 2Ch(44)의 길이만큼의 데이터를 StringFromGuid2함수 호출로 사용자가 식별가능한 문자열로 변형
합니다.

 

[그림.78]

 

그림.77의 StringFromGuid2의 결과입니다.

 

[그림.79]


변형된 GUID문자열을 이름으로 CreateEvent함수를 호출하고 각각 [ESP+14],EAX(CreateEvent HANDLE) / [ESP+18],ECX(feut.exe CreateProcess HANDLE)값

을 저장합니다.
그리고 WaitForMultiplecObjects 함수를 호출하며, 각각 nObjects = 2, pObjects = 0x0012EDEC, WaitForAll = FALSE 파라미터를 전달합니다.
WaitForMultiplecObjects 함수는 스레드 및 프로세스등의 흐름제어를 할 수 있는 동기화 함수입니다.
각각 함수의 이름을 통해 알 수 있듯이 WaitForSingleObject가 단일대상의 흐름을 제어하기 위해 사용된다면 WaitForMultiplecObjects는 다수의 대상의

흐름제어를 위해 사용됩니다.
WaitForMultiplecObjects 첫번째 파라미터는 다수의 대상 프로세스 및 스레드 등의 핸들의 갯수를 지정합니다. 그리고 두번째로 그 핸들이 있는 배열의

포인터를 저장합니다.
마지막 파라미터는 True/False로 지정됩니다.
이것의 부연설명을 하면, 프로세스가 생성이 되면 커널오브젝트도 생성이 되는데 이때 커널오브젝트의 멤버변수중에 상태정보를 저장하는 변수가 있습니

다.
프로세스가 처음 생성이 되면 커널오브젝트의 상태가 non-signal(사용시작/사용중/사용불가 의 의미)이 되고 프로세스가 종료되면 커널오브젝트는

signal(사용종료/미사용중/사용가능 의 의미)상태가 됩니다.
그림.79를 보면 이벤트핸들과 프로세스핸들 2개에 마지막 파라미터에 False가 할당이 되었습니다.
여기서 마지막 파라미터가 True 일경우 이것은 WaitForMultiplecObjects 의 함수는 지정된 핸들의 소유주 모두 signal상태일때 까지 기다리라는 의미이

고, False일경우 지정된 핸들의 소유주중 하나만 signal이 될때까지 기다리라는 뜻입니다.
하지만 제가 그림.73에서 드롭한 feut.exe 자식프로세스를 어셈블리 코드를 조작하여 생성과동시에 정지상태(CREATE_SUSPENDED)로 호출했기 때문에 자식

프로세스는 종료가 되지 않은상태입니다. 즉 feut.exe는 아직 non-signal임으로 WaitForMultiplecObjects함수에 의해 블로킹 상태가 되는것입니다.

 

[그림.80]


이 상태에서 자식프로세스를 ATTACH하여 분석을 진행해도되고, Suspended상태를 resume상태로 돌려 이어서 분석을 해도됩니다.
하지만 저는 feut.exe를 후에 분석하기로 하고 우선 프로세스 생성 후 다음 행위를 파악하기위해 이어서 분석을 진행합니다.

 

[그림.81]


여기서부터 자가삭제를 하기위해 배치파일을 작성하는 작업을 시작하는 부분입니다.
우선 vsprinf함수로 ransomeware.exe를 삭제하는 문자열을 입력합니다.

 

[그림.82]


그리고 Temp 디렉토의 경로를 얻어옵니다.

 

[그림.83]


NTMA5BF.bat 배치파일을 읽기/쓰기 전용으로 생성합니다.

 

[그림.84]


다음으로 "@echo off%sdel /F "%S"" 문자열의 길이를 구하고, 명령라인 0x0043DE76에서 Heap을생성하여 0x015F2010의 주소를 할당받습니다.
그리고 wvnsprintf 의 4번째 파라미터 주소값 0x0012E4C8에 그전에 만들어놨던 ransomeware.exe를 삭제하는 문자열과 temp디렉토리에 생성된 배치파일경

로를 저장해 놓고 값을 넘겨 wvnsprintf함수를 호출하여 0x015F2010에 저장합니다.

 

[그림.85]


그리고 writefile로 0x015F2010에 저장 되어있는 문자열을 NTMA5BF.bat파일에 쓰게됩니다.

쓰여진 배치파일 내용을 확인해보면 ransomeware.exe 와 NTMA5BF.bat 파일을 삭제하는 명령어를 확인 할 수 있습니다.

 

 

 [그림.86]

 

GetEnvironmentVariable 함수로 환경변수의 ComSpec값 c:\WINDOWS\system32\cmd.exe 값을 0x0012E7FC 버퍼에 저장합니다.

 

 [그림.87]

 

그리고 그림.87과같이 C:\WINDOWS\system32\cmd.exe /c c:\DOCUME~1\dklee\LOCALS~1\Temp\NTMA5BF.bat 문자열로 만듭니다.

 [그림.88]

 

그리고 CreateProcess로 C:\WINDOWS\system32\cmd.exe /c c:\DOCUME~1\dklee\LOCALS~1\Temp\NTMA5BF.bat 배치파일을 서브프로세스로 실행시킵니다.

 [그림.89]

 

ProcessExplorer 프로그램을 통해서 cmd.exe를 이용해 배치 파일을 실행시키는 것을 확인 할 수 있습니다.

 

[그림.90]

 

그리고 ExitProcess함수로 종료합니다.

결국 ransomeware.exe 악성코드는 feut.exe를 드롭하는 드롭퍼 인것을 알 수 있습니다.

 

 

posted by 미스터리 DKL