ミニキャンプ沖縄・仮想化技術を用いたマルウェア解析-Revenge(プラグイン開発編)

ミニキャンプ沖縄で出来なかったところのリベンジです!

環境構築は前回書いてるのでそちらを参考にしてください.

 

解析対象はblue.exeというマルウェアっぽい挙動をするマルウェアではないexeです.

それでは,Let's プラグイン開発!!

 (1週間程度の冬休みにぼちぼちとやる予定です.随時更新します.)

geteip.c

ソースを全部載せると見づらくなるので

適当に抜粋しています.

    
static plugin_interface_t geteip_interface;
static DECAF_Handle processbegin_handle = DECAF_NULL_HANDLE;
static DECAF_Handle blockbegin_handle = DECAF_NULL_HANDLE;
static DECAF_Handle isdebuggerpresent_handle = DECAF_NULL_HANDLE;
char targetname[512];
uint32_t target_cr3;

typedef struct {
        uint32_t call_stack[1]; //paramters and return address
        DECAF_Handle hook_handle;
} IsDebuggerPresent_hook_context_t;

/*
 * BOOL IsDebuggerPresent(VOID);
 */

static void IsDebuggerPresent_ret(void *param)
{
        IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t *)param;
        hookapi_remove_hook(ctx->hook_handle);
cpu_single_env->regs[R_EAX] = 0; DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]); free(ctx); } static void IsDebuggerPresent_call(void *opaque) { DECAF_printf("IsDebuggerPresent "); IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t*)malloc(sizeof(IsDebuggerPresent_hook_context_t)); if(!ctx) return; DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack); ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], IsDebuggerPresent_ret, ctx, sizeof(*ctx)); } static void geteip_block_begin_callback(DECAF_Callback_Params* params) { if(params->bb.env->cr[3] == target_cr3) { target_ulong eip = params->bb.env->eip; target_ulong eax = params->bb.env->regs[R_EAX]; // DECAF_printf("EIP = 0x%08x, EAX = 0x%08x\n", eip, eax); } } static void geteip_loadmainmodule_callback(VMI_Callback_Params* params) { if(strcmp(params->cp.name,targetname) == 0) { DECAF_printf("Process %s you spcecified starts \n", params->cp.name); target_cr3 = params->cp.cr3; isdebuggerpresent_handle = hookapi_hook_function_byname("kernel32.dll", "IsDebuggerPresent", 1, target_cr3, IsDebuggerPresent_call, NULL, 0); blockbegin_handle = DECAF_register_callback(DECAF_BLOCK_BEGIN_CB, &geteip_block_begin_callback, NULL); } }

優しいことに,IsDebuggerPresent()は,雛形があります.

IsDebuggerPresent_ret()の中に

cpu_single_env->regs[R_EAX] = 0;

を入れるだけです.

このIsDebuggerPresent()というのは,Debugしているかを調査し,

Debugされていたら終了するという挙動を示します.

戻り値が0になればDebugされていないと認識されます.

IsDebuggerPresent()はHookできました.ここから課題スタートです.

IsDebuggerPresent_ret()とIsDebuggerPresent_call()を参考にこれからHookしていきたいと思います.

blue.exeのソースは同じディレクトリにあるblue.cppですが,

本当のマルウェアはソースを与えてくれるなんて優しいことはしないと思うので

IDAで見ます.(blue.cppを見ながら...w)

[Level1] Blue...

f:id:tsunpoko:20151227185316p:plain

static inline int Blue(){
        printf("\n[Level 1] Blue ...");
        Sleep(360000);
        DWORD time1 = GetTickCount();
        Sleep(500);
        if ((GetTickCount() - time1) < 450) Detected();
        else return 0;
}

 Level1は,このような挙動を示します.

6分間Sleep()した後に,GetTickCount()を2回コールしています.

ちなみに,Detected()は終了する関数みたいな感じです.

なぜ6分間もSleep()しているのかですが,もしマルウェアがサンドボックスなどに投げられて,自動解析される際に,挙動を隠すことができるからです.

Malwr - Malware Analysis by Cuckoo Sandbox

などは実際に投げられたプログラム(マルウェア)を動かし,スクリーンショットでマルウェアの挙動を返してくれます.

その際に,6分間何もしなければスクリーンショットには,それっぽい挙動は写りこみません.

話が少しそれましたが,手始めにSleep()をHookしていきたいと思います.

 

構造体を宣言し,Sleep_ret(),Sleep_call()の順でコードを書いていきます.

static DECAF_Handle sleep_handle = DECAF_NULL_HANDLE;
                 .
                 .
typedef struct{
        uint32_t call_stack[2];
        DECAF_Handle hook_handle;
}Sleep_hook_context_t;
                 .
                 .
static void Sleep_ret(void *param){
        Sleep_hook_context_t *ctx = (Sleep_hook_context_t*)param;
        hookapi_remove_hook(ctx->hook_handle);
        DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
        free(ctx);
}

static void Sleep_call(void *opaque){
        DECAF_printf("Sleep ");
        Sleep_hook_context_t *ctx = (Sleep_hook_context_t*)malloc(sizeof(Sleep_hook_context_t));
        if(!ctx) return;
        DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack);
        ctx->call_stack[1] = 1;
        DECAF_write_mem(NULL, cpu_single_env->regs[R_ESP], 2*4, ctx->call_stack);
        ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], Sleep_ret, ctx, sizeof(*ctx));
}
                 .
                 .
static void geteip_loadmainmodule_callback(VMI_Callback_Params* params)
{
             sleep_handle = hookapi_hook_function_byname("kernel32.dll", "Sleep", 1, target_cr3, Sleep_call, NULL, 0);
}

どんな事をしているかですが,注目するのはSleep_call()中の

ctx->call_stack[1] = 1; です.

stackにリターンアドレス,引数の順で値を積んでいます.(たぶん)

構造体宣言でcall_stack[2]としたのはコレが理由です.

だからcall_stackの2番目に1を代入して1msしかSleep()しないようにしています.

DECAF_write_mem()して反映させて終わりです.

次はGetTickCount()をHookしていきます.

でもこの関数さすがに分からなかったのでMSDNで調べました.

GetTickCount 関数

GetTickCount

システムを起動した後の経過時間を、ミリ秒(ms)単位で取得します。この時間は、システムタイマの分解能による制限を受けます。システムタイマの分解能を取得するには、GetSystemTimeAdjustment 関数を使います。

DWORD GetTickCount(VOID);

パラメータ

パラメータはありません。

戻り値

関数が成功すると、システムを起動した後の経過時間が、ミリ秒単位で返ります。

解説

経過時間は DWORD 型で保存されています。システムを 49.7 日間連続して動作させると、経過時間は 0 に戻ります。

 てな感じです.

Blue()の中で2回GetTickCount()をコールしていて,

1回目と2回目の間にSleep(500)を挟み,差を比較しています.差が450以内だとプログラムが終了する.

なるほど,Sleep()が1msとか小さい値でHookされていたら差が450以内で収まるのか.

頭いい...イタチごっことはまさにこの事か...

戻り値...戻り値をどうにかすれば良さそう!!

static DECAF_Handle gettickcount_handle = DECAF_NULL_HANDLE;
                     .
                     .
int cnt = 1;
                     .
                     .
typedef struct{
        uint32_t call_stack[1];
        DECAF_Handle hook_handle;
 }GetTickCount_hook_context_t;

                     .
                     .
static void GetTickCount_ret(void *param){
        GetTickCount_hook_context_t *ctx = (GetTickCount_hook_context_t*)param;
        hookapi_remove_hook(ctx->hook_handle);
        if(cnt){
                cpu_single_env->regs[R_EAX] = 0;
                cnt--;
        }
        else{
                cpu_single_env->regs[R_EAX] = 114514;
                //cpu_single_env->regs[R_EAX] = 0;
        }
        DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
        free(ctx);
}

static void GetTickCount_call(void *opaque){
        DECAF_printf("GetTickCount ");
        GetTickCount_hook_context_t *ctx = (GetTickCount_hook_context_t*)malloc(sizeof(GetTickCount_hook_context_t));
        if(!ctx) return;
        DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack);
        ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], GetTickCount_ret, ctx, sizeof(*ctx));
}
                     .
                     .
static void geteip_loadmainmodule_callback(VMI_Callback_Params* params)
{
            gettickcount_handle = hookapi_hook_function_byname("kernel32.dll", "GetTickCount", 1, target_cr3, GetTickCount_call, NULL, 0);
}

解説をすると1回目のGetTickCount()では0を返して,2回目のGetTickCount()では114514を返すだけの簡単な事をしています. 

差が450をはるかに超えています...Hookできた...!!

これでlevel1のBlue()をbypassできました~w

[level2] Charlie...

f:id:tsunpoko:20151228114629p:plain

 static inline int Charlie(){
        printf("\n[Level 2] Charlie ...");
        SYSTEM_INFO siSysInfo;
        GetSystemInfo(&siSysInfo);
        if (siSysInfo.dwNumberOfProcessors < 2) Detected();
        else return 0;
}

 SYSTEM_INFO構造体ってなんだ...調べた.

構造体: SYSTEM_INFO

プロセッサおよびメモリに関するシステム情報が格納されます。


定義

Type SYSTEM_INFO
    dwOemId As Long
    dwPageSize As Long
    lpMinimumApplicationAddress As Long
    lpMaximumApplicationAddress As Long
    dwActiveProcessorMask As Long
    dwNumberOfProcessors As Long
    dwProcessorType As Long
    dwAllocationGranularity As Long
    wProcessorLevel As Integer
    wProcessorRevision As Integer
End Type

 なるほど.

GetSystemInfo()ってなんだ...調べた.

GetSystemInfo

現在のシステムに関する情報(ページのサイズやプロセッサの種類など)を取得します。

 
 
VOID GetSystemInfo(
  LPSYSTEM_INFO lpSystemInfo  // システム情報
);

パラメータ

lpSystemInfo
1 個の 構造体へのポインタを指定します。関数から制御が返ると、この構造体に、システムに関する情報が格納されます。

戻り値

戻り値はありません。

 なるほど.

Charlie()は,GetSystemInfoでSYSTEM_INFO構造体メンバのプロセッサの個数が格納されているやつを読み取って1以下だと終了する!という認識です.

ということは,以下の様なコードでHookできるはず.

static DECAF_Handle getsysteminfo_handle = DECAF_NULL_HANDLE;
                     .
                     .
typedef struct{
        uint32_t call_stack[10];
        DECAF_Handle hook_handle;
}GetSystemInfo_hook_context_t;
                     .
                     .
static void GetSystemInfo_ret(void *param){
        GetSystemInfo_hook_context_t *ctx = (GetSystemInfo_hook_context_t*)param;
        hookapi_remove_hook(ctx->hook_handle);
        DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 10*4, ctx->call_stack);
        ctx->call_stack[5] = 2;
        DECAF_write_mem(NULL, cpu_single_env->regs[R_ESP], 10*4, ctx->call_stack);
        DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
        free(ctx);
}

static void GetSystemInfo_call(void *opaque){
        DECAF_printf("GetSystemInfo ");
        GetSystemInfo_hook_context_t *ctx = (GetSystemInfo_hook_context_t*)malloc(sizeof(GetSystemInfo_hook_context_t));
        if(!ctx) return;
        DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4*10, ctx->call_stack);
        /*
        ctx->call_stack[5] = 2; この関数の中で書き換えたら上手くいかない
        DECAF_write_mem(NULL, cpu_single_env->regs[R_ESP], 4*10, ctx->call_stack);  
        */
        ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], GetSystemInfo_ret, ctx, sizeof(*ctx));
} . . static void geteip_loadmainmodule_callback(VMI_Callback_Params* params) { getsysteminfo_handle = hookapi_hook_function_byname("kernel32.dll", "GetSystemInfo", 1, target_cr3, GetSystemInfo_call, NULL, 0); }

 戻り値がないからGetSystemInfo_call()の中でどうにかするんだな~って思い込んでました.(Sleep()も戻り値が無く,Sleep_call()でどうにかしたため)

でもMSDNちゃんと読むと

!!!関数から制御が帰ると!!!システムに関する情報が格納される

 みたいなこと書いてたので,もしかしてGetSystemInfo_ret()内で何かするんじゃねって思ったので,GetSystemInfo_call()でやってた事を,GetSystemInfo_ret()内のhookapi_remove_hook()の後にそのまま持ってきてやると出来ました.

ちなみに,これもともとプロセッサとか2つ以上割り当ててたらどうなるんだろう...

マシンに1つしか積んでないので出来ないから分からない.

[Level3] Delta...

f:id:tsunpoko:20151228131909p:plain

Graph Viewできない,なんでだろう

static inline int Delta(){
        printf("\n[Level 3] Delta ...");
        unsigned long NumberOfProcessors = 0;
        __asm{
                mov eax, fs:[0x30]
                mov eax, [eax + 0x64]
                mov NumberOfProcessors, eax
        }
        if (NumberOfProcessors & 0x1) Detected();
        else return 0;
}

 なんか,NumberOfProcessorsってあるからlevel2と同じなのかな...でも構造体とかないぞ...どうなんだろう.

とりあえずインラインアセンブラが使われているからムズそう.ゆうて簡単な命令しか書いてないけど.

----------------------2015/12/28------------------------------------

考えたけど思い浮かばないので,一旦この記事を終わる.できたら更新します.

とりあえずNumberOfProcessors_[ret/call]()作ったけどどうしていいのかわからない.

---------------------2015/12/29-------------------------------------

再考.

よくよく考えると別に関数呼出しているわけでもない...

そして,fsとか0x30とか__asmとか色々調べてたらいい記事を見つけてしまった.神.

pinksawtooth.hatenablog.com

TEB構造体,PEB構造体なるものがある事がわかった.これは結構でかい.

記事にも書いているがTEB構造体から0x30の位置にPEB構造体があり,そこからPEB構造体にアクセスできるらしい.

そして,コードから読み取る限りは,PEB構造体(fs:[0x30])から0x64の位置にプロセッサの数についての情報が格納されているらしい.(PEB構造体の25番目)

偶然,持っていた本にPEB構造体について書かれていた.

うさぴょんさん著の"デバッガによるx86プログラム解析入門(x64対応)"のP65にある.

googleの試し読み的なやつにもあったのでその画像を載せてみる.

f:id:tsunpoko:20151229131921p:plain

f:id:tsunpoko:20151229131943p:plain

100(0x64)/4で確かに25番目にNumberOfProcessorsとある.

こうして本に書いてあるが実際にはこのような情報は公表はされていないらしい.

よし,あとはコレを書き換えるだけだが,TEB構造体のアドレスの取得のやり方がわからない.頑張って探した.日本の記事は調べたけどだれも書いてなさそうっぽい,言語設定を英語にして探した.(検索結果が変わるかは分からないけど)

http://decaf-platform.googlecode.com/svn-history/r247/trunk/shared/windows_vmi.cpp

ページ内を0x30で検索してあげると,TEB構造体からPEB構造体へアクセスしているコードが載っている.

これを参考にDECAF_[read/write]_mem()するだけでいけそう.

static void NumOfProc(){
        uint32_t base = 0, peb = 0, peb_addr = 0, proc = 0;
        base = cpu_single_env->segs[R_FS].base;
        peb = base + 0x30;
        DECAF_read_mem(NULL, peb , 4, &peb_addr);
        DECAF_read_mem(NULL, peb_addr + 0x64 , 4, &proc);
        proc = 2;
        DECAF_write_mem(NULL, peb_addr + 0x64 , 4, &proc);
}
                 .
                 .
static void geteip_loadmainmodule_callback(VMI_Callback_Params* params)
{
                 NumOfProc();
}

いえーいbypassed
疲れた〜楽しい〜w

[level4] Echo...

f:id:tsunpoko:20151229134939p:plain

 

static inline int Echo(){

        printf("\n[Level 4] Echo ...");
        __try{__asm{cmpxchg8b fs:[0x1000]}}
        __except (1){Detected();}
        return 0;
}

 もう限界を迎えた.

なんか例外処理みたいな事してるな~くらいしか考える頭がない...疲れた(甘え)

 今の方針は明確ではないけど,"cmpxchg8b"命令がF00F Bugというバグを引き起こしている命令っぽいのでそれをどうにかするのかなって...そろそろ本気でつらいw