#1. 개요


참조사이트 : www.virustotal.com


(1) 바이러스 개요


[그림.1]


검색엔진에서 구한 파일명 virus.exe란 파일을 받아서 혹시나 하는 마음에 virustotal을 돌려 보았는데 40개의 제품에서 탐지가 됬고 명칭은 Win32/Resurrect 입니다.

웹에서 바이러스 관련해 분석 된 자료가 있나 찾아보니 관련 자료는 없고 간략한 바이러스에 대한 설명만 있습니다.

해당 파일을 구했을때 C로된 풀소스와 EXE바이너리 파일이 같이 압축되어 있던걸로 보아 학습 또는 테스트 용도로 만들어 놓은것 같습니다.


(2) 바이러스 최초 발견과 마지막 발견 및 전파 파일명


[그림.2]


(3) 악성코드의 기본 정보


[그림.3]


(4) 악성코드의 상세정보


[그림.4]

 

Win32/Resurrect.29696 악성코드 분석


#2. 동적분석


(1) 테스트 환경 및 도구

Windows XP Service Pack3, Ollydbg, Stud_PE

바이러스가 패킹 되었는지 확인하기 위해 Stud_Pe프로그램을 사용하여 패킹여부를 확인하였습니다.


[그림.5]]


컴파일러는 Visual c++ 6.0을 사용하고 패킹되지 않았습니다.


(2) ProecessExplorer 프로세스 생성확인.


[그림.6]


VIRUS.EXE 파일을 실행하면 conime.exe프로세스를 생성 후 VIRUS.EXE 파일은 종료합니다.


(3) RegShot으로 실행 전/후 레지스트리 변화 확인

[그림.7]


Regshot 1.8.1 결과

설명:
날짜:2012/10/24 13:12:11  ,  2012/10/24 13:12:31
컴퓨터:NTS , NTS
사용자 이름: ,

----------------------------------
생성된 값:2
----------------------------------
HKU\S-1-5-21-515967899-1214440339-842925246-1003\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{75048700-EF1F-11D0-9888-006097DEACF9}\Count\HRZR_EHACNGU:P:\Qbphzragf naq Frggvatf\qxyrr\바탕 화면\if_fnzcyr\jva32\IVEHF.RKR: 07 00 00 00 06 00 00 00 E0 73 BB 30 E9 B1 CD 01


HKU\S-1-5-21-515967899-1214440339-842925246-1003\Software\Microsoft\Windows\ShellNoRoam\MUICache\C:\Documents and Settings\dklee\바탕 화면\vs_sample\win32\VIRUS.EXE: "VIRUS"

----------------------------------
변경된 값:2
----------------------------------
HKLM\Software\Microsoft\Cryptography\RNG\Seed: 45 78 D0 DE CC 18 91 27 2F B5 DC 77 BA B2 29 EA C6 F0 79 BB 04 05 25 FE 53 A5 D6 61 76 78 D5 E6 AF 4B D3 35 12 EB 4C 1C 67 1F 56 26 FB A7 33 60 03 7C 5B 99 E2 DF 12 E4 D2 7E 4E 50 04 F7 24 22 19 16 CA 4A AC 74 95 69 E8 E0 3A 7C B4 20 8A F9


HKLM\Software\Microsoft\Cryptography\RNG\Seed: 8E 39 F0 81 81 0C 16 0D 84 F3 E5 33 08 4A 1F 1F EC D0 13 53 44 17 19 EA 70 1D D9 7A 74 81 1A C5 43 85 23 2F 76 B6 EF D4 A6 8B E6 64 D6 61 9F 53 4C 4C 45 4D C5 99 68 1A 2B 8A 4E CF 8F BC FD AF 6E 6B 99 69 FF 3B 7A 31 CF B6 11 C7 51 9E 69 15


HKU\S-1-5-21-515967899-1214440339-842925246-1003\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{75048700-EF1F-11D0-9888-006097DEACF9}\Count\HRZR_EHACNGU: 07 00 00 00 28 01 00 00 C0 28 AA D5 E8 B1 CD 01


HKU\S-1-5-21-515967899-1214440339-842925246-1003\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{75048700-EF1F-11D0-9888-006097DEACF9}\Count\HRZR_EHACNGU: 07 00 00 00 29 01 00 00 E0 73 BB 30 E9 B1 CD 01

----------------------------------
생성된 파일:1
----------------------------------
C:\WINDOWS\Prefetch\VIRUS.EXE-2DA0382F.pf

----------------------------------
변경된 파일:3
----------------------------------
C:\Documents and Settings\dklee\NTUSER.DAT.LOG
C:\WINDOWS\Prefetch\CONIME.EXE-13EEEA1A.pf
C:\WINDOWS\system32\config\software.LOG

----------------------------------
종합:8
----------------------------------

[생성된 값:2, 변경된 값:2, 생성된 파일:1, 변경된 파일:3]


(4) prefetch파일 생성

 

윈도우의 부팅커널까지 포함하여 모든 프로그램 실행시 실행되었던 기계어와 실행하는 데 필요한 DLL파일들의 주소를 파일에 저장해 놓고 다음 번에 실행시 정보를 저장한 파일을 읽어 미리미리 하드디스크에서 RAM으로 로딩하게끔 하는 역할이라고 합니다.


[그림.8]


[그림.9]


파일을 실행하면 처음에 pf파일을 생성합니다.


[그림.11]


pf파일을 열면 그림6처럼 virus.exe 파일이 access한 목록을 string으로 볼 수 있습니다.

물론 string을 보는 툴을 사용해서 확인해도 되지만, 수작업으로 정리했습니다.

 
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\NTDLL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\KERNEL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UNICODE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LOCALE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SORTTBLS.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\CONIME.EXE
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\USER32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\GDI32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\IMM32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\ADVAPI32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\RPCRT4.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SECUR32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\MSVCRT.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SHIMENG.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\APPPATCH\SYSMAIN.SDB
\DEVICE\HARDDISKVOLUME1\WINDOWS\APPPATCH\ACGENRAL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\WINMM.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\OLE32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\OLEAUT32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\MSACM32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\VERSION.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SHELL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SHLWAPI.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\USERENV.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UXTHEME.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\CTYPE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LPK.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\USP10.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\WINSXS\X86_MICROSOFT.WINDOWS.COMMON-CONTROLS_6595B64144CCF1DF_6.0.2600.6028_X-WW_61E65202\COMCTL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\WINDOWSSHELL.MANIFEST
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\COMCTL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\MSCTF.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\MSCTFIME.IME
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\IMEKR61.IME
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\WIN32K.SYS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\CONFIG\SOFTWARE\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\CONFIG\SYSTEM
\DEVICE\HARDDISKVOLUME1\
\DEVICE\HARDDISKVOLUME1\WINDOWS\)
\DEVICE\HARDDISKVOLUME1\WINDOWS\APPPATCH\)
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\'
\DEVICE\HARDDISKVOLUME1\WINDOWS\WINSXS\z
\DEVICE\HARDDISKVOLUME1\WINDOWS\WINSXS\X86_MICROSOFT.WINDOWS.COMMON-CONTROLS_6595B64144CCF1DF_6.0.2600.6028_X-WW_61E65202\OFTWARE


#3. 정적분석


(1) 프로그램을 실행하기 위한 사전 준비 작업


<401E92 CALL VIRUS.00401F61 함수분석>


[그림.14]


메인함수를 실행하기 전까지 운영체제에서 프로그램을 실행하기 위한 코드입니다.

401E5F에서는 Kernel32.Getversion 함수를 호출하여 프로그램이 실행 된 운영체제의 시스템 버전정보를 가져옵니다.

그 후 두번째 함수 401E92번지에 CALL VIRUS.00401F61 명령을 실행하여 함수를 호출하는데 이 함수의 역할은 Heap을 할당하는 함수입니다.


[그림.15]


함수 내부로 들어와서 401F61 ~ 401F72 까지는 HeapCreate함수를 실행하여 4096byte의 힙을 생성합니다.


[그림.16]

HeapCreate 함수를 호출 후 정상적으로 호출이 이뤄지면 핸들을 리턴합니다.

이때 401F78 TEST EAX,EAX 명령어가 실행이 됩니다.

TEST 명령어는  AND연산을하고 결과값을 저장하지않고 플래그에만 반영합니다.

즉 TEST EAX,EAX = 자기 자신의 값을 AND연산합니다. 연산 후 값이 0일 경우 ZF 플래그를 1로 세팅하며, 0이아닐경우 ZF 플래그를 0으로 세팅하게 되어 조건문을 실행하게 됩니다.

그림3의 경우 TEST연산 후 401F7F의 주소에서 JE 명령어를 만나 분기하게 되는데 여기서는 HeapCreate 함수를 정상으로 호출하여 핸들값이 0이 아니기 때문에 ZF 플래그는 0으로 세팅되어 JUMP하지 않습니다.

(JE Jump if equal = 결과값이 0이면 ZF=1, 결과값이 0이 아니면 ZF=0으로 세팅 JE는 ZF플래그가 1이되어야 분기함.)

※정리 call 후 값이 존재하면 ZF=0으로 세팅, call 후 값이 null 이면  ZF=1



[그림.17]


그림1을 보면 분기하지 않으므로 401F81번지의 VIRUS.00401F9D 함수를 호출하고 그림4와 같이 함수 내부로 들어가게 됩니다.

여기서 HeapAlloc 함수호출로 힙에 메모리를 할당합니다.

401FB0에서 리턴하는 값이 없는데 특정값을 EAX 레지스터에 가저와 TEST연산을 다시 수행합니다.

그 후 401FB0 ~ 401FDA까지 명령어를 실행하는데 401FB7 JNZ SHORT VIRUS.00401FBA 명령어에서 JUMP하여 401FBA AND DWORD PTR DS:P409024,0 명령을 실행합니다.

즉 401FB0부분은 함수호출을 정상적으로 수행했는지 확인하는 코드임을 알수 있습니다.


[그림.18]


HeapAlloc함수를 빠저나와 401F86 에서 다시한번 검증을합니다.

HeapAlloc함수가 정상적으로 수행했는지 테스트하고 테스트 후 함수호출 실패시 HeapDestroy 함수를 호출하고 리턴합니다.


[그림.19]

▶정리

4096byte의 힙을 생성하고 힙에 메모리를 할당.

힙생성이 정상적인지 검증 후 힙생성 실패 시 다시 힙을 생성합니다.


<00401EA8 VIRUS.00403C08 함수 분석>


[그림.20]

00403C46번지를 보면 AB0EF0의 주소값을 OR연산하여 FFFFFFFF로 세팅합니다.

바로아래는 Ah(10)으로 세팅하는데 이과정을 ESI > EAX조건을 만족할때까지 진행합니다.

그리고나서 00403C60 번지의 GetStartupInfo 함수를 호출하는데 그 인자값에 Startupinfo 구조체가 입력이되어 실행된 프로세스의 정보를 가저오고 00403D38 번지로 점프합니다.


[그림.21]


처음 00403D60번지에서 GetStartHandle 에 STD_INPUT_HANDLE(FFFFFFF6) 의 인자값으로 함수를 호출 합니다. 

이 함수의 호출의미는 표준 입력장지 입력버퍼에 관한 핸들을 반환하는 함수라고 합니다.

함수의 핸들리턴값은 3이며 반환값 3을 00403D6E GetFileType 함수의 인자값으로 사용합니다.

GetFileType의 함수는 파일에 대한 형식을 정수값으로 리턴하는데 리턴한 정수값은  00000002입니다.

MSDN참조결과 리턴된 정수값 2는 FILE_TYPE_CHAR로써 지정된 파일은 문자파일입니다.

일반적으로 LPT나 콘솔이라는 설명을하며, 결과는 콘솔파일타입 이라는 결론을 낼 수 있습니다.

[그림.22]


마지막으로 00403DA5 SetHandleCount 함수에 20h(32) 값을 인자값으로 해서 호출을 하며, SetHandleCount 함수는 프로그램에서 사용 할 수 있는 핸들 수 를 지정합니다.


[그림.23]



▶정리

프로세스 정보를 가저온다.

GetFileType호출로 파일에 대한 형식을 정수값으로 리턴하며, 리턴결과 콘솔파일타입 임을 알 수 있다.


<00401EB8  CALL VIRUS.00403AD6 함수 분석> 


[그림.24]


그림18에서 GetEnvironmentStrings 함수를 호출하는데 이 함수의 역할은 현제 실행된 프로세스의 환경변수를 가져오는 역할을 합니다.

이 함수가 호출되면 00010000의 주소값을 리턴하는데 이것은 유니코드형식의 환경변수 문자열이 저장된 주소값을 리턴합니다.

리턴 후 저장된 문자열에 0이 나올때까지 반복하여 환경변수값을 가져옵니다.


[그림.25]


그림 18에서 디버가 하단의 ASCII 코드를 복사한 후 문자열로 만들었습니다.

ProcessExplorer에서 현재 실행 된 virus.exe 프로세스의 환경변수를 비교하였고 그 결과 동일합니다.


[그림.26]


403B69번지의 WideCharToMultiByte 함수 호출로 유니코드로 된 스트링을 멀티바이트로 변경합니다.

그 후 00403B72 CALL VIRUS.004028E0  번지의 함수 호출을 합니다.

함수 호출하면 914988의 값을 리턴하며, 914988숫자의 의미는 멀티바이트로 변경된 환경변수의 값을 저장하기위한 시작주소의 값 입니다.


[그림.27]


다음으로 00403B8B  CALL EDI  함수 호출을하며 여기서 멀티바이트 문자열이 914988번지부터 914DF8번지까지 환경변수의 문자열을 복사합니다.


[그림.28]

 문자열 복사 후 00403BA4 CALL DWORD PTR DS:[<&KERNEL32.FreeEnvironmentStringsW>] 함수를 호출하며 유니코드로 저장된 00010000번지의 메모리를 반환합니다.

 

▶정리

00401EB8  CALL VIRUS.00403AD6 함수를 정리하면 다음과 같습니다.

virus.exe 프로세스가 메모리에 올라가서 실행되며 실행되는 프로세스의 환경변수의 문자열을 유니코드 로 구한 후 914988번지의 빈주소에 멀티바이트로 변환하여 복사합니다.

복사가 완료되면 기존의 환경변수의 문자열(유니코드) 00010000번지의 메모리를 반환합니다.


<00401EC2 CALL VIRUS.00403889 함수 분석>


[그림.29]


00403889 함수가 하는 역할은 간단합니다.

GetMouduleFileName 함수를 사용하여 현재 실행되는 프로세스의 경로를 가져옵니다.


[그림.30]


그림 16과 같이 지금 실행되고 있는 프로세스의 경로를 문자열로 가져옵니다.


<00401EC7 CALL VIRUS.004037D0 함수 분석>


[그림.31]


004037D0 함수의 역할은 환경변수를 줄단위로 실행 후 처음에 생성했던 힙을 초기화 시킵니다.


[그림.32]


004037EA ~ 00403800 까지는 환경변수의 첫라인부터 마지막 라인까지 각 줄에 대한 길이를 EAX레지스터에 리턴합니다.


[그림.33]


0040382F ~ 00403864 에서는 그림18에서 환경변수의 문자열 라인의 길이를 리턴하였는데 그 환경변수 의 길이를 기준으로해서 환경변수의 각 라인을 스택에 저장 합니다.


[그림.34]


그 후 아래 0040386D CALL VIRUS.00401CEF 함수를 호출하는데 함수 내부로 진입하게 되면 HeapFree(914988 환경변수 문자열을 저장했던 메모리)하여 생성했던 메모리를 초기화합니다.

 

처음 그림4에서 힙을 생성하고 생성한 힙을 사용하지않아 "왜 힙을 생성했을까?" 라고 생각했던 의문이 여기서 풀리는데요.

즉 비교적 변수에 저장하기 어려운 대용량 데이터나 가변적인 문자열을 일반 변수에 저장하기 어려운 부분이 있기 때문에 보통 힙(동적메모리)을 사용하여 그 공간에 저장 하게 됩니다. 이 때문에 힙을 생성해서 가변적인 공간을 생성한 것입니다.

데이터가 작거나 고정적인 데이터 또는 고정적인 문자열일 경우에는 힙을 생성할 필요없이 변수를 선언해서 사용하면 됩니다.

 

바이러스 메인 함수에 들어가기전 운영체제가 프로세스를 실행할때의 원리가 궁금하여 Stub code까지 전부 분석해 보았는데요.

궁금증이 어느정도 해결된거 같습니다..

 

<정리>

1. virus.exe 를 실행하면 제일처음 4096byte의 힙을 생성합니다.

2. 그 후 실행하려는 타입을 반환합니다. 분석중인 프로그램은 콘솔이였습니다.

3. 현재 실행 된 프로세스의 환경변수 와 실행 된 프로세스의 경로를 가져옵니다.

4. 환경변수를 파악 후 한줄씩 적용하고 완료시 생성된 힙을 초기화 합니다.


(2) PE 헤더정보 추출


<00401EE8 CALL VIRUS.00401945 메인함수 분석>

[그림.35]


00401EE8  CALL VIRUS.00401945 함수 내부로 Step in[F7]하여 진입합니다.

00401964 70604000 CALL DWORD PTR DS:[<&KERNEL32.GetModuleFileNameA>] 호출하는데 실행 된 VIRUS.EXE파일의 경로를 가저와 그 아래 12FDF0의주소에 해당 경로를 저장합니다.


[그림.36]


연속으로 실행되는 프로세스의 경로를 가저오고 난 후 GetModuleHandle함수를 호출하여 프로세스의 베이스주소 값을 리턴하게 됩니다.

리턴 된 값은 EAX레지스터에 저장되며 그 값은 00400000입니다.


[그림.37]


리턴된 값 00400000베이스주소를 PEview로 확인결과 정확이 일치합니다.


[그림.38]


베이스주소를 구한 후 EAX값 VIRUS.00400000을 407B94주소에 저장합니다.

저장 후에 아래 명령 0040197F ADD ECX,EAX 으로 00400000(베이스주소) + C8 = 004000C8의 연산을 진행합니다.

004000C8의 주소값은 "PE" 문자열의 주소를 가르킵니다.

아래 00401987 CMP DWORD PTR DS:[ECX],4550 명령에는 004000C8(PE)가 실행파일인지 비교를 합니다. 4550은 PEview로 확인하면 PE Hex Code임을 확인 할 수 있습니다.


[그림.39]


이 또한 PEview로 확인한 결과 입니다.


[그림.40]


계속 디버깅 하다보면 004019B4 907B4000 MOV DWORD PTR DS:[407B90],EDX 명령어가 나옵니다.

여기서는 실행파일의 .text section을 가져옵니다.

참고로 .text section은 실제 코드가 들어있는 섹션이며, 지역변수 등을 담고있는 섹션입니다.


[그림.41]


PEview확인결과 일치합니다.


[그림.42]


계속 디버깅 하다보면 004019C8 MOV ESI,VIRUS.00401932(33 C0 50 50) 명령어가 나옵니다.

.text section의 00401932(33 C0 50 50)의 주소를 ESI레지스터에 저장하고 아래 004019CD SUB ESI,EAX

연산으로 베이스주소값 4000000값을 ESI(401932)값과 마이너스 연산하여401932 - 400000  = 1932 값을 ESI레지스터에 저장합니다.

00401932 는 뒤에서 확인 할 수 있겠지만 스레드 함수의 주소입니다.


[그림.43]


.text section의 00401932(스레드 주소) 값을 그림28과 그림29 PEview에서 확인 하였습니다.


[그림.46]

 

그림.46에서 [00400168]=0000A000값이 나오는데 이값은 IMAGE_OPTIONAL_HEADER의 BASE RELOCATION TABLE의 RVA값으로 확인 할 수 있습니다.

(4회전)

[그림.47]

 

반복문 처음으로 돌아와 보면 004019E6 MOV EDX,DWORD PTR SS:[EBP-8] 명령어로 EDX레지스터에 00400244 주소값을 저장하는데 이 값은 IMAGE_SECTION_HEADER .reloc 섹션의 RVA값 0000A000값입니다.

그리고 004019EB 주소에서 비교를 하는데 EDX값과 00001E39값을 비교합니다.

0000A000(EDX) - 00001E39 = 000081C7 결과는 양수가 나왔고 EDX값이 더 큰것으로 나와 아래 조건문을 만족하여 00401A15주소로 점프합니다.


[그림.48]

 

그림.47에서 IMAGE_SECTION_HEADER .reloc 섹션의 RVA값 0000A000를 PEView에서 확인 할 수 있습니다.

 

[그림.49]

00401A15주소까지 점프 하고 디버깅하면 바로 아래 명령어 00401A18주소의 명령어까지 실행합니다.

해당 명령어는 DS:[00400148]=000064D4 값을 EBX레지스터에 저장하는데 000064D4는 IMAGE_OPTIONAL_HEADER의 IMPORT Table의 RVA값입니다.

명령어를 00401A23주소까지 진행 하면 비교문이 나오고 0000A000(RVA) - 000064D4(IMPORT Table) = 00003B2C 결과값이 양수입니다. 그래서 EDX값이 더 큰것으로 나오게 되며, 조건이 성립되어 00401A4C주소로 점프합니다.



[그림.50]

그림.49에서 IMAGE_OPTIONAL_HEADER 섹션의 IMPORT Table RVA값 000064D4를 PEView에서 확인할 수 있습니다.


[그림.51]

 

00401A4C 까지 명령어를 실행하면 00401A51에서 비교문이 나오는데 이 비교문에서 0000A000값과 0000A000값을 비교하고 결과값이 0이나와 아래 00401A57에서 조건이 성립하지 않아 점프하지 않게 되고 00401A59번지 명령어를 실행하게 됩니다.


그림.51의 박스 안의 주소 00401A59 ~ 00401A71까지 명령어 실행 과정입니다.

EDX레지스터에 Stack SS:[0012FF74]=00000003 값을 저장합니다.

EDX값 00000003을 004079C0에 저장합니다.

DS:[00400240]=000006E6 EDX 레지스터에 000006E6 값을 저장합니다.

EDX값 000006E6을 004079C4에 저장합니다.

DS:[0040016C]=000005C0 EDX 레지스터에 000005C0 값을 저장합니다.

EDX값 000005C0 를 004079C8에 저장합니다.

박스의 과정을 정리하면 004079C0 ~ 004079C8의 빈 공간에 각각 4바이트의 크기로 00000003, 000006E6, 000005C0값을 저장합니다.

값의 사용은 그림.91에 나옵니다.


총 4회(해당 프로그램의 섹션의 수) 만큼 반복이 이루어 젔으며 조건이 성립되어 반복문을 빠저나와 00401C39 주소로 점프합니다.


<정리>

1. GetModuleHandle 함수를 호출하여 프로세스의 BaseAdress를 리턴합니다. 리턴값은 00400000이며 이 값은 PE구조의 ImageBase의 해당하는 값입니다.

2. 0040197F ADD ECX,EAX 연산을 하는데 00400000(Base Adress) + 000000C8(e_lfanew) = 004000C8의 결과 값이 나오며, 이 값은 PE구조의 PE 문자열(4550h)의 해당하는 값입니다.(Base Adress 와 e_lfanew 값을 더하여 "PE" 문자열의 주소값을 계산하는 연산입니다.)

3. 004000C8(PE) 실행 된 PE파일의 PE문자열과 4550(PE)가 같은 값인지 비교하는데 이 것은 실행 된 파일이 실행가능한 PE파일인지 확인하는 과정입니다.

4. 004019B4 907B4000 MOV DWORD PTR DS:[407B90],EDX로 004001C0값을 저장하는데 004001C0값은 실행파일의 .text section 주소를 의미합니다.

5. 004019CD SUB ESI,EAX 연산으로 ESI(00401932) - ImageBase(004000000) = 1932의 값을 ESI레지스터에 저장합니다.

6. 004019DD ADD EDX,0C 연산을 하는데 이것은 004001C0(.TEXT) + 0000000C = 004001CC값이 나오며, 이값은.Text section의 RVA값입니다.


(1회전 반복문)

7. 반복문에 진입하고 PE의 섹션의 수(VIRUS.EXE는 4개의 섹션으로 구성) 만큼 반복문을 진행합니다.

8. .text section의 RVA값 00001000을 가져옵니다.

9. IMAGE_FILE_HEADER의 Size Of Raw Data값 00005000을 가져옵니다.

10. 00005000 + 00001000 = 00006000 .text RVA값과 Raw Data값을 더하여 00006000값으로 만듭니다.

11. IMAGE_OPTIONAL_HEADER의 Adress of Entry Point 값 00001E39를 가져옵니다.

12. Stack SS:[0012FF74]=00000000 1증가 후 004001CC .text RVA값에 28h를 더해 004010F4주소값을 만들며 이는 다음섹션 즉 .rdata RVA값의 주소를 의미합니다.0012FF74의값 1세팅 1회전 반복 종료


(2회전 반복문)

13. .rdata section의 RVA값 00006000을 가져옵니다.

14. IMAGE_OPTIONAL_HEADER의 Import Table의 RVA값 000064D4 가져옵니다.

15. 004000000 + 000064D4 = 004064D4 BaseAdress와 Import Table의 RVA값을 더하여 IMPORT Directory Table 의 Import Name Talbe RVA값을 가져옵니다.0012FF74의값 2세팅 2회전 반복 종료


(3회전 반복문)

16. .data section의 RVA값 00007000을 가져옵니다.

17. IMAGE_OPTIONAL_HEADER의 Import Table의 RVA값 000064D4 가져옵니다.

18. IMAGE_OPTIONAL_HEADER의 BASE RELOCATION Table 값 0000A000을 가져옵니다.

19. 00007000 - 0000A000 = FFFFD000 비교연산 후 아래JNZ 조건문에서 조건이 성립되어 0012FF74의값 3세팅 3회전 반복 종료


(4회전 반복문)

20. .reloc section의 RVA값 0000A000을 가져옵니다.

21. IMAGE_OPTIONAL_HEADER의 Import Table의 RVA값 000064D4 가져옵니다.

22. 0000A000 - 000064D4 = 00003B2C IMAGE_OPTIONAL_HEADER의 BASE RELOCATION Table 값 0000A000와 IMAGE_OPTIONAL_HEADER의 Import Table의 RVA값 000064D4를 비교연산 하며, 결과는 양수 즉 IMAGE_OPTIONAL_HEADER의 BASE RELOCATION Table 값 0000A000가 더 큰것으로 나옵니다. 결과가 성립되어 00401A4C주소로 점프하게 됩니다.

23. 00401A4C에서 00401A59번지 까지 연산을 진행하면 0000A000 - 0000A000 = 00000000 비교연산이 진행되며, JNZ 조건문이 성립되지않아 반복문 끝의 연산까지 진행된다.

24. 004079C0 주소에 000000003을 저장한다.

25. 004079C4 주소에 0000006E6을 저장한다.

26. 004079C8 주소에 0000005C0을 저장한다.

27. 총 4회 섹션의수 만큼 반복하여 각 섹션에 대한 정보를 가져오고 반복문을 종료한다.


(3) 프로그램 내의 스레드 실행 과 EXE 실행파일 탐색


그림51 에서 00401A97 에서 JMP문을 만나 그림 52부분으로 넘어와 실행합니다.

여기부터는 총 2개의 스레드를 생성하고 각각 메세지박스를 띄우는 스레드 하나, 각 시스템상 드라이브 타입을 구하고 로컬드라이브와 네트워크공유 드라이브인 경우 드라이브 최상위 부터 EXE(실행파일)을 찾으며, 찾은 실행파일에 대해 PE부분을 감염시키는 행위를 합니다.


[그림.52]

 

00401C48의 GetCommandLineA함수로 C:\Users\DK64\Desktop\win32\VIRUS.EXE 문자열이 저장되어 있는 메모리 주소를 파라미터로 넘기고 00401932 스레드 호출합니다.


[그림.53]

 

00401932 스레드 호출 시 Process Explorer로 스레드가 생성된 상태를 확인할 수 있습니다.



[그림.54]

 

그림 54에서 initialOwner 파라미터에 FALSE 값을 인자로 넘기면서 먼저 차지하는 스레드가 소유할 수 있는 옵션을 할당하여 뮤텍스를 생성합니다.

[그림.55]

 

00401C9C에서 전달 인자 없이 00401892 대기상태의 두 번째 스레드를 호출 합니다.

해당 스레드는 THREAD_PRIORITY_ID를 인자로 SetThreadPriority 함수를 호출하며, 스레드 실행 우선순위를 낮추고 ResumeThread함수로 대기상태의 스레드를 실행합니다.

이후 WaitForSingleObject 함수로 두번째 스레드가 실행이 완료 될 때 까지 INFINITE 무한 대기상태로 빠지게 됩니다.



[그림.56]

 

그림56은 그림55에서 두 번째 스레드를 호출 시 생성된 스레드를 확인합니다.
여기서 첫 번째와 두 번째 스레드가 생성되기 전에 3336스레드가 먼저 기본적으로 있었는데요.

이것은 하나의 프로세스는 기본적으로 하나의 스레드를 소유하고 있기 때문에 스레드는 총 3개로 볼 수 있습니다.


[그림.57]

 

00401932 첫번째 스레드 가 실행되며, 스레드의 기능은 단순히 메세지박스를 띄우고 리턴 합니다.

 

[그림.58]

스레드를 실행했을때 화면입니다.

 


[그림.59]

첫번째 스레드 종료 후 두 번째 스레드 00401892스레드가 실행됩니다.

해당 스레드가 실행되면 처음에 하는 역할이 GetLogicalDrives함수를 호출하게 되는데 이 함수의 역할은 현재 시스템에 설치되어 있는 디스크 드라이브에 대한 정보를 비트열로 반환합니다.

 

[그림.60]

제가 분석중인 시스템에는 A,C,D 총 3개의 드라이브를 사용 중입니다.

그림 59에서 함수리턴값이 0000000D입니다. 이것을 16진수에서 2진수로 변환하면 1101로 변환이 되고 1101을 오른쪽부터 읽습니다. 1101 = D,C,B,A 각 해당 문자열의 드라이브 타입이 있다면 1없으면 0이므로 제 시스템은 A,C,D의 드라이브가 있는것으로 확인 할 수 있습니다.

 

[그림.61]


그림 61에 004018BA 에서 ADD AL,41명령을 실행하고 이 값을 EAX레지스터에 저장합니다.

여기서 ADD AL,41의 41은 ASCII CODE로 A값을 뜻합니다.

GetDriveTypeA함수의 인자값으로 드라이브 타입을 아스키코드로 넘긴 후 호출하는데, 리턴값은 정수로 리턴합니다.


각각의 정수가 의미하는 값에 대한 설명입니다.


DRIVE_UNKNOWN 0 알 수 없음
DRIVE_NO_ROOT_DIR 1 최상위 경로가 없음
DRIVE_REMOVABLE 2 이동형 저장장치(USB등)
DRIVE_FIXED 3 고정형 저장장치(하드디스크)
DRIVE_REMOTE 4 네트워크 Drive(공유디스크)
DRIVE_COROM 5 DVD/CD-ROM 
DRIVE_RAMDISK 6 Ram Disk


004018C9, 004018CE에서 각각 3,4를 비교하는데 이는 로컬하드디스크와 공유디스크만 검색 하겠다는 뜻 입니다.

이후 조건이 성립하여 004018D7함수를 호출합니다.



[그림.62]

함수 호출 후 처음 SetCurrentDirectoryA 함수로 C:\의 위치로 세팅합니다.


[그림.63]

FindFirstFileA함수로 LPWIN32_FIND_DATA 구조체 변수 주소와 "*.EXE" 값을 인자로 넘기고 함수를 호출 하는데 함수호출 실패시 INVALID_HANDLE_VALUE(-1=FFFFFFFF)값을 리턴합니다.

C:\에 EXE파일이 없으므로 00401803으로 점프합니다.


 

[그림.64]

명령어를 계속실행하면 0040181A에서 FindFirstFile 함수를 호출하며 이때는 "*"를 인자로 넘김으로써 모든 파일 및 디렉토리를 찾습니다.

 


[그림.65]

0040182D ~ 0040183A 까지 보면 PUSH명령에서 $Recycle.Bin 을 인자로 넘기고 함수를 호출하는데 "도대체 저 문자열은 무엇일까?" 궁금해서 검색을 하니 휴지통 관련된 것 이라고 나옵니다.

저의 로컬 디스크 C:\에는 4개의 디렉토리밖에 없는데 뭔가 해서 혹시 숨겨지거나 시스템 관련 된 히든속성의 디렉토리인가? 라는 생각이 들었습니다.



[그림.66]

짐작 했던대로 역시 4개의 디렉토리 외에 별도로 여러가지 디렉토리가 있었고 디렉토리를 알파벳순으로 탐색하면서 실행파일 EXE만 찾는 행위를 합니다.

 

[그림.67]

다음 명령을 보면 SYSTEM 문자열과 2E(ASCII "." 콤마)문자열일 경우 0040186B로 점프합니다. 바로아래 함수 00401760를 건너 뜁니다.


 

[그림.68]

그림 67에서와 같이 SYSTEM 문자열과 2E(ASCII "." 콤마)가 아닌 경우 00401760함수를 호출하는데 이 함수의 역할은 재귀함수를 호출하는 역할입니다.

즉 그림 62의 과정을 다시 반복하여 SetCurrentDirectory함수부터 시작해서 EXE파일을 다시찾고 없으면 다시 디렉토리를 찾는데 여기서 SYSTEM과 SYSTEM 문자열과 그리고 2E(ASCII "." 콤마)가 아닌 디렉토리만 찾는과정을 반복적으로 진행합니다.

 

[그림.69]

루틴을 돌며 실행파일이 있을경우 CreateFile함수로 파일을 오픈합니다. 여기서 오픈은 파일을 실행해서 오픈하는것이 아닌 파일을 읽고 쓰기위한 준비 과정이라고 보시면됩니다.

파일 오픈 후 GetFileTime함수로 해당 실행파일의 만든날짜, 수정한날짜, 엑세스한날짜를 가져옵니다.

그 다음 4017CE번지의 함수를 호출하는데 이 부분 분석을 진행 하겠습니다.


(4) 정상 실행 파일의 PE헤더 비교


<004011F0 CALL VIRUS.00401022 함수 분석>


이 함수의 주 역할은 감염되는 파일의 PE헤더정보를 가저오며, 이 함수에서 제시한 조건이 하나라도 성립되지 않을 시 함수를 빠저나가는(리턴주소)로 분기 하게끔 되어있습니다.

 

[그림.70]

그림 68에서 004017CF 함수내부로 들어오면 그림69 에서 00401022의 또 다른 함수를 호출합니다. 이 부분 역시 분석을 진행해야 하므로 Step in(F7)하여 진입합니다.


[그림.71]

함수내부로 들어오면 ReadFile함수로 파일의 64바이트(40h) 만큼 읽습니다.

읽고나면 아래 메모리덤프에 4D5A(IMAGE_DOS_SIGNATURE MZ)부터 E8(e_lfanew)까지 읽었다는것을 확인 할 수있습니다.

00401045 ~ 0040105B 를 보면 함수 호출 후 40h와 비교를 합니다. 이것은 ReadFile함수 호출 후 40h(64바이트) 만큼 읽었는지 확인을 하는것이며, 아래 5A4D는 실행파일의 MZ시그니쳐를 확인 함으로써 실행파일인지 체크를 하는 것 입니다.

 

[그림.72]

SetFilePointer함수로 현재위치의 파일포인터로부터 E8(232)만큼 떨어진곳까지 옮깁니다.

즉 IMAGE_NT_HEADER까지 옮기게 됩니다.

 

[그림.73]

지금까지 감염시키는 파일의 IMAGE_DOS_HEADER부터 MS-DOS STUB까지 읽고 파일포인터를 IMAGE_NT_HEADERS의 위치까지 옮긴 후 IMAGE_NT_HEADERS끝까지 읽어 들입니다.

이후 그림 70과 같이 읽어들인 파일크기가 같은지 IMAGE_NT_HEADERS의 첫 시작 시그니쳐 PE가 맞는지 체크하고 틀릴경우 함수의 마지막 리턴부분으로 분기조건에 의해 점프합니다.

 

[그림.74]

그림 32에 4019CF주소를 보면 4079A4에 EDI값 4를 저장합니다.
그리고 EDI값 만큼 반복을하는데 이 값은 VIRUS.EXE실행 파일의 섹션의 수를 저장했던 값 입니다.

그 값과 961C0004 + 4(VIRUS.EXE 섹션의 수)를 연산합니다.

00401CF까지 실행하면 마지막 MOVZX EAX,AX로 0140을 32비트로 확장하여 00000140(생성 할 동적메모리의 사이즈)으로 EAX에 저장 합니다.

 

[그림.75]
004010D2 에서 연산결과로 나온값을 사이즈(140)을 인자로 넘겨서 GlobalAlloc함수로 동적메모리를 할당 합니다.
이 후 VIRUS.EXE 악성코드 현재 위치 에서부터 파일을 읽게 되는데 그 위치는 IMAGE_SECTION_HEADER .text의 Name값 2E746578부터 읽어서 악성코드의 마지막 섹션 IMAGE_SECTION_HEADER .rsrc까지 각각 섹션헤더의 정보를 가져와 002EFAA8부터 140만큼 읽어 들입니다.
그림 75를 보면 노란박스가 각 섹션헤더의 Name값이고 작은 빨간박스가 첫 번째 .text섹션의 Name, VirtualSize, RVA등의 정보입니다.
각 섹션 Name의 뒤에는 해당하는 섹션의 정보를 가저옵니다.

 


[그림.76]
00401129 ~ 0040115A까지 분석을 진행 한 내용이며 헤더 내용을 체크합니다.

1. 00401135에서 3C8값과 IMAGE_OPTIONAL_HEADER의 Size Of Header값 400과 비교하여 3C8값이 Size Of Header값보다 크면 분기문으로 종료합니다.

2. 00401149 아래 명령에서 IMAGE_NT_HEADER에 IMAGE_OPTIONAL_HEADER의 Adress Entry Point값 3DA6와 바로 위에서 연산한 4000값과 비교해서 Adress 
Entry Point가 크거나 같으면 리턴문으로 점프하여 종료합니다. 하지만 조건이 성립되지 않으므로 분기 하지 않습니다.

3. 00401151에서 01값과 20값을 TEST연산으로 BYTE로 잘라 비교 연산 하는대 이 값은 IMAGE_FILE_HEADER의 Characteristics 값 입니다.
Characteristics 값중 0100은 IMAGE_FILE_32BIT_MACHINE값으로 32비트 실행파일을 의미하며, 0200은 DLL파일을 의미합니다.
비교 후 DLL파일이 아닌 실행파일 이므로 리턴주소로 분기하지않고 다음 로직을 실행합니다.

4. 그리고 0040117D에서 ADD 연산을 하게되는데 .rsrc의 Size of Raw Data(2E00) 값과 Pointer to Raw data(5600)값을 더한 결과값 8400을 EDX에저장합니다.
이후 아래 명령 0040117D에서 SetFilePointer에서 리턴한 파일포인터값 8400과 위에서 연산한 8400과 비교를하고 같지 않을 경우 리턴주소로 분기합니다.

5. 마지막으로 00401183에서 00002D30(.rsrc의 Virsual Size 값 )과 00002E00(.rsrc 의 Size of Raw Data값) 값을 비교하는데 아래 명령에서 JA 명령어를 사용하므로 .rsrc의 Virtual Size값 00002D30이 Size of Raw Data 00002E00보다 크면 리턴으로 분기 합니다.
 

 

[그림.77]
그림 75에서 가저왔던 PE헤더의 정보를 확인 할 수 있습니다.
이로써 00401022 함수분석이 끝났습니다.


<정리>
1. 타겟 PE헤더 정보를 읽습니다. 처음 4D5A(IMAGE_DOS_SIGNATURE MZ)부터 E8(e_lfanew)까지 읽습니다.
2. E8(e_lfanew)까지 읽고 파일포인터를 E8(e_lfanew)로 옮깁니다. 즉 IMAGE_DOS_HEADER를 읽고난 후 IMAGE_NT_HEADER를 읽기위해 포인터를 옮깁니다.
3. E8까지 옮긴 후 타겟 파일이 PE파일인지 체크하고 PE가 아닐경우 리턴문으로 분기합니다.
4. Size Of Header, Adress Entry Point, Characteristics,SetFilePointer에서 리턴한 파일포인터값,Virtual Size 값들을 체크하고 조건에 맞지 않을경우 함수를 빠저나와 다른 실행 파일을 찾는 역할을 합니다.

(5) 감염대상의 파일 수정 후 코드 복사

<004011F0 CALL VIRUS.004011D6 함수 분석>
그림 69에 보면 4011D6의 함수 루틴을 보면 이 함수 안에서 00401022(PE헤더정보 읽는 함수)를 호출했고 호출완료 후 이어서 분석이 진행됩니다. 

 

[그림.78]
위에서 분석 된 PE헤더정보의 조건이 하나라도 만족하지 않을시 분기문으로 점프하여 리턴하며, 리턴 후 TEST명령으로 리턴값이 0일경우 다시 루틴의 처음 부분인 디렉토리와 파일을 찾는부분 부터 다시 진행이 됩니다.
004011F8의 분기문에서 PE헤더의 정보를 정상적으로 읽어왔기 때문에 분기하지 않고 로직을 이어서 실행합니다.
004011FE ~ 00401221까지의 분석내용입니다.
각각 3개의 레지스터 ESI, EAX, ECX에 PE정보를 저장합니다.
ESI 에는 0004를 32비트 확장하여 00000004(Number of Section)로 저장합니다
EAX 에는 IMAGE_OPTIONAL_HEADER의 Address Entry Point 00003DA6을 저장합니다.
ECX 에는 IMAGE_OPTIONAL_HEADER의 IMPORT Table data 5010의 주소값을 ECX저장합니다.
이 후 EAX값을 백업하고 다시 EAX값에 .text섹션의 시작주소 002B0E90을 저장합니다.
그리고 00000004(Number of Section)와 00000000을 비교하여 섹션값이 0보다 작거나 같다면 분기합니다.

 


[그림.79]

2회전 0040122C ISH-.rdata의Size of Raw Data(1C00)값을 EBX에 저장 합니다.
004012FF에서 기존 EBX값 RVA(1000)+Size of Raw Data(3000) =4000에 1C00를 더하여 5C00으로 만듭니다.

이 후 5C00 과 401233의 5010(IOH-IMPORT Table)과 비교합니다.
RVA와 Size of Raw Data값을 더한값 5C00이 더크므로 반복문을 빠저나와 0040123F로 분기합니다.
401242에서 OR BYTE PTR DS:[EAX+ECX*8+27],80 명령문에서 [EAX+ECX*8+27]이 부분을 확인하면, EAX는 .text섹션의 시작주소 002EFAA8이고 ECX는 바로위에서 계산한 주소값입니다.
즉 .text섹션의 시작주소 [002EFAA8+(5*8)+27]의 식이 성립되며, 계산을 하면 002EFAF7=40('@') 결과가 나옵니다.
이 값을 확인하면 .rdata섹션의 characteristics 40000040의 제일 마지막값 40임을 확인 할 수 있습니다.
이제 00401242에서 40, 80을 OR연산하게 되고 결과로 C0값을 갖게되어 characteristics 값은 C0000040의 값이 되며, 이값의 의미는 80000000 값 IMAGE_SCN_MEM_WRITE 을 추가한 것입니다.
정리하면 .data 섹션의 characteristics 값에 IMAGE_SCN_MEM_WRITE 옵션을 추가하여 .data섹션의 쓰기권한을 부여합니다.


[그림.80]

401259 번지에서 MOV EDX,DWORD PTR DS:[EBX+EAX-1C]의 명령을 실행하는데 실행하기 전 EBX에는 14의값을 SHL EBX,3명령으로 000000A0으로 만듭니다.
그래서 [EBX+EAX-1C] 이 부분은 [000000A0+00190E90-1C]가 되며, 이 주소의값 4바이트를 EDX에 저장하합니다. 저장되는 값은 00007000으로 ISH-.rsrc값입니다.


[그림.81]

그림 79에서 00401262의 JNZ분기문을 그림 80의 004012D2로 이동하게 됩니다.

이 부분은 004012DA에서 IOH-Size of image 0000A000 값을 EAX저장 하는데 이것은 감염대상의 실행파일이 메모리를 차지할 크기를 의미합니다.

이후 004012E6와 004012F9에서 Size of image 값을 각각 014FFDD8과 004079D0에 저장하고, 파일포인터를 파일의 제일 끝으로 이동합니다.


[그림.82]


※중요

004012FF, 00401308, 00401318 3개의 라인입니다.

이 부분은 ESI와 EDI 레지스터를 각각 주소값으로 세팅하는데 ESI레지스터에는 VIRUS.EXE PE Header의 IMAGE_SECTION_HEADER .text의 시작 주소값 00401C0로 세팅하여 저장합니다.

EDI레지스터에는 감염대상(EX:String.exe)의 PE header의 IMAGE_SECTION_HEADER .rsrc의 끝나는 바로 뒷부분의 빈공간의 시작주소로 세팅하게 됩니다.

세팅이 완료되면 00401318 명령에서 VIRUS.EXE PE Header의 IMAGE_SECTION_HEADER의 총사이즈만큼 감염대상의 빈공간에 저장을 하며 저장을 할 때 4바이트씩 복사를 합니다.



[그림.83]

악성코드 VIRUS.EXE의 IMAGE_SECTION_HEADER입니다.

정확한 확인을 위해 그림.84에 악성코드와 감염대상의 PE구조를 첨부 하였습니다.

그림84와 비교 시 악성코드의 IMAGE_SECTION_HEADER을 확인 할 수 있습니다.



[그림.84]

그릶83은 감염 대상(EX:String.exe) 파일의 IMAGE_SECTION_HEADER .rsrc 의 다음 1바이트 부터 160바이트의 크기의 빈공간입니다.

악성코드는 이 공간에 28h(40d * 4바이트)씩 자신의 IMAGE_SECTION_HEADER를 복사합니다.


 

[그림.85]

그림81 부터 그림84까지의 내용을 확인 하면 정확히 일치합니다.

 

 

[그림.86]

00401334에서 GlobalAlloc 함수로 4096Byte 크기의 Heap을 생성 합니다.

이때 생성된 Heap의주소는 001F0C40주소로 할당이 됩니다.(힙이 생성되는 주소는 랜덤)

할당한 후 힙의 생성이 실패하면 로직의 끝 이동하여 종료합니다.

0040134C에서 ECX레지스터에 VIRUS.EXE 의 .text섹션의 시작주소 004001C0를 저장합니다.

0040135A ~ 0040135D까지보면 생성한 Heap의 시작주소 001F0C40에 8을 더하여 001F0C48로 만듭니다.

00401362에서 ※ECX(.TEXT 4001C0)+EAX(F)*8+C=400244 reloc의 rva값 0000A000를 ESI에저장합니다.

[ 그림.87]

00401379에서 ADD ESI,DWORD PTR DS:[407B94]명령이 나오는데 이것은 VIRUS.EXE의 IMAGE_BASE값에 .reloc섹션의 RVA값 0000A000를 더하여 .reloc 섹션의 Virual Adress를 얻습니다.

다음으로 00401382에서는 그림.87에서 할당 한 Heap주소(EDI)에 VIRUS.EXE의 .reloc 섹션의 값(ESI)을 4바이트씩 복사합니다.

그리고 00401399에서 .text의 시작주소를 EAX에 저장합니다.


[그림.88]

그림.84에보면 감염대상의 IMAGE_SECTION_HEADER 뒤의 빈 공간의 VIRUS.EXE자신의 IMAGE_SECTION_HEADER를 복사했었습니다.

그림.88의 004013C1 ~ 004013EE까지는 복사 한 섹션헤더의 정보를 수정하는 작업을 합니다.

첫 번째 감염대상파일의 .text Pointer of Raw Data(005FFB5C=00001000)값을 00008400(파일포인터 값값)으로 변경합니다.

두 번째 감염대상파일의 .text RVA(005FFB54=00001000)값을 0000A000으로 변경합니다.

그리고 004013D5에서 IOH의 FILE Alignment값 200을 014DFDD8주소에 저장합니다.

세 번째로 004013E2에서 감염대상파일의 .text 의 Size of Raw Data값 00005000을 00005000으로 변경 시도를 하지만 값이 같기때문에 변화는 없습니다.


[그림.89]

위의 그림에서 와 같이 복사한 IMAGE_SECTION_HEADER의 .text Section의 RVA, Size of Raw Data, Pointer to Raw Data 3개의 값을 변경하고 조건문에 의해 분기합니다.

 

[그림.90]


 

분기 후 VIRUS.EXE파일의 Image Base 00400000 과 .text 섹션의 Pointer to raw data 00001000값을 더하여 .text 섹션의 rva값으로 세팅을 하고 004014AC 부분에 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] 명령으로 동적메모리를 할당한 영역의 주소에 .text 섹션전체를 4바이트씩 저장합니다.

 

[그림.91]

 

이어서 위의 그림에서는 004014B5에서 ESI에 VIRUS.004000C8("PE") 시그니쳐의 시작주소를 세팅합니다.

그리고 ESI에는 VIRUS.EXE의 .reloc SECTION의 첫 시작주소 값 0020E420으로 세팅합니다.

각각 ESI와 EDI값을 세팅 한 후 0020E420에 5C0값을더하여 0020E9E0으로 세팅하여 스택에 저장합니다.

그림.51에서 3개의 값을 레지스터에 저장했었는데 3개중의 1개의 값 5C0를 더하는 것입니다.

 

[그림.92]

 

이 부분의 루틴은 0040152D에서 [EBP-8]이 가르키는 주소값에 2를 더하여 저장하고, D4h(212)만큼 루프가 진행되는 로직입니다.

예로 0028E64C라는 값이 EBP-8이 가르키는 값이라고 가정합니다. 이때 0028E64C값이 가르키는 값 30A53085에서 2바이트 3085를 AX값에 저장합니다.

그럼 값은 0028E64C가아니라 00283085가 됩니다. 그리고 이 값을 EDI레지스터에 저장합니다.

그리고 AND DL,0F000 명령을 실행하여, 00283000값으로 변경하고 CMP DL,3000 을 실행하여 JNZ분기문을 실행하는데 결과로 ZF= 1이므로 분기하지 않게 됩니다.

여기까지가 004014FE ~ 00401510까지의 내용입니다.


이어서 생성된 힙 주소를 EDI에 세팅하고, EAX값 00283085 를 0FFF와 AND연산하여 00000085로 EAX값을 세팅하고, 힙 주소 00291208에 85를 더하여 0029128D값으로 EAX를 세팅합니다.

그리고 0040152B라인의 명령어를 보면 0029128D가 가르키는 곳에 00009000을 더하게 되며, 아래와 같이 루프를 반복 하게 됩니다.

 

[그림.93]

 

그림.92에서 00009000더하고 그림 93에서는 코드를 타겟 대상에 복사를 합니다.

 

 

[그림.94]

 

처음은 5000(20480),1000(4096),1000(4096)크기로 감염대상파일에 코드를 복사합니다.

 

[그림.95]

 

그림.94에서 코드를 복사했고 그 만큼 파일 사이즈도 늘어나며 PE구조도 변경 됬습니다.

그리고 그림.95에서는 변경된 정보를 수정하고 있습니다.

각각 Number of section을 우선 8로 세팅합니다 기존 4에서 4만큼 늘어나 섹션이 총 8개로 된것을 짐작할 수 있습니다.

그리고 Adress of Entry Point를 AE39, Size of Image를 14000, Import Table의 RVA 값을 F4D4, Size를 3C로 각각 세팅합니다.

 

 

[그림.96]

 

이어서 각각 20, 40, 80과 비교하여 EAX가 가르키는 각 섹션헤더의 RVA값을 EDI,ESI,EDX의 조건에 맞는 레지스터에 덧셈을 합니다.

총 8개의 섹션으로 총 8번 루프를 돌게 되며, 결과는 ESI=7800, EDI=8000, EDX=0으로 세팅되어 PE 정보를 세팅합니다.

PEView에서 각각 세팅 된 값은 Size of Code=8000, Size of Initialized Data=7800, Size of Uninitialized Data=0으로 세팅이 됩니다.

 

 

 

[그림.97]

 

이어서 SetfilePointer함수로 감염대상의 파일의 포인터를 000000F0로 세팅하고 WriteFile함수로 F8h(248)만큼 데이터를 복사합니다.

그리고 다시 WriteFile 함수로 .text 섹션헤더부터 복사를 합니다.

이 과정을 보면 변경된 PE구조의 정보를 정상 파일에 세팅하기 위함으로 추측 할 수 있습니다.

 

[그림.98]

 

마지막으로 SetfileTime함수로 최종 파일 접근 시간을 세팅하고 CloseHandle함수를 호출 하여 종료합니다.

이런식으로 정상적인 EXE파일을 검색하고 감염되지 않은 파일인 경우 자신의 악의 적인 코드를 정상파일에 삽입하게 됩니다.

이때 사용자는 자신도 모르게 정상적인 프로그램을 실행 하게되고 프로그램내에 숨겨뒀던 악의적인 코드가 자동 실행함으로써 공격자의 의도대로 공격이 가능하게 됩니다.

하지만 분석에 사용 된 샘플은 자신의 EXE파일을 찾고 감염되지않은 실행파일에 대해서만 자신의 코드를 붙여넣는 기능외의 특이 기능은 없는것으로 보아 테스트용 으로 사용된것으로 추측되며, 이와 비슷한 변종이 많이 있을거라고 예상됩니다.

 


posted by 미스터리 DKL