악성코드는 AV 엔진으로부터의 탐지를 회피하기 위해 암호화(encryption) 또는 다형성(polymorphism) 기법을 자주 사용한다. 그 중 다형성 코드(polymorphic code)의 의미를 Wikipedia에서는 아래와 같이 설명하고 있다.

In computer terminology, polymorphic code is code that uses a polymorphic engine to mutate while keeping the original algorithm intact. That is, the code changes itself each time it runs, but the function of the code (its semantics) will not change at all. This technique is sometimes used by computer viruses, shellcodes and computer worms to hide their presence.

Wikipedia의 설명처럼 다형성 코드란 코드 자체의 기능은 변하지 않지만 실행 시마다 코드의 내용이 변화되는 것을 의미한다. 그림으로 표현하면 아래와 같지 않을까?

[그림 1]

위와 같이 다형성 코드를 사용하면 런타임 이 후 각각 다른 output code(파일) 생성이 가능하며, 이는 signature-based detection에서는 탐지가 쉽지 않을 것이다.

   

최근 주말마다 아주 활발하게(?) 유포되고 있는 온라인게임 계정 탈취를 목표로 하고 있는 악성코드의 경우 [그림 2]와 같이 main dropper인 x.exe의 hash 값은 동일한데 반해 main dropper에 의해 생성된 .sys 파일과 .dll 파일의 hash 값은 다른 것을 확인할 수 있다.

[그림 2]

어떤 방법을 이용하여 위와 같이 같은 dropper에서 서로 다른 파일을 생성할 수 있었을까?

  

먼저 hash 값이 다른 두 개의 파일을 비교해 보면 [그림 3]과 같이 0x40 offset부터 0x80 bytes, 0x118 offset부터 0x2 bytes만 상이한 것을 확인할 수 있다.

[그림 3]

해당 offset은 PE 파일의 구조로 상 "MS-DOS Stub program" 부분(0x40 ~ 0xC0)과 IMAGE_OPTIONAL_HEADER의 Checksum(0x118) 부분에 해당한다.

[그림 4] MS-DOS Stub program

[그림 5] Checksum

   

main drooper(x.exe)는 일반적인 dropper의 파일 생성 방법처럼 아래 그림과 같이 리소스에 drop 대상 파일을 로드한 뒤 파일로 생성한다.

[그림 6]

 

drop 대상을 리소스로부터 읽어 메모리에 로드한 뒤 아래 루틴에서 "MS-DOS stub program"과 "checksum" 부분 변조를 수행한다.

[그림 7]

 

0040F797 /$ 55 PUSH EBP

0040F798 |. 8BEC MOV EBP,ESP

0040F79A |. 51 PUSH ECX

0040F79B |. 51 PUSH ECX

0040F79C |. 8365 FC 00 AND [LOCAL.1],0

0040F7A0 |. 56 PUSH ESI

0040F7A1 |. 8BF1 MOV ESI,ECX

0040F7A3 |. 57 PUSH EDI

0040F7A4 |. 8955 F8 MOV [LOCAL.2],EDX

0040F7A7 |. 66:813E 4D5A CMP WORD PTR DS:[ESI],5A4D ; Compare 'MZ' Header

0040F7AC |. 75 53 JNZ SHORT x.0040F801

0040F7AE |. 8B46 3C MOV EAX,DWORD PTR DS:[ESI+3C] ; Go to 'DOS Stub Program'

0040F7B1 |. 813C30 504500>CMP DWORD PTR DS:[EAX+ESI],4550 ; Compare 'PE' Header

0040F7B8 |. 8D3C30 LEA EDI,DWORD PTR DS:[EAX+ESI] ; EDI = DOS Stub program address

0040F7BB |. 75 44 JNZ SHORT x.0040F801

0040F7BD |. 53 PUSH EBX

0040F7BE |. 6A 40 PUSH 40 ; 'DOS Stub Program' offset

0040F7C0 |. 5B POP EBX

0040F7C1 |. 3BC3 CMP EAX,EBX

0040F7C3 |. 76 0E JBE SHORT x.0040F7D3

0040F7C5 |> E8 6DF8FFFF /CALL x.0040F037 ; random byte generation

0040F7CA |. 880433 |MOV BYTE PTR DS:[EBX+ESI],AL ; write random byte

0040F7CD |. 43 |INC EBX ; next pointer

0040F7CE |. 3B5E 3C |CMP EBX,DWORD PTR DS:[ESI+3C]

0040F7D1 |.^ 72 F2 \JB SHORT x.0040F7C5

0040F7D3 |> 8B45 F8 MOV EAX,[LOCAL.2]

0040F7D6 |. 8367 58 00 AND DWORD PTR DS:[EDI+58],0 ; EDI + 58 = checksum

0040F7DA |. 40 INC EAX

0040F7DB |. 5B POP EBX

0040F7DC |. D1E8 SHR EAX,1

0040F7DE |. 74 17 JE SHORT x.0040F7F7

0040F7E0 |> 66:8B16 /MOV DX,WORD PTR DS:[ESI]

0040F7E3 |. 8B4D FC |MOV ECX,[LOCAL.1]

0040F7E6 |. 0155 FC |ADD [LOCAL.1],EDX

0040F7E9 |. 66:394D FC |CMP WORD PTR SS:[EBP-4],CX

0040F7ED |. 73 03 |JNB SHORT x.0040F7F2

0040F7EF |. FF45 FC |INC [LOCAL.1]

0040F7F2 |> 46 |INC ESI

0040F7F3 |. 46 |INC ESI

0040F7F4 |. 48 |DEC EAX

0040F7F5 |.^ 75 E9 \JNZ SHORT x.0040F7E0

0040F7F7 |> 0FB745 FC MOVZX EAX,WORD PTR SS:[EBP-4]

0040F7FB |. 0345 F8 ADD EAX,[LOCAL.2]

0040F7FE |. 8947 58 MOV DWORD PTR DS:[EDI+58],EAX ; modify checksum value

0040F801 |> 5F POP EDI

0040F802 |. 5E POP ESI

0040F803 |. C9 LEAVE

0040F804 \. C3 RETN

 

[그림 8]은 실제 메모리 상에서 변조된 값을 보여준다.

[그림 8]

 

랜덤 값을 생성할 때에는 [그림 9]과 같이 GetTickCount 함수와 GetLocalTime 함수를 이용하며, 따라서 실행시마다 랜덤한 값을 생성할 수 있다.

GetTickCount

Retrieves the number of milliseconds that have elapsed since the system was started, up to 49.7 days.

GetLocalTime

Retrieves the current local date and time.

To retrieve the current date and time in Coordinated Universal Time (UTC) format, use the GetSystemTime function.

 

[그림 9]

 

위와 같은 방법으로 메모리 상에서 "DOS stub program"과 checksum (DLL 파일 생성시에는 checksum은 변조하지 않는다.) 부분에 대한 변조를 한 뒤 특정 파일로 write한다.

[그림 10]

 

또 하나 해당 악성코드의 재미있는 사항으로 루트킷 설치를 위해 드라이버 파일을 생성할 때 드라이버 파일의 이름이 랜덤한 값으로 생성되는 것을 확인할 수 있었다.

[그림 11] 드라이버 파일명 1b5db27f.sys

해당 드라이브 파일명은 어떤식으로 생성된 것일까? 먼저 GetVolumeInformation 함수를 이용하여 C 드라이브의 볼륨 정보를 얻어온다.

[그림 12]

   

이어서 GetComputerName 함수를 이용하여 컴퓨터의 NetBIOS 이름을 얻어온다.

[그림 13]

 다음 LAN card의 MAC address를 얻어온다.

[그림 14]

   

위에서 얻어온 세 가지 값을 합쳐 하나의 문자열을 생성한 뒤 해당 문자열의 MD5 값을 얻어온다.

[그림 15] 얻어온 값들 하나의 문자열로 합침

   

[그림 16] 문자열의 MD5 값을 얻어옴

이후 MD5을 2차로 가공(이 부분은 귀찮음 50% + 실력 부족 50% 으로 패스)하여 파일명을 결정한 뒤 생성한다.

[그림 17]

  

+---------------------------------+
| Infinite Flow..                              |
| mail : reverseinsight@gmail.com    |
| Blog : http://sinun.tistory.com       |
| twitter : @unpacker                      |
| CISSP, SIS1급                             |
+----------------------------------+

신고
Posted by By. PHR34K

티스토리 툴바