0. Windows 11 에서 정상적인 실행이 되지 않으므로, hyper-V 등을 통해서 windows 10에서 실행하는 것을 권장한다.

1. 다운로드 : Python3, Visual studio 2022, DynamoRIO(https://github.com/DynamoRIO/dynamorio/releases)

2. 적절한 경로에 DynamoRIO 압축 해제

3. config.h 에서 input 파일 최대 사이즈 수정  (1*1024*1024 를 아래와 같이 수정)

/* Maximum size of input file, in bytes (keep under 100MB): */

#define MAX_FILE            (100 * 1024 * 1024)

4. x64 Native Tools Command Prompt for VS 2022 열기

git clone https://github.com/googleprojectzero/winafl
cd winafl
git submodule update --init --recursive
mkdir build64
cd build64
cmake -G"Visual Studio 17 2022" -A x64 .. -DDynamoRIO_DIR=I:\DynamoRIO\cmake -DUSE_DRSYMS=1 -DINTELPT=1 -DUSE_COLOR=1
cmake --build . --config Release

5. harness 작성

#include <iostream>
#include <windows.h>

typedef int(__stdcall* _OHMYGOD)(const char* data); 
_OHMYGOD func;

extern "C" __declspec(dllexport) __declspec(noinline) int fuzzme(const char* path)
{   
    int result = func(path);
    return result;
}

int main(int argc, char *argv[])
{    
    HMODULE hMod = GetModuleHandle(0);
   
    hMod = LoadLibrary(L"I:\\victim\\x64\\Release\\victim.dll");
    if (NULL == hMod)
    {
        printf("dll load error\n");
        return 0;
    }
   
    func = (_OHMYGOD)GetProcAddress(hMod, "ohmygod");
    fuzzme(argv[1]);    
}

6. victim 작성 (타겟 DLL)

#include "pch.h"
#include <iostream>

extern "C" __declspec(dllexport) int ohmygod(const char* path)
{
    std::cout << path << std::endl;
    char data[40] = { 0, };
    char buf[30] = { 0, };
    
    HANDLE hFile = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile)
    {
        DWORD dwRead;
       
        ReadFile(hFile, data, sizeof(data), &dwRead, NULL);
        std::cout << "read : " << dwRead << std::endl;
        if (dwRead)
        {
            memcpy(buf, data, dwRead+40);
            std::cout << buf;
        }
        CloseHandle(hFile);
    }
    return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

원활한 crash 발생을 위해 각각 Release 로 빌드한다.

7. 입출력 디렉토리 설정

afl은 사용자가 지정한 입력 디렉토리에 있는 파일들을 퍼징 데이터로 사용한다. 

crash를 유발하지 않는 정상 입력 데이터이어야 첫 실행 시 에러가 발생하지 않는다.

아래 데이터를 1.txt로 저장한다.

abcd

8. 공격

afl-fuzz.exe -D I:\\dynamorio\\bin64  -i "d:\\fuzz_input" -o "d:\\fuzz_output" -t 1000 -- -coverage_module victim.dll -target_module harness.exe -target_method fuzzme -fuzz_iterations 10 -nargs 1 -- I:\\harness\\x64\\Release\\harness.exe @@

실행은 반드시 afl-fuzz.exe 디렉토리에서 하여야 한다. 그렇지 않다면 아래와 같은 에러가 발생한다.

주의 : -coverage_module 및 -target_module 에는 파일명만 기입한다.

아래와 같은 화면이 나오면 실행 성공이다.

crash가 발생할 경우, i:\fuzz_output\crashes 경로에 상세 정보가 저장된다.

만약 CPU가 인텔인 경우, -D 옵션 대신 -P옵션을 사용하도록 하자. 속도가 압도적으로 빠르다. (내 PC에서는 5배 이상 차이가 났다.)

afl-fuzz.exe -P -i "d:\\fuzz_input" -o "d:\\fuzz_output" -t 1000 -- -coverage_module victim.dll -target_module harness.exe -target_method fuzzme -fuzz_iterations 10 -nargs 1 -- I:\\harness\\x64\\Release\\harness.exe @@

 

x64 Native Tools Command Prompt for VS 2022 프롬프트 실행

git clone https://github.com/curl/curl.git
cd curl
buildconf.bat
Set RTLIBCFG=static
cd winbuild
nmake /f MakeFile.vc mode=static vc=17

빌드는 아래 경로에 생성 됨

curl\builds\libcurl-vc17-x64-release-static-ipv6-sspi-schannel

curl.zip
1.25MB

프로젝트 설정

#include <curl.h>
int main()
{
    CURL* curl = curl_easy_init();
    CURLcode res;
}

 

'Windows > Dev' 카테고리의 다른 글

[VC++] string deallocate  (0) 2022.05.23
get EIP (gcc / vc)  (0) 2019.04.11
C용 초경량 XML 파서 : Mini-XML 소개 및 사용법  (0) 2017.06.29
x86 __usercall 함수 후킹하기  (0) 2017.05.11
[C] 컴파일러 별 자료형 크기 비교  (0) 2017.03.14

deallocate시 문자열 길이 단위는 0x10이며, 문자열 길이가 0x10이하면 _Count 에는 1이 들어가고, _Deallocate를 호출하면서 0x10 * 1하여 0x10 사이즈로 deallocate를 하게 된다.

예시)  string abc = "this is";

_Ptr은 이중 포인터이며, &abc의 주소를 담고 있다.

길이가 0x10을 초과하는 경우 param에 블록단위(0x10의 배수)가 들어간다.

예시) string abc = "live a live is not fun"; // 22바이트

 

긴 사이즈에서 짧은 사이즈로 리사이즈 하는 경우, 최초에 할당한 길이로 deallocate가 한 번만 수행된다.

string abc = "live a live is not fun";
abc = "papa";

vc++에서는 문자열의 길이를 deallocate의 인자로 확인할 수 없으므로, deallocate 재정의를 통한 memory wipe는 수행할 수 없다.

namespace secure
{
    template <class T> class allocator : public std::allocator<T>
    {
    public:

        template<class U> struct rebind { typedef allocator<U> other; };
        allocator() throw() {}
        allocator(const allocator&) throw() {}
        template <class U> allocator(const allocator<U>&) throw() {}

        void deallocate(T* const p, size_t _Count)
        {            
            memset((void*)p, 0, _Count); // _Count가 문자열 길이를 뜻하는 것이 아니다                    
            std::allocator<T>::deallocate(p, _Count);
        }
    };

    typedef std::basic_string<char, std::char_traits<char>, allocator<char> > string;
}

 

테스트 환경 : windows 10 x64 build 19041

이 방식은 DRIVER_OBJECT -> OBJECT_HEADER_NAME_INFO ->  OBJECT_DIRECTORY -> OBJECT_DIRECTORY_ENTRY 순서대로 구조체를 얻어온 뒤 ENTRY 체인을 끊어 목록에서 제거하는 방식입니다.

이를 위해 먼저 최상위 OBJECT_DIRECTORY 부터 살펴봅니다.

lkd> !object ffffcb83e84023c0
Object: ffffcb83e84023c0  Type: (ffffdc8a4aa9c4e0) Directory
    ObjectHeader: ffffcb83e8402390 (new version)
    HandleCount: 0  PointerCount: 58
    Directory Object: 00000000  Name: \

    Hash Address          Type                      Name
    ---- -------          ----                      ----
     01  ffffdc8a4ccfe450 Mutant                    PendingRenameMutex
         ffffcb83e8451ad0 Directory                 ObjectTypes
     02  ffffdc8a4f6ec6e0 FilterConnectionPort      storqosfltport
     04  ffffdc8a55ba1c20 FilterConnectionPort      TsFltScanPort_V3LITE40
     05  ffffdc8a55ba1670 FilterConnectionPort      TsFltCheckPort_V3LITE40
         ffffcb83e84b6520 SymbolicLink              SystemRoot
     06  ffffcb83e8cfba20 Directory                 Sessions
     08  ffffcb83e848fb80 Directory                 ArcName
     09  ffffdc8a4f6ec9a0 FilterConnectionPort      WcifsPort
         ffffcb83e84ec060 Directory                 NLS
     10  ffffdc8a4fa27760 Event                     LanmanServerAnnounceEvent
         ffffdc8a4f86baa0 ALPC Port                 ThemeApiPort
         ffffdc8a4ce59d40 Device                    UdfsCdRom
         ffffcb83e8ba5380 Directory                 Windows
         ffffcb83e8467c50 Directory                 GLOBAL??
     11  ffffcb83e8cfb2a0 Directory                 RPC Control
         ffffdc8a4c3596a0 ALPC Port                 PdcPort
     13  ffffdc8a55b87400 FilterConnectionPort      MEDSRVPOSTPORT_V3LITE40_2.13.1.63
         ffffdc8a4cd7a4e0 Event                     EFSInitEvent
     14  ffffcb83e8c68930 SymbolicLink              Dfs
         ffffdc8a4c53c870 Device                    clfs
     15  ffffdc8a4c22dc90 Event                     CsrSbSyncEvent
         ffffdc8a4c62a790 ALPC Port                 SeRmCommandPort
     16  ffffcb83e8445830 SymbolicLink              DosDevices
     17  ffffdc8a55b92450 FilterConnectionPort      MEDSRVPENDINGPORT_V3LITE40_2.13.1.63
         ffffcb83e8cfbc00 Directory                 KnownDlls32
     18  ffffdc8a55b88fa0 FilterConnectionPort      MEDSRVSENDPORT_V3LITE40_2.13.1.63
         ffffcb83e849ad20 Key                       \REGISTRY
     19  ffffcb83e8836c00 Directory                 BaseNamedObjects
     20  ffffcb83e8ec49d0 Section                   Win32kCrossSessionGlobals
         ffffdc8a4ab13df0 ALPC Port                 PowerPort
     21  ffffdc8a4f412a10 ALPC Port                 SmSsWinStationApiPort
         ffffdc8a4c22b7b0 Event                     UniqueInteractiveSessionIdEvent
         ffffcb83e84ec920 Directory                 UMDFCommunicationPorts
     22  ffffcb83e8cfb840 Directory                 KnownDlls
         ffffdc8a4cc52d40 Device                    FatCdrom
         ffffdc8a4cc53d40 Device                    Fat
         ffffdc8a4ab46df0 ALPC Port                 PowerMonitorPort
     23  ffffdc8a4c626b50 Device                    Ntfs
         ffffcb83e84ec3e0 Directory                 FileSystem
         ffffcb83e8441cc0 Directory                 KernelObjects
     26  ffffdc8a4ebebd40 ALPC Port                 SeLsaCommandPort
         ffffcb83e8451ca0 Directory                 Callback
     28  ffffdc8a4f6ec580 FilterConnectionPort      BindFltPort
         ffffdc8a4c23dc30 Event                     DSYSDBG.Debug.Trace.Memory.2bc
         ffffcb83e84588d0 Directory                 Security
     29  ffffdc8a4ce31e00 Device                    UdfsDisk
     30  ffffcb83e8490060 Directory                 Device
     32  ffffcb83e84f8190 SymbolicLink              DriverData
     34  ffffcb83ebcfe610 Section                   LsaPerformance
         ffffdc8a4c8aec70 ALPC Port                 SmApiPort
     35  ffffdc8a4f6ec4d0 FilterConnectionPort      CLDMSGPORT
         ffffcb83e84b66d0 SymbolicLink              OSDataRoot
     36  ffffdc8a4c23e9f0 Event                     SAM_SERVICE_STARTED
         ffffcb83e84ec220 Directory                 Driver
         ffffcb83e8489820 Directory                 DriverStores

저의 windows의 windbg 화면으로, OBJECT_DIRECTORY 구조체는 타입과 이름을 가지고 있습니다. 최상위 구조체라서 타입은 HANDLE을 가지고 있습니다.

우리에게 필요한 것은 드라이버 목록인데, 이는 각각 23번, 36번 인덱스 2번째에 속해 있습니다. (FileSystem, Driver) 

일반적으로 드라이버를 로딩하면 Driver의 OBJECT_DIRECTORY에 연결되므로, 36번 인덱스의 Driver만 보도록 하겠습니다. 

위 주소를 다시 구조체로 표현해봅니다.

lkd> dt !_OBJECT_DIRECTORY  ffffcb83e84023c0
nt!_OBJECT_DIRECTORY
   +0x000 HashBuckets      : [37] (null) 
   +0x128 Lock             : _EX_PUSH_LOCK
   +0x130 DeviceMap        : (null) 
   +0x138 ShadowDirectory  : (null) 
   +0x140 NamespaceEntry   : (null) 
   +0x148 SessionObject    : (null) 
   +0x150 Flags            : 0
   +0x154 SessionId        : 0xffffffff

HashBucket은 37개의 배열로 구성되어 있으며, 각각 OBJECT_DIRECTORY_ENTRY로 표현됩니다. 각 OBJECT_DIRECTORY_ENTRY체인으로 연결되어 있습니다. 

운영체제는 각각의 Name에서 Hash Index 를 계산해 각 배열에 삽입하는 방식입니다. 따라서 연결된 Entry의 갯수는 일정하지 않습니다. 

보시면 00, 03번 인덱스는 존재하지 않고, 10번의 경우 5개의 오브젝트가 존재하는데 Name에서 동일한 Hash Index가 계산된 결과입니다. (Hash인덱스를 어떻게 구하는지는 모르겠습니다. 만약 알게되면 기술하도록 하겠습니다.)

HashBucket을 눌러보면 아래와 같이 나옵니다.

lkd> dx -id 0,0,ffffdc8a4aa96040 -r1 (*((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY * (*)[37])0xffffcb83e84023c0))
(*((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY * (*)[37])0xffffcb83e84023c0))                 [Type: _OBJECT_DIRECTORY_ENTRY * [37]]
    [0]              : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [1]              : 0xffffcb83e8ca8b10 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [2]              : 0xffffcb83ec36c2e0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [3]              : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [4]              : 0xffffcb83f4a17c00 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [5]              : 0xffffcb83f4a17b70 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [6]              : 0xffffcb83e8caca10 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [7]              : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [8]              : 0xffffcb83e84b5060 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [9]              : 0xffffcb83ebf53e70 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [10]             : 0xffffcb83ec371200 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [11]             : 0xffffcb83e8ca8d20 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [12]             : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [13]             : 0xffffcb83ece2c4c0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [14]             : 0xffffcb83e8b80690 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [15]             : 0xffffcb83e8caec90 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [16]             : 0xffffcb83e84af0c0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [17]             : 0xffffcb83ece2c430 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [18]             : 0xffffcb83ece2c400 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [19]             : 0xffffcb83e854c2e0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [20]             : 0xffffcb83e85439d0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [21]             : 0xffffcb83e8845930 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [22]             : 0xffffcb83e8ca8d80 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [23]             : 0xffffcb83e855f600 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [24]             : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [25]             : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [26]             : 0xffffcb83e8841160 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [27]             : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [28]             : 0xffffcb83ec36cca0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [29]             : 0xffffcb83e8cad460 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [30]             : 0xffffcb83e84b5630 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [31]             : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [32]             : 0xffffcb83e8545a00 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [33]             : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [34]             : 0xffffcb83e8841250 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [35]             : 0xffffcb83ec36c430 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [36]             : 0xffffcb83e8842420 [Type: _OBJECT_DIRECTORY_ENTRY *]

36번 인덱스의 주소를 구조체로 확인해봅니다.

lkd> dt _OBJECT_DIRECTORY_ENTRY 0xffffcb83e8842420
nt!_OBJECT_DIRECTORY_ENTRY
   +0x000 ChainLink        : 0xffffcb83`e8568920 _OBJECT_DIRECTORY_ENTRY
   +0x008 Object           : 0xffffdc8a`4c23e9f0 Void
   +0x010 HashValue        : 0x9b5074d

2번째에 위치해있으니 ChainLink를 따라가봅니다.

lkd> dt _OBJECT_DIRECTORY_ENTRY 0xffffcb83e8568920
nt!_OBJECT_DIRECTORY_ENTRY
   +0x000 ChainLink        : 0xffffcb83`e84b5b10 _OBJECT_DIRECTORY_ENTRY
   +0x008 Object           : 0xffffcb83`e84ec220 Void
   +0x010 HashValue        : 0x80a0801

해당 OBJECT_DIRECTORY_ENTRY에 0xffffcb83e84ec220 오브젝트가 있음을 확인할 수 있습니다. (!object 한 것과 값을 비교해보세요) 이 주소는 OBJECT_DIRECTORY 구조체로 표현됩니다.(그래서 Type명이 Directory입니다.) 이를 다시 !object로 확인해봅니다.

lkd> !object 0xffffcb83e84ec220
Object: ffffcb83e84ec220  Type: (ffffdc8a4aa9c4e0) Directory
    ObjectHeader: ffffcb83e84ec1f0 (new version)
    HandleCount: 0  PointerCount: 124
    Directory Object: ffffcb83e84023c0  Name: Driver

    Hash Address          Type                      Name
    ---- -------          ----                      ----
     00  ffffdc8a50e1eb20 Driver                    kldbgdrv
         ffffdc8a4c9a2e20 Driver                    fvevol
         ffffdc8a4c356d70 Driver                    vdrvroot
     01  ffffdc8a4cd2ee00 Driver                    usbuhci
         ffffdc8a4cc9dd40 Driver                    GpuEnergyDrv
         ffffdc8a4ca4b9e0 Driver                    NetBT
         ffffdc8a4aa3fd00 Driver                    acpiex
         ffffdc8a4aa8bde0 Driver                    Wdf01000
     02  ffffdc8a4faabb70 Driver                    mpsdrv
         ffffdc8a4c53a060 Driver                    storahci
     03  ffffdc8a51c56540 Driver                    BthEnum
         ffffdc8a4fd73e20 Driver                    MMCSS
         ffffdc8a4f924e30 Driver                    lltdio
         ffffdc8a4cbdeb40 Driver                    bam
         ffffdc8a4cc63d80 Driver                    Psched
         ffffdc8a4cb8ae40 Driver                    BasicRender
         ffffdc8a4c9c28f0 Driver                    disk
     04  ffffdc8a4facadf0 Driver                    HTTP
         ffffdc8a4c382d80 Driver                    stornvme
     05  ffffdc8a4cdbe5b0 Driver                    e1i65x64
         ffffdc8a4cb9acb0 Driver                    WscVReg
     06  ffffdc8a4f641a70 Driver                    monitor
         ffffdc8a4cdb7b50 Driver                    usbehci
         ffffdc8a4cb9a6c0 Driver                    ahcache
         ffffdc8a4cafae40 Driver                    vmrawdsk
         ffffdc8a4c9c1dc0 Driver                    iorate
         ffffdc8a4c53b640 Driver                    pcw
     07  ffffdc8a4fcd4df0 Driver                    AMonCDW8
         ffffdc8a4cde8060 Driver                    Ucx01000
         ffffdc8a4cde7060 Driver                    USBXHCI
         ffffdc8a4c359dc0 Driver                    partmgr
     08  ffffdc8a52b98e50 Driver                    AntiStealth_V3LITE40
         ffffdc8a4fd83a10 Driver                    PEAUTH
         ffffdc8a4ab88e40 Driver                    MsLldp
         ffffdc8a4cc6fa30 Driver                    Vid
         ffffdc8a4ab0cd80 Driver                    ACPI_HAL
     09  ffffdc8a4c372850 Driver                    spaceport
     10  ffffdc8a4cf1ae00 Driver                    HidUsb
         ffffdc8a4cc62dd0 Driver                    vwififlt
     11  ffffdc8a50ea0e10 Driver                    condrv
         ffffdc8a4cb9be40 Driver                    DXGKrnl
         ffffdc8a4ab35e20 Driver                    PnpManager
     12  ffffdc8a4c9c9740 Driver                    Null
         ffffdc8a4c35ca80 Driver                    vsock
         ffffdc8a4ab3be20 Driver                    intelpep
     13  ffffdc8a506c4e30 Driver                    MeDCoreD_V3LITE40
         ffffdc8a4aa81e20 Driver                    Telemetry
         ffffdc8a4aae6de0 Driver                    SoftwareDevice
     14  ffffdc8a52e98a70 Driver                    RFCOMM
         ffffdc8a4ca57e10 Driver                    Serenum
         ffffdc8a4c8ae0a0 Driver                    CLFS
         ffffdc8a4ab28e20 Driver                    WindowsTrustedRTProxy
     15  ffffdc8a4ccdae20 Driver                    Serial
         ffffdc8a4cc59d40 Driver                    NdisCap
         ffffdc8a4c8b3d30 Driver                    KSecDD
         ffffdc8a4c35bda0 Driver                    volmgr
         ffffdc8a4aaced80 Driver                    DeviceApi
     16  ffffdc8a4fcd3c90 Driver                    VMMemCtl
         ffffdc8a4ccd6e20 Driver                    umbus
         ffffdc8a4aad9c10 Driver                    MsSecFlt
         ffffdc8a4abd8cc0 Driver                    CNG
     17  ffffdc8a4ce72e20 Driver                    Win32k
         ffffdc8a4cce0dc0 Driver                    i8042prt
         ffffdc8a4cc96d70 Driver                    npsvctrig
         ffffdc8a4c9bed90 Driver                    volume
         ffffdc8a4c8b6e00 Driver                    KSecPkg
     18  ffffdc8a4cca25b0 Driver                    mouclass
     19  ffffdc8a4ea59e00 Driver                    X58a84jzGG36Q
         ffffdc8a4c53d350 Driver                    msisadrv
     20  ffffdc8a4fd79e10 Driver                    Ndu
         ffffdc8a4ccdddb0 Driver                    kbdclass
     21  ffffdc8a4c8b8e00 Driver                    mouhid
         ffffdc8a4c9bf8f0 Driver                    volsnap
     22  ffffdc8a567efe30 Driver                    MeDVpDrv_V3LITE40
         ffffdc8a4cc71dd0 Driver                    nsiproxy
         ffffdc8a4c944bd0 Driver                    AhnRghNt
         ffffdc8a4aaddd80 Driver                    WMIxWDM
     23  ffffdc8a528188f0 Driver                    BthPan
         ffffdc8a4fac3e30 Driver                    MsQuic
         ffffdc8a4cb6f9a0 Driver                    tdx
         ffffdc8a4c360a90 Driver                    vmci
     24  ffffdc8a5102ae30 Driver                    AntiStealth_V3LITE40F
         ffffdc8a52f8b570 Driver                    BTHUSB
         ffffdc8a4ab32e20 Driver                    WindowsTrustedRT
     25  ffffdc8a4cd2fde0 Driver                    HDAudBus
         ffffdc8a4cbbde40 Driver                    BasicDisplay
     26  ffffdc8a4cd62e00 Driver                    rdpbus
     27  ffffdc8a50eaca70 Driver                    Cdm2DrNt
         ffffdc8a4fcd3a70 Driver                    DBGV
         ffffdc8a4c358dc0 Driver                    pdc
     28  ffffdc8a4f92de20 Driver                    rspndr
         ffffdc8a4e14fe00 Driver                    vmusbmouse
     29  ffffdc8a4ce59670 Driver                    HdAudAddService
         ffffdc8a4cc98d60 Driver                    mssmbios
         ffffdc8a4cc97de0 Driver                    CSC
         ffffdc8a4c372d90 Driver                    volmgrx
         ffffdc8a4c536060 Driver                    pci
     30  ffffdc8a545d5e50 Driver                    ArtDrv
         ffffdc8a4cd67ce0 Driver                    NdisVirtualBus
         ffffdc8a4cdeb300 Driver                    CmBatt
         ffffdc8a4cce1e00 Driver                    vm3dmp_loader
         ffffdc8a4ccd8e20 Driver                    kdnic
         ffffdc8a4c9c1ba0 Driver                    cdrom
         ffffdc8a4c8b54e0 Driver                    NDIS
     31  ffffdc8a4cd65dc0 Driver                    swenum
     32  ffffdc8a4ce6ee30 Driver                    usbhub
         ffffdc8a4c9bfb00 Driver                    rdyboost
         ffffdc8a4c997e30 Driver                    WFPLWFS
         ffffdc8a4c8b7e00 Driver                    Tcpip
         ffffdc8a4aad9e20 Driver                    SgrmAgent
     33  ffffdc8a543a7750 Driver                    TSFltDrv_V3LITE40
         ffffdc8a4ce66cb0 Driver                    USBHUB3
         ffffdc8a4cdec940 Driver                    intelppm
         ffffdc8a4cd2fb20 Driver                    gencounter
         ffffdc8a4c162e40 Driver                    Beep
         ffffdc8a4c374410 Driver                    atapi
     34  ffffdc8a4cf18e00 Driver                    usbccgp
         ffffdc8a4cb6b9a0 Driver                    AFD
         ffffdc8a4c373c40 Driver                    mountmgr
         ffffdc8a4c365e30 Driver                    intelide
     35  ffffdc8a4e156410 Driver                    ATamptNt_V3LITE40
         ffffdc8a52bb1b70 Driver                    BTHPORT
         ffffdc8a4fd83e30 Driver                    tcpipreg
         ffffdc8a4ce32060 Driver                    ksthunk
         ffffdc8a4ccd9e30 Driver                    vmmouse
         ffffdc8a4cb6c9a0 Driver                    afunix
     36  ffffdc8a55296750 Driver                    asc_kbc_V3LITE40
         ffffdc8a4ca547b0 Driver                    vm3dmp
         ffffdc8a4cca4db0 Driver                    CompositeBus
         ffffdc8a4caf99a0 Driver                    ws2ifsl
         ffffdc8a4c64edd0 Driver                    EhStorClass
         ffffdc8a4aa954c0 Driver                    ACPI

로딩된 드라이버 목록이 나오는 것을 확인할 수 있습니다. 

위에서 언급했듯이, 각각의 Entry(1줄당 하나의 Entry, 여기서는 드라이버를 의미)는 체인으로 연결되어 있습니다. 아래 그림처럼 특정 Entry를 제거한 후 체인을 이을 수 있습니다.

33번 인덱스의 2번째인 USBHUB3를 목록에서 숨겨본다고 가정하겠습니다. 

우선 위 주소를 구조체로 표현해봅니다.

lkd> dt _OBJECT_DIRECTORY 0xffffcb83e84ec220
nt!_OBJECT_DIRECTORY
   +0x000 HashBuckets      : [37] 0xffffcb83`f16ee960 _OBJECT_DIRECTORY_ENTRY
   +0x128 Lock             : _EX_PUSH_LOCK
   +0x130 DeviceMap        : (null) 
   +0x138 ShadowDirectory  : (null) 
   +0x140 NamespaceEntry   : (null) 
   +0x148 SessionObject    : (null) 
   +0x150 Flags            : 0
   +0x154 SessionId        : 0xffffffff

HashBucket을 눌러보면 아래와 같이 나옵니다.

lkd> dx -id 0,0,ffffdc8a4aa96040 -r1 (*((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY * (*)[37])0xffffcb83e84ec220))
(*((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY * (*)[37])0xffffcb83e84ec220))                 [Type: _OBJECT_DIRECTORY_ENTRY * [37]]
    [0]              : 0xffffcb83f16ee960 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [1]              : 0xffffcb83e8ca9a10 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [2]              : 0xffffcb83ec3710b0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [3]              : 0xffffcb83f2246b20 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [4]              : 0xffffcb83ec370900 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [5]              : 0xffffcb83e8ca9e30 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [6]              : 0xffffcb83f66bb380 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [7]              : 0xffffcb83edd0bf70 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [8]              : 0xffffcb83edd081f0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [9]              : 0xffffcb83e8545490 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [10]             : 0xffffcb83e8568860 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [11]             : 0xffffcb83efa5efd0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [12]             : 0xffffcb83e854c820 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [13]             : 0xffffcb83edd05790 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [14]             : 0xffffcb83f2246ac0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [15]             : 0xffffcb83e8ca8c30 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [16]             : 0xffffcb83ec373fc0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [17]             : 0xffffcb83e8b809c0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [18]             : 0xffffcb83e8ca8a50 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [19]             : 0xffffcb83f2245590 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [20]             : 0xffffcb83ece2d4b0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [21]             : 0xffffcb83e8cae4e0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [22]             : 0xffffcb83edd05c10 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [23]             : 0xffffcb83f2246eb0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [24]             : 0xffffcb83edd08c70 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [25]             : 0xffffcb83e8ca9a40 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [26]             : 0xffffcb83e8cab330 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [27]             : 0xffffcb83edd08be0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [28]             : 0xffffcb83ec36f310 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [29]             : 0xffffcb83e8cad760 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [30]             : 0xffffcb83edd0cb10 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [31]             : 0xffffcb83e8cab180 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [32]             : 0xffffcb83e8cacda0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [33]             : 0xffffcb83edd0ced0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [34]             : 0xffffcb83e85686b0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [35]             : 0xffffcb83edd087c0 [Type: _OBJECT_DIRECTORY_ENTRY *]
    [36]             : 0xffffcb83edd0cf30 [Type: _OBJECT_DIRECTORY_ENTRY *]

33번 인덱스의 주소를 구조체로 확인해봅니다.

lkd>  dt _OBJECT_DIRECTORY_ENTRY 0xffffcb83edd0ced0
nt!_OBJECT_DIRECTORY_ENTRY
   +0x000 ChainLink        : 0xffffcb83`e8cadac0 _OBJECT_DIRECTORY_ENTRY
   +0x008 Object           : 0xffffdc8a`543a7750 Void // TSFltDrv_V3LITE40 오브젝트
   +0x010 HashValue        : 0x602bada7

2번째에 위치해있으니 ChainLink주소를 따라가봅니다.

lkd> dt _OBJECT_DIRECTORY_ENTRY 0xffffcb83`e8cadac0
nt!_OBJECT_DIRECTORY_ENTRY
   +0x000 ChainLink        : 0xffffcb83`e8caa730 _OBJECT_DIRECTORY_ENTRY
   +0x008 Object           : 0xffffdc8a`4ce66cb0 Void // USBHUB3 오브젝트
   +0x010 HashValue        : 0x19f5be6d

해당 OBJECT_DIRECTORY_ENTRY에 USBHUB3인 0xffffdc8a4ce66cb0 오브젝트에 도달한 것을 확인할 수 있습니다. 여기서 이전 Entry의 ChainLink를 USBHUB3 Entry가 가리키는 ChainLink로 변경하면 됩니다. 

이제 중요한 것은 해당 OBJECT_DIRECTORY_ENTRY에 어떻게 도달하느냐인데, DriverEntry함수의 PDRIVER_OBJECT는 Directory 타입의 "Driver"에 속해있는 유형입니다. "Driver"에 속해있는 드라이버라면 OBJECT_DIRECTORY(일종의 부모)가 동일합니다. 따라서 타 드라이버의 DRIVER_OBJECT 등을 구할 필요없이 자신의 드라이버 모듈을 Head로 사용하면 됩니다.

"Driver" 의 OBJECT_DIRECTORY에 접근하기 위해서 우선 ObQueryNameInfo함수로 OBJECT_HEADER_NAME_INFO 구조체 주소를 구해야 합니다. 해당 구조체에 OBJECT_DIRECTORY를 가리키는 필드가 있습니다.

POBJECT_HEADER_NAME_INFO pObjectHeaderNameInfo = ObQueryNameInfo(pDriverObject);
POBJECT_DIRECTORY pObjectDirectory = pObjectHeaderNameInfo->Directory;

OBJECT_DIRECTORY를 구했으면 각각의 ENTRY를 순회한 뒤 이름이 일치하면 ChainLink를 변경하는 코드를 작성합니다.

UNICODE_STRING DriverName = RTL_CONSTANT_STRING(L"USBHUB3");
for (int i = 0; i < 37; i++)
{
	POBJECT_DIRECTORY_ENTRY pBlinkObjectDirectoryEntry = NULL; // 이전 노드 저장
	POBJECT_DIRECTORY_ENTRY pObjectDirectoryEntry = pObjectDirectory->HashBuckets[i];

	while (pObjectDirectoryEntry)
	{        
		// 일치하면
		if (RtlEqualUnicodeString(DriverName, &pObjectHeaderNameInfo->Name, TRUE))
		{
			// 체인의 0번째(=헤드)가 자신의 Entry일때
			if (pObjectDirectory->HashBuckets[p] == pObjectDirectoryEntry)
			{			
				pObjectDirectory->HashBuckets[p] = pObjectDirectoryEntry->ChainLink; // 내 Entry의 다음 Entry를 헤드로 만든다.
				pObjectDirectoryEntry->ChainLink = NULL;
			}
			else // 1번째부터
			{			
				pBlinkObjectDirectoryEntry->ChainLink = pObjectDirectoryEntry->ChainLink; // 이전 Entry의 다음 Entry를 내 다음 Entry로 가리킨다.
				pObjectDirectoryEntry->ChainLink = NULL;
			}
		}
	        pBlinkObjectDirectoryEntry = pObjectDirectoryEntry;
		pObjectDirectoryEntry = pObjectDirectoryEntry->ChainLink;
	}
}

코드를 실행하여 체인 연결을 끊은 후 windbg로 확인해보면 TSFltDrv_V3LITE40의 ChainLink가 USBHUB3가 아닌 intelppm을 가리키는 것을 확인할 수 있습니다.

    33  ffffdc8a543a7750 Driver                    TSFltDrv_V3LITE40
        ffffdc8a4cdec940 Driver                    intelppm
        ffffdc8a4cd2fb20 Driver                    gencounter
        ffffdc8a4c162e40 Driver                    Beep
        ffffdc8a4c374410 Driver                    atapi
lkd> dt _OBJECT_DIRECTORY_ENTRY 0xffffcb83edd0ced0 // TSFltDrv_V3LITE40 엔트리
nt!_OBJECT_DIRECTORY_ENTRY
   +0x000 ChainLink        : 0xffffcb83`e8caa730 _OBJECT_DIRECTORY_ENTRY // intelpm 엔트리
   +0x008 Object           : 0xffffdc8a`543a7750 Void // TSFltDrv_V3LITE40 오브젝트
   +0x010 HashValue        : 0x602bada7
lkd> dt _OBJECT_DIRECTORY_ENTRY 0xffffcb83`e8cadac0
nt!_OBJECT_DIRECTORY_ENTRY
   +0x000 ChainLink        : (null) // 체인이 끊어짐
   +0x008 Object           : 0xffffdc8a`4ce66cb0 Void // USBHUB3 오브젝트
   +0x010 HashValue        : 0x19f5be6d
lkd> dt _OBJECT_DIRECTORY_ENTRY 0xffffcb83e8caa730 // intelppm 엔트리
nt!_OBJECT_DIRECTORY_ENTRY
   +0x000 ChainLink        : 0xffffcb83`e8caaa30 _OBJECT_DIRECTORY_ENTRY
   +0x008 Object           : 0xffffdc8a`4cdec940 Void // intelppm 오브젝트
   +0x010 HashValue        : 0x29f82c1

 

테스트환경 : windows 10 x64 build 19041

OBJECT_HEADER에서 OBJECT_HEADER_NAME_INFO를 어떻게 얻을까 다양한 방법으로 구글링해보다가 ObQueryNameInfo(PVOID pObject) 함수가 있다는 것을 발견했습니다. 이 함수를 쓰면 POBJECT_HEADER_NAME_INFO 주소를 리턴하더군요. 어떻게 얻나 궁금해서 windbg로 까봤습니다.

0: kd> u ObQueryNameInfo
nt!ObQueryNameInfo:
fffff803`6987b920 0fb641ea        movzx   eax,byte ptr [rcx-16h]
fffff803`6987b924 4883c1d0        add     rcx,0FFFFFFFFFFFFFFD0h
fffff803`6987b928 a802            test    al,2
fffff803`6987b92a 7416            je      nt!ObQueryNameInfo+0x22 (fffff803`6987b942)
fffff803`6987b92c 83e003          and     eax,3
fffff803`6987b92f 488d154aa55a00  lea     rdx,[nt!ObpInfoMaskToOffset (fffff803`69e25e80)]
fffff803`6987b936 0fb60410        movzx   eax,byte ptr [rax+rdx]
fffff803`6987b93a 482bc8          sub     rcx,rax
fffff803`6987b93d 488bc1          mov     rax,rcx
fffff803`6987b940 c3              ret

먼저 인자에서 0x16을 뺀것을 포인터 참조해서 저장하고, 그 다음은 0x30만큼 빼고 주소를 저장하네요. (인자로는 DRIVER_OBJECT 0xffffc28f1318ce30 를 줘봤습니다. )

일단 0x30만큼 뺀 것은 OBJECT_HADER 구조체였습니다. (대충 감으로 때려맞춤...)

kd> dt _OBJECT_HEADER 0xffffc28f1318ce30-0x30
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n3
   +0x008 HandleCount      : 0n0
   +0x008 NextToFree       : (null) 
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x13 ''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0x2 ''
   +0x01b Flags            : 0x12 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y1
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y1
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0xffffc28f
   +0x020 ObjectCreateInfo : 0x00000000`00000001 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0x00000000`00000001 Void
   +0x028 SecurityDescriptor : 0xffffb20d`a6735cee Void
   +0x030 Body             : _QUAD

0x16만큼 뺀것은 +0x1A에 위치해 있는 InfoMask 필드네요. InfoMask & 2 가 참이면 InfoMask & 3을 하여 index로 사용합니다. (예제에서는 2 & 3 = 2(index))

그 다음으로 ObpInfoMaskToOffset 에서 index를 사용하여 오프셋을 꺼내옵니다.

(BYTE*)ObpInfoMaskToOffset(0xfffff803`69e25e80) + index(2) (예제는 fffff803`69e25e82가 되겠군요.)

2: kd> db fffff803`69e25e80
fffff803`69e25e80  00 20 20 40 10 30 30 50-20 40 40 60 30 50 50 70  .  @.00P @@`0PPp
fffff803`69e25e90  10 30 30 50 20 40 40 60-30 50 50 70 40 60 60 80  .00P @@`0PPp@``.
fffff803`69e25ea0  10 30 30 50 20 40 40 60-30 50 50 70 40 60 60 80  .00P @@`0PPp@``.
fffff803`69e25eb0  20 40 40 60 30 50 50 70-40 60 60 80 50 70 70 90   @@`0PPp@``.Ppp.
fffff803`69e25ec0  10 30 30 50 20 40 40 60-30 50 50 70 40 60 60 80  .00P @@`0PPp@``.
fffff803`69e25ed0  20 40 40 60 30 50 50 70-40 60 60 80 50 70 70 90   @@`0PPp@``.Ppp.
fffff803`69e25ee0  20 40 40 60 30 50 50 70-40 60 60 80 50 70 70 90   @@`0PPp@``.Ppp.
fffff803`69e25ef0  30 50 50 70 40 60 60 80-50 70 70 90 60 80 80 a0  0PPp@``.Ppp.`...

OBJECT_HEADER 주소에서 꺼내온 0x20(OBJECT_HEADER_NAME_INFO 구조체 사이즈)만큼 빼고 해당 주소를 리턴합니다.

예제는 0xffffc28f1318ce00 - 0x20 = 0xffffc28f1318cde0

kd> dt _OBJECT_HEADER_NAME_INFO 0xffffc28f1318cde0
nt!_OBJECT_HEADER_NAME_INFO
   +0x000 Directory        : 0xffffb20d`a66ec5a0 _OBJECT_DIRECTORY
   +0x008 Name             : _UNICODE_STRING "KernelV"
   +0x018 ReferenceCount   : 0n0
   +0x01c Reserved         : 0

잘 얻어졌습니다.

ObpInfoMaskToOffset는 0x100개의 배열이며, 직접 생성할 수도 있습니다. (아래의 소스에 자세히 나와있습니다.)

github.com/hfiref0x/WinObjEx64/blob/master/Source/WinObjEx64/kldbg.c

 

 

드라이버를 서비스 형태로 등록한 경우 DRIVER_OBJECT가 할당되며, 이 구조체로부터 드라이버 정보가 담긴 LDR_DATA_TABLE_ENTRY 구조체에 다다를 수 있다.

hidusb.sys를 샘플로 잡고 windbg에서 DRIVER_OBJECT 구조체를 살펴보면 0x28 오프셋에 DriverSection이 있는데 이는 LDR_DATA_TABLE_ENTRY 구조체를 가리킨다.

0x0 오프셋 구조체에 다음&이전 드라이버의 구조체 주소가 담겨있다.

다음&이전 구조체를 가리키는 Flink, Blink는 LDR_DATA_TABLE_ENTRY로 표현된다.

아래와 같이 Flink 값의 구조체를 살펴보면 다음 드라이버를 가리키는 것을 확인할 수 있다.

여기서, 숨기고자 하는 드라이버의 Flink와 Blink를 자신의 LDR_DATA_ENTRY를 가리키게 하고, 이전 드라이버 구조체의 Flink 를 다음 드라이버 구조체의 Blink와 이으면 LDR_DATA_TABLE_ENTRY 트레버싱이나 ZwQuerySystemInformation()의 드라이버 목록에서 사라지게 된다.

PLDR_DATA_TABLE_ENTRY PsLoadedModuleList;
void GetPsLoadedModuleList(PLDR_DATA_TABLE_ENTRY currentEntry)
{
	while (TRUE)
	{
		if (NULL == currentEntry->DllBase)
		{		
			PsLoadedModuleList = currentEntry;
			break;
		}
		currentEntry = (PLDR_DATA_TABLE_ENTRY)currentEntry->InLoadOrderLinks.Flink;
	}
}
void HideDriver(PCWSTR szFileName)
{
	PLDR_DATA_TABLE_ENTRY prevEntry, nextEntry, Entry;
	Entry = (PLDR_DATA_TABLE_ENTRY)PsLoadedModuleList->InLoadOrderLinks.Flink;
	
	while (Entry != PsLoadedModuleList)
	{
		if (0 == wcscmp((PCWSTR)Entry->BaseDllName.Buffer, szFileName))
		{
			prevEntry = (PLDR_DATA_TABLE_ENTRY)Entry->InLoadOrderLinks.Blink; // 이전 노드
			nextEntry = (PLDR_DATA_TABLE_ENTRY)Entry->InLoadOrderLinks.Flink; // 다음 노드
			prevEntry->InLoadOrderLinks.Flink = Entry->InLoadOrderLinks.Flink; // 이전 노드의 다음을 내 다음으로 바꿈
			nextEntry->InLoadOrderLinks.Blink = Entry->InLoadOrderLinks.Blink; // 다음 노드의 이전을 내 이전로 바꿈 
			// 내 노드의 앞, 뒤를 나 자신으로 바꿈
			// 바꾸지 않는다면 드라이버 서비스를 stop할때 BSOD발생 (KERNEL SECURITY CHECK FAILURE)
			Entry->InLoadOrderLinks.Flink = (PLIST_ENTRY)Entry;
			Entry->InLoadOrderLinks.Blink = (PLIST_ENTRY)Entry;
			break;
		}	
		
		Entry = (PLDR_DATA_TABLE_ENTRY)Entry->InLoadOrderLinks.Flink;
	}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
	UNREFERENCED_PARAMETER(pRegistryPath);
	pDriverObject->DriverUnload = DriverUnload;
	PLDR_DATA_TABLE_ENTRY pCurrentTableEntry = (PLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
	GetPsLoadedModuleList(pCurrentTableEntry);
	HideDriver(L"hidusb.sys");
 }

https://github.com/codetronik/DepthFantasiaHack/blob/master/DFHack/ddrawhook.cpp

 

codetronik/DepthFantasiaHack

Contribute to codetronik/DepthFantasiaHack development by creating an account on GitHub.

github.com

 

DirectDrawCreateEx 후킹 시 if ((DWORD)lplpDD == 0x8fa7d4)는 특정 게임을 타겟으로 했으므로 삭제해주셔야 합니다.

 

본 문서는 scherzo의 "INSIDE CODE VIRTUALIZER" 기반으로 작성되었습니다.

LINK : http://index-of.es/Reverse-Engineering/Inside%20Code%20Virtualizer.pdf

FULL VERSION : https://tuts4you.com/e107_plugins/download/download.php?view.3162 (zip 안에 포함되어 있음)

Code Virtualizer 1.0.1.0 (Unpacking).rar
0.56MB

분석 시 사용한 Code Virtualizer는 unpack된 버전을 사용하였습니다.

LINK : https://bbs.pediy.com/thread-29018.htm (회원가입 필요)

Unpacked_Code_Virtualizer_v1.0.1.0_Demo.rar
2.21MB

사용 예제 : 비번 1

target.zip
0.04MB

분석 편의성을 위해 아래 3가지를 고정했습니다.

가상 코드 난독화 레벨 : 0 

가상 머신 복잡성 : 0

Key SEED : 1 

예제 코드는 target.exe를 보시면 됩니다.

 

아래의 예제 코드를 작성 후 빌드합니다.

int main()
{  
    VIRTUALIZER_START
    int a = 0x12345678;
    VIRTUALIZER_END
    printf("%d", a);
}

빌드한 바이너리를 디버거에 붙여보면 아래와 같은 형태가 됩니다.

가상화되는 부분은 0x411900의 7바이트입니다.

이 바이너리를 Code Virtualizer에 넣고 protect 버튼을 누르면, 코드 가상화 및 난독화 기능을 수행합니다. 

프로그램의 동작 순서는 아래와 같습니다.

암/복호화 구조체 생성 → KEY 초기화(with time seed) → 레지스터 인덱스 생성 → 핸들러 구조체 생성 → 핸들러에 복호화 코드 추가 → 핸들러 난독화 →  원본 어셈블리어 파싱 및 가상 CPU가 읽을 수 있는 코드로 변환 → 구조체1,2,3 생성 → Prolog & Epilog 핸들러 추가 → fake 핸들러 추가 → 다음 핸들러를 가리키기 위한 핸들러 번호 및 명령어에 대한 데이터 암호화 → 파일에 저장

1) 코드 가상화

Code Virtualizer는 코드 가상화를 위해 가상화에 필요한 정보가 담긴 3개의 구조체를 생성합니다.

첫 번째 구조체 생성을 위해 가장 먼저 하는 일은 가상화 대상의 어셈블리어 코드를 기반으로 아래와 같은 텍스트 형태의 어셈블리어를 생성합니다.

section .code base 0000000h code
MOV dword ptr [EBP + 0fffffff8h], 012345678h
PUSH 000411919h // 복귀를 위해 Code Virtualizer가 추가
RET


LOAD DWORD 0x12345678
가상 CPU는 일반 CPU와 명령어를 읽는 구조가 다르므로, 이를 가상 CPU가 인식할 수 있는 문법으로 변환합니다.  (0x41EFB8  함수)

MOVE ADDR, DWORD PTR [0xF0000030] // F0000030는 EBP에 대한 범용 레지스터 변수를 의미. 추후 다룸
ADD ADDR, 0xFFFFFFF8 
STORE DWORD PTR [ADDR]
LOAD 0x411919h 
RET

이를 가지고 가상 CPU의 명령어, 접미어가 담긴 구조체.1을 생성합니다. 

구조체.1는 14바이트입니다. x64 파일인 경우 4바이트 명령어 데이터가 추가되어 18바이트가 되지만 다루지 않겠습니다.

0x00 1바이트 : 가상 CPU 명령어 번호

0x01 2바이트 : 미사용 (0x00, 0x00)

0x03 1바이트 : 0x80일 경우 명령어에 상대 주소가 있음을 의미함

0x04 4바이트 : 명령어에 대한 접미어 번호

0x08 4바이트 : 명령어에 대한 데이터

0x0C 2바이트 : 미사용 (0x00, 0x00) 

 

가상 CPU의 명령어는 52개, 접미어는 36개가 있습니다. 아래는 예제에서 사용하는 일부입니다.

가상CPU 명령어 번호 명령어 가상CPU 접미어 번호 접미어
0x00 LOAD 0x03 %sADDR, %.8x%h
0x01 STORE 0x06 DWORD PTR %s[ADDR]
0x02 MOVE 0x0E ADDR, DWORD PTR %s[%.8x%h]
0x03 IFJMP 0x11 %s%.8x%h
0x08 ADD 0x1A DWORD
0x0A CMP 0x1E FLAGS
0x33 RET 0x22 %sDWORD %d

표에 대응하여 아래와 같은 구조체.1을 생성할 수 있습니다.

 

struct.1 example
0x00 0x0000 0x00 0x00000022 0x12345678 0x0000 // LOAD DWORD 0x12345678
0x02 0x0000 0x80 0x0000000E 0xF0000030 0x0000 // MOVE ADDR, DWORD PTR [0xF0000030]
0x08 0x0000 0x00 0x00000003 0xFFFFFFF8 0x0000 // ADD ADDR, 0xFFFFFFF8
0x01 0x0000 0x00 0x00000006 0x00000000 0x0000 // STORE DWORD PTR [ADDR]
0x00 0x0000 0x00 0x00000011 0x00411919 0x0000 // LOAD 0x411919
0x33 0x0000 0x00 0x00000000 0x00000000 0x0000 // RET

이제 구조체.1을 바탕으로 구조체.2를 생성합니다.

Reversing Tip
전역변수 0x70AB68에 구조체.2 주소가 저장됩니다.
0x60A38E 함수에서 구조체.1을 구조체.2에 복사하는 작업을 수행합니다.

구조체.2는 40바이트입니다.

0x00 4바이트 : 카운터 (구조체마다 0xE씩 증가)

0x04 4바이트 : 원본 명령어가 담겨있는 메모리 주소 (명령어에 대한 첫 구조체마다)

0x08 4바이트 : 몰라

0x0C 4바이트 : 카운터에 0xE를 더한 값

0x10 1바이트 : if/switch 등 조건문 명령어가 jnz 등 인 경우 TRUE

0x11 1바이트 : (구조체.1) 가상 CPU 명령어 번호

0x12 4바이트 : (구조체.1) 명령어에 대한 접미어 번호

0x16 4바이트 : (구조체.1) 명령어에 대한 데이터

0x1A 4바이트 : (구조체.1) 명령어에 대한 x64 확장 데이터 (x86에선 0x00 0x00 0x00 0x00)

0x1E 2바이트 : (구조체.1) 미사용 (0x00, 0x00)

0x20 7바이트 : 몰라

0x27 1바이트 : 명령어에 상대주소가 있으면 TRUE (구조체.1 0x03이 0x80인 경우)

 

(구조체.1)로 표시된 것은 구조체.1에서 그대로 복사하여 사용합니다.

0x04BF000A c7 45 f8 78 56 34 12 mov dword ptr [ebp-8], 0x12345678
0x04BF0011 68 19 19 41 00       push 0x411919
0x04BF0016 C3                   ret​


구조체.2의 0x04에 위와 같은 원본 코드가 담겨 있습니다.

struct.2 example
0x00000000 0x04BF000A 0x00000000 0x0000000E 0x00 0x00 0x00000022 0x12345678 0x00000000 0x0000 00000000000000 0x00 // LOAD DWORD 12345678h
0x0000000E 0x00000000 0x00000000 0x0000001C 0x00 0x02 0x0000000e 0xf0000030 0x00000000 0x0000 00000000000000 0x01 // MOVE ADDR, DWORD PTR [0xF0000030]
0x0000001C 0x00000000 0x00000000 0x0000002A 0x00 0x08 0x00000003 0xfffffff8 0x00000000 0x0000 00000000000000 0x00 // ADD ADDR, 0xFFFFFFF8
0x0000002A 0x00000000 0x00000000 0x00000038 0x00 0x01 0x00000006 0x00000000 0x00000000 0x0000 00000000000000 0x00 // STORE DWORD PTR [ADDR]
0x00000038 0x04BF0011 0x00000000 0x00000046 0x00 0x00 0x00000011 0x00411919 0x00000000 0x0000 00000000000000 0x00 // LOAD 0x41191A
0x00000046 0x04BF0016 0x00000000 0x00000054 0x00 0x33 0x00000000 0x00000000 0x00000000 0x0000 00000000000000 0x00 // RET

 


마지막으로 구조체.1 및 구조체.2를 가지고 구조체.3을 생성합니다.

Reversing Tip
전역변수 0x70A810에 구조체.3 주소가 저장되어 있습니다.
0x606D23 함수에서 구조체.3에 값을 복사하는 작업을 수행합니다.

구조체.3은 24바이트입니다.

0x00 (2바이트) : 핸들러 번호

0x02 (4바이트) : 구조체.2 메모리 주소

0x06 (4바이트) : 암호화 된 코드 메모리 주소 (Code Virtualizer.exe에서의 주소)

0x0A (1바이트) : 명령어에 대한 데이터를 몇 바이트를 읽을 것인가? 읽을 필요가 없는 경우 0 (즉, 핸들러에 LODS 명령어가 포함되어 있지 않은 경우), LODS가 포함되어 있다면 핸들러에 따라 1(BYTE), 2(WORD), 4(DWORD), 8(QWORD)

0x0B (1바이트) : 구조체.2에 상대주소가 있거나 접미어가 FLAGS인 경우 TRUE

0x0C (4바이트) : 명령어에 대한 데이터

0x10 (4바이트) : 명령어에 대한 x64 확장 데이터 (x86에선 0x00 0x00 0x00 0x00)

0x14 (4바이트) : 암호화 된 데이터 (target.exe)

 

핸들러에 대한 설명이 필요한 경우, 2절을 미리 읽고 오시는 것을 추천합니다.

0x60802B 함수에서 구조체.3을 만드는데, 구조체.2의 0x11, 0x12, 0x27에 따라 필요한 핸들러의 갯수와 각 핸들러마다 구조체.3의 0x00, 0x0A, 0x0B, 0x0C를 지정합니다.

만약 명령어에 상대 주소가 있는 경우(구조체.2 0x27), 0x0C는 본래의 값에 특수한 연산을 취한 값을 가지게 됩니다.

0x06, 0x14는 암호화가 수행될 때 작성됩니다.  (3절에서 다룸)

struct.3 example
0x015B 0xFFFFFFFF 0x00000000 0x00 0x00 0x00000000 0x00000000 0x00000000 // Prolog Signature (FIX)
0x0004 0x04790000 0x09C5002F 0x04 0x00 0x12345678 0x00000000 0x004219df // LOAD DWORD 0x12345678
-> 바이너리 : ee(핸들러 번호) 2f 9f d6 8c(명령어 데이터)
0x0007 0x04790028 0x09C50034 0x01 0x01 0x00000000 0x00000000 0x004219e4 // MOVE ADDR, DWORD PTR [F0000030h]
-> 바이너리 : ed(핸들러 번호) 82(명령어 데이터)
0x0001 0x04790028 0x09C50036 0x00 0x00 0x00000000 0x00000000 0x004219e6
-> 바이너리 : 10(핸들러 번호)
0x0009 0x04790050 0x09C50037 0x00 0x00 0x00000000 0x00000000 0x004219e7 // ADD ADDR, 0xFFFFFFF8
-> 바이너리 : 99(핸들러번호)
0x0004 0x04790050 0x09C50038 0x04 0x00 0xfffffff8 0x00000000 0x004219e8
-> 바이너리 : c1(핸들러 번호) f2 f3 6d 94(명령어 데이터)
0x0006 0x04790050 0x09C5003D 0x00 0x00 0x00000000 0x00000000 0x004219ed
-> 바이너리 : 2a(핸들러 번호)
0x0001 0x04790050 0x09C5003E 0x00 0x00 0x00000000 0x00000000 0x004219ee
-> 바이너리 : 79(핸들러 번호)
0x0018 0x04790078 0x09C5003F 0x00 0x00 0x00000000 0x00000000 0x004219ef // STORE DWORD PTR [ADDR]
-> 바이너리 : 99(핸들러 번호)
0x0004 0x047900A0 0x09C50040 0x04 0x00 0x00411919 0x00000000 0x004219f0 // LOAD 0x411919
-> 바이너리 : 93(핸들러 번호) e1 8a ac 94(명령어 데이터)
0x015D 0x047900C8 0x09C50045 0x00 0x00 0x00000000 0x00000000 0x004219f5 // RET (해당 라인은(0x15D)은 다음 명령어가 없을 경우에만 추가됨)
-> 바이너리 : 52(핸들러 번호)
0x0154 0x047900C8 0x09C50046 0x04 0x00 0xfffffffe 0x00000000 0x004219f6
-> 바이너리 : a4(핸들러 번호) 01 00 00 00(명령어 데이터)
0x0161 0xFFFFFFFF 0x09C5004B 0x00 0x00 0x00000000 0x00000000 0x004219fb // Epilog (키를 0으로 설정)
-> 바이너리 : 1b(핸들러 번호)
0x015C 0xFFFFFFFF 0x00000000 0x00 0x00 0x00000000 0x00000000 0x00000000 // Epilog Signature (FIX)


MOVE ADDR, DWORD PTR [F0000030h]의 경우, 해당 명령어를 구현하기 위해 2개의 핸들러(0x7, 0x1)가 필요합니다.

해석해보면, 명령어에 대한 데이터를 메모리에서 1바이트 읽어와서 0x7번 핸들러에서 처리 후 0x1번 핸들러를 실행하면 MOVE ADDR, DWORD PTR [F0000030h]이 실행되는 것입니다.

구조체의 핸들러 번호를 순차적으로 실행하면, 6줄의 명령(LOAD~RET)이 모두 실행됩니다. 

 

구조체.3에서는 시작과 끝에 핸들러가 추가됩니다. 0x015B, 0x015C는 핸들러가 아닌 단순한 Signature이며, Code Virtualizer는 구조체.3 파싱 도중 이를 인식하면 시작과 끝에 Prolog 와 Epilog 핸들러 체인을 추가합니다.

구조체.2의 0x10이 TRUE인 경우, ebx(키, 추후 다룸)를 초기화 하는 0x161 핸들러가 추가됩니다. 확인해보지는 않았지만 실행 중 ebx가 변하면서 key를 예측할 수 없는 경우를 염두한 것 같습니다.

 

계속해서 데이터 값에 F0000030  혹은 03을 보아왔는데, 이 값들은 target의 범용 레지스터 변수 주소를 가리킵니다.

Prolog 핸들러는 각 블록(VIRTUALIZER_START~END)에서 가상화에 사용하기 위해 메인핸들러에서 PUSHAD 및 PUSHFD로 백업한 레지스터들을 레지스터 배열(Code Virtualizer 베이스 주소에 할당)에 저장합니다.

target에 각 레지스터가 저장되는 배열 위치는 키(핸들러 구조체 키와 동일한) 체인에 의해 생성됩니다. 이는 일반적으로 통용되는 공격을 막기 위함입니다. 

 

구조체.3의 03은 아래의 수식으로 Code Virtualizer.exe의 레지스터 배열 위치를 가리킵니다.

6(array no) = (0xF0000030(데이터) + 0x10000000) >> 3 

 

array no. Code Virtualizer.exe (fixed) 레지스터 데이터 target 배열 위치 예시
0 0x70A814 EAX 0xF0000000 3
1 0x70A818 EBX 0xF0000008 2
2 0x70A81C ECX 0xF0000010 6
3 0x70A820 EDX 0xF0000018 5
4 0x70A824 ESI 0xF0000020 4
5 0x70A828 EDI 0xF0000028 1
6 0x70A82C EBP 0xF0000030 0
7 0x70A830 EFLAGS 0xF0000038 7

 

아래는 target에서 메인 핸들러 진입 전의 레지스터입니다.

Prolog 핸들러 실행 후 아래와 같이 배열에 저장됩니다. (예시이므로 순서는 항상 다를 수 있습니다.)

2) 핸들러

핸들러는 가상 opcode를 처리하기 위해 존재합니다. 가상 CPU라고 봐도 무방합니다. Code Virtualizer는 모든 코드를 핸들러 단위로 처리합니다.

각각의 핸들러는 opcode와 접미어가 결합된 형태로 구성되어 있습니다. 총 150개가 존재하며, 메인 핸들러는 별도로 존재합니다.

이해를 돕기위해 LOAD DWORD 12345678h가 있다고 가정합니다.

가상 CPU 명령어 LOAD는 명령어 데이터를 push하는 명령어인데, 접미어에 따라 push하는 바이트 수가 다릅니다.

명령어에 대한 데이터는 ".v-lizer"섹션 메모리에 저장되어 있습니다. 접미어가 DWORD이므로 메모리에서 4바이트를 읽습니다. 

이를 어셈블리어로 구현하면 아래와 같이 됩니다.

LODS DWORD PTR DS:[ESI]
PUSH EAX

이러한 코드를 가진 핸들러가 0x4번에 구현되어 있습니다. 따라서 구조체.3의 "LOAD DWORD 12345678h"에 0x4번 핸들러를 읽으라고 지정된 것입니다.

만약 LOAD WORD이라면, 아래와 같이 됩니다. 

LODS WORD PTR DS:[ESI]
MOVZX EAX,AX
PUSH AX

이 또한 0x3번 핸들러에 구현되어 있습니다.

즉, 모든 상황에 맞는 prebuild된 핸들러가 존재한다고 보시면 됩니다.

LOAD 처럼 단일 핸들러로 처리할 수 있는 명령어도 있지만, MOVE처럼 2개이상의 핸들러를 필요로 하는 명령어도 있습니다. 이 경우 여러 개의 핸들러가 실행됩니다. (구조체.3 참고)

 

수 많은 핸들러들을 다루기 위해 Code Virtualizer는 핸들러 구조체를 만들어 사용합니다. 핸들러 구조체(0x603B72)는 아래와 같은 16바이트 구조로 되어 있습니다.

0x00 (2바이트) : 핸들러 번호 (0x0~0x161)

0x02 (4바이트) : Code Virtualizer에서의 핸들러 시작 주소

0x06 (4바이트) : Code Virtualizer에서의 핸들러 끝 주소

0x0A (4바이트) : target(가상화 할)에서의 핸들러 시작 주소

0x0E (2바이트) : target에서의 핸들러 번호 (0x0E~0xA3)

 

파일을 가상화하기 전에는 아래와 같이 초기화되어 있으며,

(Code Virtualizer.exe 프로세스 메모리 일부, 이하 생략)

 

가상화가 끝나면 아래와 같이 채워집니다. target에서의 핸들러 번호는 키 체인에 의해 생성됩니다. (0x6084E9 함수)

중간중간에 비어있는 번호가 보이는데, 이 중 0x015B, 0x015C 는 실제로 존재하는 핸들러가 아닌 구조체.3의 START, END를 식별하는 Signature입니다.

 

이해를 돕기위해 예시를 들어보겠습니다. 0x1번 핸들러는 아래와 같습니다.  (단지 pop edx만 수행)

그러나 target의 핸들러는 모양이 다른데, 이는 Code Virtualizer가 복잡성을 증가시키기 위한 코드를 삽입하였기 때문입니다. target 파일마다 다르게 생성되므로 분석가는 핸들러를 특정할 수 없게 됩니다.

아래는 가상 머신의 복잡성 레벨을 0으로 설정했을 경우입니다. (복잡성 레벨은 0~3) 레벨이 높을 수록 삽입되는 코드가 많아집니다.

handler 0x1 example
PUSH DWORD PTR SS:[ESP]
MOV EDX,DWORD PTR SS:[ESP]
ADD ESP,0x4
ADD ESP,0x4

언뜻 보기엔 복잡하게 보이지만, 실제 수행은 동일합니다.

LODS 명령어가 없는 핸들러의 경우는 데이터 복호화 로직이 없기 때문에, 코드들을 전부 NOP처리한 후, pop edx 명령어만 붙여넣어도 코드는 정상적으로 수행됩니다. 

150개의 핸들러 주소는 target파일의 ".v-lizer" 섹션에 추가됩니다. ".v-lizer"섹션은 원본의 ".resource" 섹션에 핸들러 주소와 핸들러 코드가 추가된 형태입니다. 

Code Virtualizer.exe의 0번째 핸들러(0x41F566)는 정확히 0x1F (0x2D - 0xE) 번째에 존재하는 것을 볼 수 있습니다.

 

여기까지 보셨으면 아마 의문이 드셨을 겁니다. 다음 핸들러의 위치는 어떻게 가리키는가? 예를들어, 구조체.3의 예시의 경우 0x4 → 0x7 인데, 0x4 핸들러에서 0x7 핸들러로 어떻게 이동하는가?

답은 NEXT 핸들러를 사용하여 다음 핸들러를 가리키게 됩니다. NEXT 핸들러는 인덱스(번호)를 이용하여 위의 ".v-lizer"섹션 메모리에서 다음 핸들러의 주소를 찾습니다.   

target을 실행하면 메인핸들러 → NEXT 핸들러 → Prolog 핸들러(여러개) → NEXT 핸들러 → 0x4 → NEXT 핸들러 → 0x7 → NEXT 핸들러 → .... → Epilog 핸들러(여러개) 순으로 실행됩니다.

 

2-1) 메인 핸들러 (+ NEXT 핸들러)

메인 핸들러는 난독화 코드 block에서 최초로 실행되는 핸들러입니다.

 

두 그림 모두 메인 핸들러로, 위는 Code Virtualizer.exe, 아래는 target.exe입니다.

target의 경우는 ASLR로 인하여 임의 주소로 변경되었습니다.

메인 핸들러 호출 직전에 암호화 된 바이너리 주소가 push됩니다.

 

메인 핸들러 실행 절차는 아래와 같습니다.

(1)범용 레지스터들과 EFLAGS 레지스터를 백업합니다. 

(2) 난독화 시 이미지 베이스 0x400000 고정으로 계산되므로 필요 시 Relocation을 하여야 합니다. 이를 위해 target에 저장할 때 해당 라인(eip)의 주소를 기입합니다.   

pop edi시 edi에 0xEDF898이 저장됩니다. 따라서 Relocation 계산에 필요한 값은 0x32F898-0x41F898 = 0xFFF10000이 됩니다. 

다음으로, 0x41F600(Code Virtualizer 베이스) + 0x2C 주소에 해당 값이 있는지 확인합니다. 값이 없으면 Relocation을 수행합니다.

(3) Relocation 값을 0x32F62C에 저장하고, 핸들러 갯수 (96개) 만큼 핸들러 주소를 Relocation을 합니다.

녹색 줄은 Relocation 완료, 파란색 줄은 미완료입니다.

 

(4) push한 0x4229B0(ASLR로 인해 .rsrc 섹션이 추가되어 주소가 변경됨)을 꺼내와 키(ebx)로 사용합니다.

다음으로, 현재 모듈의 암호화 된 바이너리 주소를 esi에 저장하여 NEXT 핸들러에서 참조할 수 있도록 합니다.

(5) Code Virtualizer 베이스 + 0x30에 TRUE를 설정합니다. (가상화 실행 중 표시)

 

각 핸들러 끝에는 다음 핸들러를 가리키는 0x604520의 NEXT핸들러가 추가됩니다.

 

여기에 암호화 된 핸들러 번호를 복호화하는 코드를 추가하여 target에 저장합니다. (3절 참조)

added decrypt code (example)
LODS BYTE PTR DS:[ESI]
SUB AL,BL
ADD AL,0x1D
ADD AL,0x96
SUB BL,AL
MOVZX EAX,AL
JMP DWORD PTR DS:[EDI+EAX*4]

 

2-2) Prolog 핸들러 (레지스터 저장)

정의된 핸들러의 조합으로 Prolog를 구성합니다.  Prolog에서는 PUSHAD, PUSHFD 를 역순으로 가져와 Code Virtualizer 변수에 저장 후 POP을 수행합니다.

실행 순서는 아래와 같습니다. (0x608F43 함수에 정의되어 있음) 모든 핸들러가 수행되어야만 레지스터가 변수에 저장되고, 스택이 정리됩니다.

0x0 ->  0x1 -> 0x18 (0x0 : EFLAGS 저장)

→ 0x0 -> 0x1 -> 0x18 (0xF0000028 : EDI 저장)

→ 0x0 -> 0x1 -> 0x18 (0xF0000020 : ESI 저장)

→ 0x0 -> 0x1 -> 0x18 (0xF0000030 : EBP 저장)

→ 0x0 -> 0x1 -> 0x18 (0xF0000008 : ESP 저장) // ESP는 저장할 필요가 없지만 POP을 위해 임시로 저장함

→ 0x157 (0xF0000010 : ECX의 인덱스를 저장)

→ 0x0 -> 0x1 →  0x18 (0xF0000008 : EBX 저장)

→ 0x0 -> 0x1 →  0x18 (0xF0000018 : EDX 저장)

→ 0x0 -> 0x1 →  0x18 (0xF0000010 : ECX 저장)

→ 0x0 -> 0x1 →  0x18 (0xF0000000 : EAX 저장)

→ 0x14E →  0x9 → 0x4 →  0x6 →  0x14D (스택 정리 : ESP원복)

 

예시는 베이스 주소(EDI)를 0x41E600으로 합니다. 괄호 안은 핸들러 번호입니다.

아래는 target의 메인 핸들러 진입 전 스택 구성입니다. (암호화 된 데이터 0x4219B0가 PUSH 되었음)

[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 7(배열 위치)
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E61C
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // EFLAGS = 41E61C
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 1
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E604
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // EDI = 41E604
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 4
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E610
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // ESI = 41E610
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 0
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E600
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // EBP = 41E600
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 2
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E608
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // ESP = 41E608 ; 임시저장
 
[0x0157] LODS BYTE PTR DS:[ESI]
[0x0157] MOV BYTE PTR DS:[EDI+0x28],AL  // EDI = 41E628(41E600 + 0x28), EAX = 6(ECX의 인덱스)
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 2
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E608
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // EBX = 41E608 ; 덮어 씌움
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 5
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E614
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // EDX = 41E614
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 6
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E618
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // ECX = 41E618
 
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                     // EAX = 3
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4] // 41E60C
[0x00] PUSH EAX
[0x01] POP EDX
[0x18] POP DWORD PTR DS:[EDX]           // EAX = 41E60C
 
[0x014E] MOV EDX,ESP  // 스택 정리한 후, EDX = ESP
[0x0009] PUSH EDX
 
[0x0004] LODS DWORD PTR DS:[ESI]        // 나머지 스택 정리
[0x0004] PUSH EAX // 4 (고정)
[0x0006] POP EAX
[0x0006] ADD DWORD PTR SS:[ESP],EAX
[0x014D] POP ESP                        // ESP = 0x19FE1C ; ESP 복원



2-3) Epilog 핸들러

Prolog에서 백업해 놓은 범용 레지스터 및 EFLAGS 레지스터를 스택에 PUSH합니다. PUSHFD, PUSHAD와 동일한 기능을 수행합니다.

마지막으로 POPAD, POPFD로 레지스터들을 원복시킵니다.

최종적으로 원래 주소로 RETN합니다.

실행 순서는 아래와 같습니다. (0x608A4B 함수에 정의되어 있음)

0x0 → 0x1 → 0xC (0x0 : EFLAGS 복원)

→ 0x0 → 0x1 → 0xC (0xF0000000 : EAX 복원 )

→ 0x0 → 0x1 → 0xC (0xF0000010: ECX 복원)

→ 0x0 → 0x1 → 0xC (0xF0000018 : EDX 복원)

→ 0x0 → 0x1 → 0xC (0xF0000008 : EBX 복원)

→ 0x0 → 0x1 → 0xC (0xF0000008 : EBX 복원) // 임시로 아무 레지스터나 복원하여 공간만 만들어 놓음. ESP는 복원하지 않아도 나중에 맞춰짐

→ 0x0 → 0x1 → 0xC (0xF0000030 : EBP 복원)

→ 0x0 → 0x1 → 0xC (0xF0000020 : ESI 복원)

→ 0x0 → 0x1 → 0xC (0xF0000028 : EDI 복원)

→ 0x14A (스택 정리 및 RETN)

9회 실행
[0x00] LODS BYTE PTR DS:[ESI]
[0x00] MOVZX EAX,AL                    
[0x00] LEA EAX,DWORD PTR DS:[EDI+EAX*4]
[0x00] PUSH EAX
[0x01] POP EDX
[0x0C] PUSH DWORD PTR DS:[EDX]  // PUSH EFLAGS, EAX, ECX, EDX, EBX, EBX, EBP, ESI, EDI
 
[0x14A] MOV DWORD PTR DS:[EDI+0x30],0x0  // 가상화 실행 OFF를 의미
[0x14A] POPAD
[0x14A] POPFD
[0x14A] RETN

 

3) 핸들러 번호 및 명령어 데이터 암/복호화 

target의 ".v-lizer" 섹션에 구조체.3의 핸들러 번호와 명령어 데이터를 담은 바이너리를 저장하는데, 키 및 암/복호화 구조체를 가지고 암호화(SUB/ADD/XOR)를 한 후 저장합니다.  

키의 경우 최초로 설정된 값은 target에서의 암호화 된 바이너리가 저장된 address이며, 각 핸들러 실행 시마다 계속 변하게 됩니다. 체인 형태로 되어 있어 이전 핸들러가 실행되어야만 다음 핸들러의 키 값을 알 수 있습니다. 

복호화를 위해 Code Virtualizer는 target의 각 핸들러(150개 + NEXT핸들러)에 복호화 코드를 추가합니다. 복호화는 암호화의 역순으로 합니다. 가령, 암호화 시에 1.SUB → 2.ADD → 3.ADD를 수행했다면, 복호화 시에 3.SUB → 2.SUB → 1.ADD를 수행하는 코드를 추가합니다.

복호화 코드는 핸들러에 LODS명령어(암호화 된 바이너리를 꺼내오는)가 있을 경우에만 추가됩니다. 

Reversing Tip
암호화 로직 생성 : 0x6087BB
복호화 로직 생성 : 0x6081A9

LODS가 읽어들이는 바이트 수(1/2/4)에 따라 동일 사이즈로 암/복호화를 하게 됩니다.

LODS 명령어 바로 밑에 4개의 복호화 명령어가 추가됩니다. 이는 암호화 시 생성되는 암/복호화 구조체([0x70A870])와 밀접하게 연관되어 있습니다.

 

암/복호화 구조체는 2가지 경우에 사용됩니다.

암호화 : 핸들러 번호 암호화 (NEXT핸들러 구조체 사용), 각 핸들러에서 처리되는 명령어에 대한 데이터 1/2/4바이트 암호화  

복호화 : NEXT 핸들러에 복호화 로직 추가 (NEXT 핸들러 구조체 사용), 각 핸들러에 복호화 로직 추가  

 

암/복호화 구조체에는 4개의 암/복호화 명령어 타입과 2개의 랜덤키가 정의되어 있습니다.

0x00 (2바이트) : 핸들러 번호 (0x0~0x161, NEXT 핸들러의 경우 0x0으로 정의되어 있음)

0x02 (1바이트) : 명령어.3 에 대한 암,복호화 타입 

0x03 (1바이트) : 명령어.2 에 대한 암,복호화 타입 

0x04 (1바이트) : 명령어.1 에 대한 암,복호화 타입 

0x05 (1바이트) : 명령어.4 에 대한 암,복호화 타입 

0x06 (4바이트) : 명령어.3 에 대한 랜덤 키

0x0A (4바이트) : 명령어.2 에 대한 랜덤 키

 

암/복호화 구조체의 명령어.1~4 타입 및 랜덤키는 키 체인(다음 키는 이전 키의 값에 영향을 받음)에 의해 생성됩니다. (0x606A40 함수에서 생성) 레지스터 변수의 인덱스 생성 시에도 동일한 키 체인이 적용됩니다.

최초 키 생성 시 SEED는 GetLocalTime() 으로 생성하는데(0x653134 함수), 암/복호화 구조체 생성 이후에 SEED를 생성하므로 최초 실행 시의 암/복호화 구조체엔  항상 동일한 SEED(0x3039)가 적용되어 아래 예제의 값은 최초 실행 시에는 고정이 됩니다.

example
0000 01 01 00 01 32A55896 10F3261D // NEXT 핸들러 및 핸들러 번호 전용
0000 00 00 00 00 62D34738 64B7CA2B // 이하 각 핸들러 및 데이터 암/복호화에 사용
0001 00 00 00 01 7D1840D9 04F96246
0002 02 02 01 00 2B2AFD01 3A7C55C9
0003 01 01 02 02 7021F1B5 1BC3CFF7
0004 01 01 02 01 78076014 0D986F9A
0006 01 02 01 02 20E473F0 6F4C262F
0007 02 02 01 00 44AD5FB7 60BFF50B
0009 02 01 02 01 7C0DFEFA 6C75F611
이하 생략

명령어 1~4의 타입에 따라 명령어가 다르게 추가됩니다. ebx는 키입니다.

타입 명령어1 명령어2 명령어3 명령어4
0 add eax, ebx(Key) add eax, RandomKey add eax, RandomKey add ebx(Key), eax
1 sub eax, ebx(Key) sub eax, RandomKey sub eax, RandomKey sub ebx(Key), eax
2 xor eax, ebx(Key) xor eax, RandomKey xor eax, RandomKey xor ebx(Key), eax

(암호화 로직에 추가되는 명령어. LODS로 읽은 바이트 수에 따라 al/ax/eax, bl/bx/ebx)

타입 명령어1 명령어2 명령어3 명령어4
0 sub eax, ebx(Key) sub eax, RandomKey sub eax, RandomKey add ebx(Key), eax
1 add eax, ebx(Key) add eax, RandomKey add eax, RandomKey sub ebx(Key), eax
2 xor eax, ebx(Key) xor eax, RandomKey xor eax, RandomKey xor ebx(Key), eax

(복호화 로직에 추가되는 명령어. LODS로 읽은 바이트 수에 따라 al/ax/eax, bl/bx/ebx)

 

구조체를 생성후 각 핸들러에 복호화 로직을 추가합니다. 

0x3번 핸들러를 예로 들어봅니다.

LODS WORD PTR DS:[ESI]
MOVZX EAX,AX
PUSH AX

구조체는 0003 01 01 02 02 7021F1B5 1BC3CFF7가 사용되었다고 가정하면 생성되는 복호화 핸들러는 아래와 같습니다.

LODS WORD PTR DS:[ESI]
XOR AX,BX
ADD AX,0xCFF7 // 랜덤키
ADD AX,0xF1B5 // 랜덤키
XOR BX,AX
MOVZX EAX,AX
PUSH AX

0x161번 핸들러 번호를 암호화 할 시에는 직후에 키가 0으로 설정됩니다. (바로 다음 암호화는 키 0을 사용)

복호화 시에는 0x161 핸들러에서 mov ebx(Key), 0을 수행합니다.

 

아래는 암호화 예시입니다. (Prolog 일부)

암호화 대상 :

1) 핸들러 번호 0x0(target : 0x2D) 

→ 2) EFLAGS 레지스터 인덱스 번호 (target : 0x7) 

→ 3)핸들러 번호 0x1(target : 0x89) 

명령어는 4 → 3→ 2→ 1순 입니다.

 

1)

최초 키 : 0x4219B0 

암호화 대상 : 0x2D (핸들러 번호)

복호화 핸들러 : NEXT 핸들러 

SUB EBX, EAX        // 4219B0-2D = 421983
SUB EAX, RandomKey  // 2D-32A55896 = CD5AA797
SUB EAX, RandomKey  // CD5AA797-10F3261D = BC67817A
ADD EAX, EBX        // BC67817A+4219B0 = BCA99B2A

핸들러에서 LODS하는 바이트가 1이므로 암호화된 값은 0x2A입니다. 

2)

키 : 0x421983

암호화 대상 : 0x7 (인덱스 번호)

복호화 핸들러 : 0x0 핸들러

ADD EBX, EAX        // 421983+7 = 42198A
ADD EAX, RandomKey  // 7+62D34738 = 62D3473F
ADD EAX, RandomKey  // 62D3473F+64B7CA2B = C78B116A
ADD EAX, EBX        // C78B116A+421983 = C7CD2AED

핸들러에서 LODS하는 바이트가 1이므로 암호화된 값은 0xED입니다.

3)

키 : 0x42198A

암호화 대상 : 0x89 (핸들러 번호)

복호화 핸들러 : NEXT 핸들러

SUB EBX, EAX        // 42198A-89 = 421901
SUB EAX, RandomKey  // 89-32A55896 = CD5AA7F3
SUB EAX, RandomKey  // CD5AA7F3-10F3261D = BC6781D6
ADD EAX, EBX        // EBX BC6781D6+42198A = BCA99B60

핸들러에서 LODS하는 바이트가 1이므로 암호화된 값은 0x60입니다.

target.exe 에 저장된 암호화 된 바이너리

 

아래는 target 프로그램 실행 시의 복호화 예시입니다.

명령어는 1 → 2→ 3→ 4순 입니다.

복호화 값은 명령어 3에서 도출되며, 4는 키 계산입니다.

1)

최초 키 : 0x4219B0 

복호화 대상 : 2A

실행 핸들러 : NEXT 핸들러

LODS BYTE PTR DS:[ESI] // 0x4219B0에서 1바이트 가져옴
SUB AL,BL       // 2A-B0=7A
ADD AL,0x1D     // 7A+1D=97
ADD AL,0x96     // 97+96=2D ; 타겟에서의 인덱스
SUB BL,AL       // B0-2D=83
MOVZX EAX,AL
JMP DWORD PTR DS:[EDI+EAX*4] // 41E600+(2D*4) = JMP [41E6B4] -> JMP 41F566 (0번 핸들러)


2)

키 : 0x83

복호화 대상 : ED

실행 핸들러 : 0번 핸들러

레지스터 변수 주소를 PUSH합니다.

LODS BYTE PTR DS:[ESI] // 0x4219B1에서 1바이트 가져옴
SUB AL,BL   // ED-83=6A
SUB AL,0x2B // 6A-2B=3F
SUB AL,0x38 // 3F-38=7
ADD BL,AL   // 83+7=8A
MOVZX EAX,AL
LEA EAX,DWORD PTR DS:[EDI+EAX*4] // EAX = 7(복호화 된 인덱스 번호), EDI = 41E600(베이스 주소)
PUSH EAX                         // EAX = 41E61C
JMP 42122C                       // NEXT 핸들러

 

3)

키 : 0x8A

복호화 대상 : 60

실행 핸들러 : NEXT 핸들러

LODS BYTE PTR DS:[ESI] // 0x4219B2에서 1바이트 가져옴
SUB AL,BL       // 60-8A=D6
ADD AL,0x1D     // D6+1D=F3
ADD AL,0x96     // F3+96=89 ; 타겟에서의 index
SUB BL,AL       // 8A-89=1
MOVZX EAX,AL
JMP DWORD PTR DS:[EDI+EAX*4] // 41E600+(0x89*4) = JMP [41E824] -> JMP 421482 (1번 핸들러)

 

4) 핸들러 난독화

LODS명령어가 존재하는 경우 복호화 코드가 추가 된 후, 핸들러의 복잡도를 높이기 위한 코드를 추가합니다.

코드 복잡성의 경우 위에서 살펴보았듯이, 단지 복잡성만 추가되고 실행 결과는 동일합니다. (0x60524F 함수에서 수행)

가상 머신의 복잡성 레벨이 높다면 코드도 복잡해집니다.

앞서 본 3번 핸들러의 경우 아래와 같이 난독화 될 수 있습니다.

LODS WORD PTR DS:[ESI]
XOR AX,BX
SUB ESP,0x2
MOV WORD PTR SS:[ESP],DI
MOV DI,0xCFF7
ADD AX,DI
MOV DI,WORD PTR SS:[ESP]
ADD ESP,0x2
SUB ESP,0x2
MOV WORD PTR SS:[ESP],BP
MOV BP,0xF1B5
ADD AX,BP
MOV BP,WORD PTR SS:[ESP]
ADD ESP,0x2
XOR BX,AX
MOVZX EAX,AX
PUSH 0x2EB2
MOV WORD PTR SS:[ESP],AX

 

5) Fake 핸들러

키가 특정 조건에 만족하는 경우 fake 핸들러가 추가되는 경우가 있습니다. (0x60693E 함수에서 수행)

이 fake 핸들러는 마치 NOP처럼 실행에 아무런 영향을 주지 않습니다. 이로 인해 분석이 어려워집니다.  

아래는 예시입니다. (구조체.3 참조)

fake 핸들러가 없는 경우 : 0x4 → 0x7 → 0x1 → 0x9 → 0x4 → 0x6 → 0x1 → 0x18 → 0x4 → 0x15D → 0x154 → 0x161

fake 핸들러가 추가된 경우 : 0x4 → 0x9 → 0x3 → 0x3 → 0x1 → 0x15 → 0x7 → 0x1 → 0x9 → 0x4 → 0x6 → 0x1 → 0x18 → 0x4 → 0x15D → 0x154 → 0x161

fake 핸들러가 추가된 경우 : 0x4 → 0x7 → 0x1 → 0x9 → 0x4 → 0x6 → 0x1 → 0x18 → 0x4 → 0x15D → 0x154 → 0x9 → 0x1 → 0xC → 0x1 → 0x15 → 0x161

fake 핸들러가 추가된 경우 : 0x4 → 0x9 → 0x9 → 0x4 → 0x6 → 0x1 → 0x15 → 0x7 → 0x1 → 0x9 → 0x4 → 0x6 → 0x1 → 0x18 → 0x4 → 0x15D → 0x154 → 0x161

fake 핸들러가 추가된 경우 : 0x4 → 0x9 → 0x0 → 0x1 → 0x15 → 0x7 → 0x1 → 0x9 → 0x0 → 0x1 → 0xC → 0x1 → 0x0 → 0x1 → 0xC → 0x1 → 0x15  0x9 → 0x4 → 0x6 → 0x1 → 0x18 → 0x4 → 0x15D → 0x154 → 0x161

 

6) target 실행 요약

Code Virtualizer를 거치면서 START 매크로는 JMP 메인핸들러로 변경되어 있습니다.

메인 핸들러에서 초기화 작업을 한 후, 스택 저장(Prolog 핸들러) → 매크로 코드 블럭에 대응하는 핸들러 → 스택 복원(Epilog 핸들러) → RETN 순으로 동작하고 마지막으로 다음 EIP로 복귀합니다.

암호화 된 바이너리 위에 평문 핸들러 번호가 기입되어 있습니다. (미기입은 명령어 데이터)

 

ppt :

프레젠테이션1.pptx
0.68MB

보통 64비트에서 드라이버를 로드하려면 코드서명을 하거나 TEST모드(bcdedit.exe를 이용한)로의 부팅이 필요합니다.

하지만 DSE을 익스플로잇 함으로서 코드서명 없이 드라이버를 로드할 수 있습니다.

소개 및 다운로드 :: https://github.com/hfiref0x/DSEFix

필자 테스트 환경 : Windows 10 Pro (10.0.18363) 64비트 / Visual Studio 2019 + WDK version 1903

1. 프로젝트 속성 페이지에서 드라이버 서명 모드를 Off로 합니다.

2. 관리자 권한으로 DSEFix.exe를 실행합니다.

3. OSRLOADER로 드라이버를 로드합니다. (Browse -> Register Service -> Start Service)

다운로드 : http://www.osronline.com/article.cfm%5Earticle=157.htm

DebugView에서 로그가 잘 찍혔습니다.

만약 DSEFix가 정상적으로 실행되지 않았다면, 드라이버 로드 시 아래와 같은 에러 메시지가 발생합니다.

익스플로잇 이후 블루스크린이 발생할 수 있습니다.

 

 

+ Recent posts