본문 바로가기

[REVERSING]/▶악성코드분석

[악성코드]BlackEnergy2(_bot.exe) #1 첫번째 프로세스 분석

 

 

 

 

 

[쿠쿠 분석결과]

 

요즘 모 교육을 받고 있는데 샘플 상세분석은 안하더라구요.ㅠ

궁금하기도 하고 해서 한번 상세 분석을 진행해 보았습니다...

 

 [그림.1]

 

그림.1을 보면 알수 없는 문자열이 스택에 저장되는 것을 볼 수있습니다.

악성코드 제작자가 분석을 방해하거나 백신우회? 를하기위해 저렇게 알 수 없는 문자열로 저장해놓고 0x15117430 .data section에 디코딩을 합니다.

디코딩을 하면 URL이 나오는데 이 정보를 숨기고 싶었나봅니다.

 

 [그림.2]

 

.bdata section 입니다. 이 데이터의 아스키 코드값을 메모리의 offset으로 사용합니다.

 

 [그림.3]

 

그리고 .data section의 데이터입니다. 그림.2에서 Memory + Ascii offset 한 위치의 값을 .data section에서 찾아오는 역할을 합니다.

 

 [그림.4]

 

.data section의 문자를 offset으로 사용하여 Memory + Ascii Offset 와 같이 연산하여 메모리 주소를 구합니다.

그리고 그 메모리 주소에있는값으로 쉬프트 연산과 or연산등을 이용하여 디코딩을 진행합니다.

그림.4의 메모리 주소(0x15111960)를 보면 이렇게 진행한 1바이트의 문자를 0x15117430으로 저장하는 것을 확인 할 수있습니다.

 

 [그림.5]

 

ollydump로 덤프떠서 확인해보니 디코딩된 문자열을 확인 할 수 있었습니다.

원본과 비교하면 확실히 다른점을 찾을 수 있습니다.

그리고 맨아래 섹션이 추가되었는데 이것은 악성코드에서 추가한 섹션이아니고 ollydump로 덤프뜨면 생기는 섹션이더라구요 참고하세요 ㅋ

 

[그림.6]

 

첫 OEP부터 코드를 보면 정말 분석하기 친절하게 함수들이 나열되어있네요..

처음 윈도우 시스템 디렉토리 경로를 구하고 GetModuleHandleA함수로 현재 실행중인 프로세스의 핸들을 얻어옵니다.

사실 정확히는  GetModuleHandleA함수로 얻어오는값은 현재 실행중인 프로세스의 ImageBase값 입니다.

리턴된 메모리주소값(0x15110000)으로 가서 확인해보면 실행파일의 처음부분인 ImageBase주소임을 확인 할 수 있습니다.

그리고 이 ImageBase값을 인자로 GetModuleFile함수를 호출하여 현재 실행중인 프로세스의 이름을 구해옵니다.

 

[그림.7]

 

여기서 뮤텍스를 이용해서 조건에 맞게끔 코드제작자가 원하는 함수를 호출 할 수 있도록 제어를 합니다.

우선 이름있는 뮤텍스를 생성하고, GetLastError 함수를 호출해서 리턴값이 0x0B7 인지 체크합니다.

이 0x0B7은 MSDN을 참고해보면 다음과 같은 값임을 알 수 있습니다.

 

0x0B7(183)

ERROR_ALREADY_EXISTS183

Cannot create a file when that file already exists.

 

위의 뮤텍스 이후의 JNZ 분기문을 해석해보면 현재 뮤텍스가 생성이 되고 에러코드가 0x0B7인지 체크하는데 첫1회 실행시 0x0B7이 아니므로 바로 아래루틴으로 이동하게 됩니다.

하지만 이 악성코드 뒷부분을 확인 해보면 같은 코드로 CreateProcess를 함수를 호출 하게되고 그때는 이부분에서 조건이 만족(0x0B7 = GetLastError 리턴값)되어 점프를 하게됩니다.

이렇게 각각의 뮤텍스를 이용하여 각각 실행되는 함수가 서로 다르게 됩니다.

 

[그림.8]

 

그리고 GetCurrentProcess로 현재 실행중인 프로세스의 모조핸들을 구하고 이 핸들을 이용하여 OpenProcessToken 현재 프로세스의 AccessToken을 구합니다.

 

[그림.9]

 

그리고 얻어온 AccessToken에서 SeDebugPrivilege권한이 있는지 확인하기 위해 LookupPrivilieageValueA함수를 호출합니다.

이 AccessToken은 프로세스의 보안속성에 관련해서 명시한 리스트들 이며, 간단히 다음과 같이 확인 할 수있습니다.

 

[그림.10]

 

ProcessExplorer로 확인이 가능합니다.

현재 BlackEnergy2의 AccessToken에서 SeDebugPriviege는 Disabled되어있습니다.

아마도 이부분을 활성화해서 타 프로세스를 디버깅모드로 접근하여 프로세스를 수정하거나 Context값들의 획득이 목적인것으로 추측합니다.

 

[그림.11]

 

그리고 여기서 AdjustTokenPrivileges 함수호출로 권한상승을 하게됩니다.

즉 위에서 확인했던 SeDebugPriviege = Disable권한을 여기서 Enable로 활성화 시킵니다.

이 함수에서 중요한 파라미터는 pNewState입니다.

pNewState = 0x0012FF20 이며 해당 버퍼에 저장된 구조체의 값들이 순차적으로 있습니다.

여기서 4번째값이  TOKEN_PRIVILEGES 구조체이며, 간단히 확인해 보겠습니다.

 

 

[그림.12]

 

MSDN에서 확인해보면 여러 값들이 있는데 여기서 사용된 값은 SE_PRIVILEGE_ENABLED입니다.

 

[그림.13]

 

이값을 WinNt.h파일에서 확인해보면 0x00000002 인것을 확인이 됩니다.

위의 HEX DUMP창의 4번째 구조체의 값과 비교해보면 정확히 일치하는것을 알 수 있습니다.

이것으로 SE_PRIVILEGE_ENABLE값으로 함수를 호출했다는것을 확인 할 수있습니다.

 

여기서 제가 분석할때 쓰려고 만든 허접하고 허접한 코드분석프로그램으로 분석이 어디까지 진행됬는지 확인한번하고 가겠습니다.ㅋ;;

 

[그림.14]

 

한 4분의1정도 분석이 완료됬네요 나머지는 lstrlen함수 호출을 반복하는 부분이 대부분이라 현재의 코드상 분석진행은 조금 남았다고 볼 수 있겠습니다. ㅎㅎㅎ

 

 

[그림.15]

 

다시 ProcessExplorer로 확인해보면 SeDebugPrivilege값이 Enabled된것을 확인 할 수있습니다.

제대로 변경 되었네요..

 

[그림.16]

 

그리고 이어서 GetModuleHandleA함수로 ntdll.dll의 ImageBase의 주소값을 얻어오고 이 값으로 GetProceAddress함수의 인자로 넘겨서 NtSystemDebugControl 함수에대한 함수포인터를 얻어옵니다.

참고로 얻어온 값은 0x7C93DE4E인데 이값은 ntdll.dll에서 EXPORT_ADDRESS_TABLE(EAT에서 얻어온 값입니다.)

PEVIEW로 해당값을 EAT에서 찾아보면 0xDE4E이며 현재 로드된 ntdll.dll의 ImageBase값과 0xDE4E값을 더하게되면 0x7C93DE4E값이 됩니다.

 

 

[그림.17]

 

그리고 ZwQuerySystemInformation 함수로 SystemModuleInfo를 인자로 함수를 호출하게 됩니다.

이것은 현제 시스템의 커널이미지에 대한 정보를 얻기위해 호출하는 함수입니다.

호출하여 저장된 버퍼주소 0x00970000부터 보면 각종정보들이 담겨져 있습니다.

현재 로드된 커널이미지는 ntkrnlpa.exe인데요 이것은 운영체제별로 로드되는 이미지가 틀리다고합니다.

 

ntoskrnl.exe (1 CPU)                    One CPU

ntkrnlmp.exe (N CPU, SMP)          MultiProcessCPU , Symmetric multiprocessing

ntkrnlpa.exe (1 CPU, PAE)            One CPU, Physical addresss extention

ntkrpamp.exe (N CPU, SMP, PAE)  MultiProcessCPU, Symmetric multiprocessing, Physical addresss extention


 

여기서 현재 로드된 커널이미지는 ntkrnlpa.exe이며 PAE(Physical addresss extention)가 있습니다.

이것은 기본적으로 Process는 4G의 메모리를 필요로 하는데 그 이상의 메모리를 사용하려고 할때 확장이 가능하다는 뜻입니다.

 

[그림.18]

 

ZwQuerySystemInformation 함수로 호출하여 리턴된 구조체의 정보를 보면 ntkrnlpa.exe의 ImageBase의 주소를 확인 할 수 있으며, 해당되는 주소값은 0x804d9000으로 커널 메모리 영역의 주소입니다.

windbg를 사용하여 해당 메모리위치로 가보면 로드된 ntkrnlpa.exe 커널이미지를 확인 할 수 있습니다.

 

[그림.19]

 

이어서 ZwQuerySystemInformation 함수로 얻어온 정보를 기반으로 ntkrnlpa문자열을 파싱하기 위한 작업을 진행하고,

LoadLibraryExA함수로 ntkrnlpa.exe 커널이미지의 메모리상에 로드된 ImageBase값을 얻어옵니다.

 

 

[그림.20]

 

그리고 ntkrnlpa.exe의 ImageBase값 0x00A60000 값을 인자로 ntkrnlpa.exe내에서 KeServiceDescriptorTable값의 주소를 EAT테이블에서 찾습니다.

KeServiceDescriptorTable은 다음과 같은 구성으로 되어있습니다.

 

KeServiceDescriptorTable 

KiServiceTable                    // SSDT테이블의 시작주소를 가르킨다.(이 테이블의 시작주소로부터 함수들의 진입주소가 리스트 되어있다.)

CounterTable                      // 각 함수들이 몇번 호출되어 있는지 나타낸다.

ServiceNum                        // SSDT테이블에 포함된 함수의 갯수를 저장한다.

KiArgumentTable                 // 각 함수별 파라미터의 크기를 나타내는 값을 저장하고 있는 테이블의 포인터가 저장되어 있다.

 

그럼 실제 KeServiceDescriptorTable의 주소를 ntkrnlpa.exe에서 찾아보겠습니다.

 

[그림.21]

 

현재 메모리상에 로드된 ntkrnlpa.exe의 ImageBase값은 0x006A0000 이었고, 이 값에다 EAT의 KeServiceDescriptorTable의 함수 진입주소를 더하면 그림.20과 같이 0x00ADC0A0값이 됩니다.

 

[그림.22]

 

그리고 ntkrnlpa.exe 의 ImageBase값(0x006A0000)과 KeServiceDescriptorTable의 VA값(0x00ADC0A0)을 빼서 RVA값(0x0007C0A0)로 만들어서 스택에 저장하고 다시 이 값을 ECX레지스터를 이용하여 함수 0x15114930을 호출하게됩니다.

함수내부로 들어가 보겠습니다.

 

[그림.23]

 

함수내부로 들어갔더니 내부에서 한개의 함수를 하나더 호출합니다.

함수이름은 0x151148E0이며 이 함수의 역할은 커널이미지 ntkrnlpa,exe 에서 PE signature offset, Machine offset , Optional header magic offset, .text section start offset을 0x12FEF4에 순차적으로 저장하고 빠저나옵니다.

그리고 다음과 같은 코드를 실행 합니다.

 

[그림.24]

 

그림.24에서 핵심은 박스안의 코드들입니다.

굉장히 복잡하네요.. 그러나 차근차근 한라인한라인 분석해보면 뭘 하려는지 파악이 됩니다.(수련을 한다고 생각하시고...-0-;)

 

우선 여기서 0xC49300 주소가 ntkrnlpa.exe의 .reloc section의 시작주소입니다.

박스 윗부분은 이 섹션에서 특정값을 파싱하고 아래의 박스안의 코드를 실행하기 위한 사전작업을 진행하는 것입니다.

별로 중요하지  않으므로 윗부분의 코드는 생략하고 박스 안의 코드부터 분석을 하겠습니다.

처음 0xC49308 주소 즉 .reloc section의 시작주소에서부터 +8 offset의 위치에 있는 WORD값 0x3784을 저장해두고, 다시 0x3784를 0x0FFF 와 AND 연산하여 0x0784값으로 만듭니다.

그리고 메모리주소 0x151149F8의 코드에서 ADD EDX,DWORD PTR DS:[EAX] 연산을 합니다.

여기서 각각 들어있는 값이 EDX에 0x0784이고, [EAX]의 값은 [0xC493000]입니다.

즉 이코드를 계산하면 0x0784과 0xC493000이 가르키는값 0을 더하는 코드입니다.

0과 더하기 때문에 당연이 연산결과는 0x0784가 되겠습니다.

그리고 이 연산결과 값과 ntkrnlpa.exe의 ImageBase값 0xA60000을 서로 더합니다.

그 결과는 0xA60784가 되며, 이 값은 ntkrnlpa.exe의 .text section(코드가 실행되는 영역)을 가르키는 주소값으로 세팅이 됩니다.

0xA60784가 가르키는 주값을 따라가 보면 0x4874FC 와같이 또다른 VA(VirtualAddress)값이 들어있습니다.(메모리주소 0x15114A06부터의 설명)

이값으로 다음과 같은 연산을 진행합니다.

SUB EAX,DWORD PTR DS:[EDX+1C]

여기서 EAX값은 0x4874FC이며, [EDX+1C] 가르키는 값은 ntkrnlpa.exe 실제 파일상의 ImageBase주소인 0x400000으로 세팅되어있습니다.

0x4874FC(VA) - 0x400000(ImageBase) = 0x874FC(RVA)와 같이 RVA를 구하기 위한 연산임을 파악 할 수 있습니다.

그리고 첫번째 블록의 마지막 CMP ECX,DWORD PTR SS:[EBP+C]의 비교연산을 진행하는데 이것을 해석하면 다음과 같습니다.

0x874FC(RVA) == 0x7C0A0(KeServiceDescriptorTable 그림.22 참조)와 비교하여 같지 않을 경우 루프의 처음으로 돌아가고 같을 경우 이어서 코드가 실행이 됩니다.

 

다음으로 두번째 블록의 코드입니다.

메모리주소 0x15114A19를 보면 ADD EDX,DWORD PTR SS:[EBP-20]의 코드가 있습니다.

여기서 EDX는 0xA60000(ImageBase)값 입니다. 그리고 :[EBP-20]의 값은  0x7C0A0(KeServiceDescriptorTable RVA)의 값을 가르키는 RVA값 1BA48E입니다.

이 두값을 더하면 C1A48E값이 되며, 이값을 EDX레지스터에 저장합니다.

그리고 [EDX-2]와같이 C1A48E에서 -2의 위치의 데이터를 가리켜 EAX에 저장을하고, 이 EAX값과 5C7값과 비교연산을 진행합니다.

즉 여기서 5C7의 값의 의미를보면 PE상의 RVA값을 구하고 그 구한 위치에서 앞으로 2바이트 이동후 그 바이트 값과 비교한다면 이것은 OPCODE로 의심해 볼 수 있습니다.

한번 확인해 보겠습니다.

 

 [그림.25]

확인 결과 0x1BA48E 주소에 0xA0C047이 있으며, 0x1BA48E- 0x2 =  0x1BA48C에는 0xC705(Little Endian표기 변환시 0x5C7) 인것을 확인 할 수있습니다.

그럼 이 OPCODE가 어떤것을 의미하는지 알아야합니다.

다음과 같이 Olly에서 빈공간에 코드를 붙여다 놓습니다.

 

[그림.26]

 

코드를 확인결과 MOV DWORD PTR DS:[47C0A0], 42AB8C의 코드로 0x47C0A0가 가르키는 곳에 0x42AB8C주소값으로 세팅하는 OPCODE입니다.

음.. 뭔가 냄새가 나기 시작하네요 ㅎㅎ

어쨌든 이어서 그림.24의 메모리주소 0x15114A30 부터분석을 이어서 진행하겠습니다.

마지막 코드 부분의 메모리 주소 0x15114A30에 보면 MOV EAX,DWORD PTR DS:[ECX+4]와 같은 코드가 있습니다.

현재의 ECX값은 0xC1A48E이며 여기서 +4의 값은 C1A492값이 되고, 이 주소값이 가르키는 값은 0x42AB8C값이 됩니다.

그리고 그 아래 SUB연산은 역시 VA값을 RVA로 변환하는 코드가 되겠습니다.

그렇게되면 0x2AB8C가 되며, 추가적인 작업없이 함수를 빠저나갑니다.

 

[그림.27]

 

그 다음 작업으로 SSDT 테이블에 함수포인터 주소들이 저장되어 있는데 이 주소들을 자기자신의 .data section에다 가져오는 파싱하는 작업을 진행합니다.
그림.27에서 분석을 하다보면 그림.26에서 MOV DWORD PTR DS:[47C0A0],42AB8C 코드의 의미를 알 수있습니다.
먼저 답을 말하자면 0x47C0A0(VA)는 KeServiceDescriptorTable의 시작주소이며, 0x42AB8C(VA)는 위에서 설명한 KeServiceDescriptorTable의 첫번째 멤버인 KiServiceTable(SSDT)의 시작주소 입니다.
즉 KeServiceDescriptorTable 의 첫번째 멤버에 SSDT 테이블의 시작주소로 세팅하는것으로 볼 수 있습니다.
박스위의 코드먼저 분석하면 다음과 같습니다.
우선 0x42AB8C(SSDT VA) - 0x400000(IMAGEBASE) = 0x2AB8C(SSDT RVA) SSDT의 RVA값을 획득하고,
0x2AB8C(SSDT RVA) >= 0x1F9900(ntkrnlpa.exe의 Optional_Header의 SizeOfImage)의 조건이 만족할때까지 루프를 돕니다.
그리고 0xA60000(LoadLibray로 로드된 krnlpna.exe의 IMAGEBASE) + 0x2AB8C(SSDT RVA) = 0xA8AB8C(SSDT VA)값으로 세팅합니다.
그리고 0xA8AB8C(SSDT VA)가 가르키는 첫번째 주소에는 함수에 대한 포인터주소가 VA형식으로 저장되어 있습니다.(그림.28참조)

0xA8AB8C(SSDT VA)가 가르키는 첫번째 주소에는 0x4C2A5E가 값이 있으며, 이 주소값이 VA이기 때문에 RVA로 변환하는 작업을하여 0x2CA5E로 만듭니다.
그리고 그림.17 과 그림.18을 보면 현재 커널영역에 로드된 커널이미지의 주소를 얻어왔었습니다.
그 주소는 0x804D9000이고 이 주소값역시 IMAGEBASE주소인것을 확인 했었습니다.
이제 이 주소에다 SSDT 테이블의 첫번째 함수주소의 RVA값을 더해서 커널영역에 로드된 커널이미지의 SSDT테이블의 첫번째 함수의 주소값으로 세팅을 합니다.
0x804D9000 + 0x2AB9C(SSDT RVA) = 0x8059BA5E(Kenel영역에 로드된 커널이미지의 SSDT 테이블의 첫번째 함수포인터 nt!NtConnectPort)

 

 

[그림.28 합쳐야함]

 

여기서 확인해보겠습니다.

커널에 영역에 로드된 커널이미지의 IMAGEBASE인 0x804D9000 주소값과 0x2AB8C(SSDT RVA) 를 더하면 0x80503B8C주소로 KeServiceDescriptorTable의 첫번째 멤버인 KiServiceTable(SSDT)의 주소인것을 확인 할 수 있으며, 다시 0x80503B8C 확인해보면 함수포인터주소로 세팅되어진 SSDT를 확인 할 수 있습니다.

 

[그림.29]

 

그리고 그림.27에서 메모리주소 0x15114BDE에서 작업한대로 현재 실행중인 악성코드의 .data section에 SSDT 테이블의 함수포인터들이 저장되어있는것을 확인 할 수 있습니다.

 

 [그림.30]

 

그리고 자기자신(_bot.exe)을 c:\windows\system32\mssrv32.exe 파일로 이름을 변경하여 시스템 디렉토리에 복사합니다.

그리고 CreateProcessA함수로 _bot.exe를 또다시 실행하게 됩니다.

 

[그림.31]

 

시스템 디렉토리에 생성된 mssrv32.exe파일과 현재 실행중인 코드와 같은지 MD5로 비교했고 결과는 동일한 파일임이 밝혀졌습니다.

이제 그림.30에서 CreateProcessA함수로 자기 자신을 또다시 실행하게 됩니다.

이때 실행되는 코드는 지금까지 실행됬던 코드 그대로 실행되는것이 아닙니다.

분석초반부 그림.7번에서 뮤텍스를 생성했었고 부모 프로세스인 _bot.exe가 뮤텍스를 갖고 있습니다.

이상태에서 자식프로세스로 자기자신을 생성했을시에는 그림.7번과 같이 0x0B7(183) ERROR_ALREADY_EXISTS 에러코드가 발생을 하고 분기문을 무시하고 순차적으로 코드가 실행이 됩니다.

간단하게 부모프로세스가 뮤텍스를 갖고있고 자식프로세스가 동일한 이름의 뮤텍스를 생성하려고 시도하자 0x0B7(183) ERROR_ALREADY_EXISTS 에러가 발생하게되어 분기의 흐름을 변경시키는 것입니다.

그럼 여기서 CreateProcess로 실행되는 코드분석은 어떻게 해야할까요?

코드를 수정해서 서스펜드 시킨 후 분석해도 되지만 뮤텍스로 코드의 흐름을 제어하는것을 알았기 때문에 그냥 현재의 디버거창은 그대로 둔 상태에서 새로운 Ollydbg로 _bot.exe파일을 올려서 분석을 진행하면 이어서 분석을 할 수가 있습니다.