Notes

Do not read codes but patch binary.

simple build command

https://github.com/Hiroshi123/bin_tools/blob/master/src/tools/build.c

暇だったので書いてみた。 makefile風のfileとtargetを指定して実行する.

windowsでのみbuildできる。

元々,windows向けのld書こうと思ってたんだけど,次いでに作ってみた。

やっていることは,

  1. 変数の登録

  2. ruleの登録

  3. ruleの依存関係解析

suffixルール,wildcardは評価可能.

依存グラフの評価は、深さ優先(多分それしかない)

パーサっぽいのは昔から書いてたので慣れているはずだが、やっぱりcで書くと中途半端になるな。

Dig into LdrInitializeThunk

On my previous post, I posted piece of code which ought to work out for a dll injection on the stage where kernel32.dll has not been mapped yet. It looked worked out apparently, in fact it missed a point.

The point is kernel32.dll will be loaded on a process if its subsystem is Windows GUI/CUI even in the case that the none of the dll of the executable file depends on kernel32.dll. It is true at least on Windows10 17763.737.

The question which might be asked is when & how it will be loaded? To answer it correctly, I need to cover a little bit of overall architecture on which any processes will be started off on user land, which will be done on last stage of process creation(I'm talking about Windows Internals 6 edition Chapter 5 Flow of createprocess Stage7).


1. First execution on user land of a process

When you've just started exploring around here, you might think it is main/wmain that an application defines. Then, after you know more, you know a set of runtime(often called as CRT) routine will be involved before main just like libc will do on do depending on linker options.
But, strictly these are not the first footprint on user land. On the last stage to create a process, kernel will put APC (Asynchronous procedure call) on top of a launching thread with KeUserApc. This APC will be initiated from an address of one of exported function of ntdll ; LdrInitializeThunk. If you often use windbg, you should know it as first breakpoint will be automatically set on one of its callers(PspCreateInitialize) when the debugger creates a process. If you confirm its presence in another way, set a tls callback as a simple example.

#pragma comment(linker, "/INCLUDE: tls_used")

void NTAPI TlsCallBac(PVOID h, DWORD dwReason, PVOID pv);

#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_thread_callback = TlsCallBac;
#pragma data_seg()

void NTAPI TlsCallBac(PVOID h, DWORD dwReason, PVOID pv)
{
MessageBox(NULL, "In TLS", "In TLS", MB_OK);
return;
}

This allows MS Linker to set the function address on an entry directory(IMAGE_TLS_DIRECTORY) pointed from one of image directory entry(IMAGE_DIRECTORY_ENTRY_TLS) on a PE header.

After its execution, you will find this tls_callback had been called multiple times before main is executed(1 is for a process creation, the others for a thread creation given 2nd param dwReason).

From Where exactly calls these? If you start a debugger of this process, then the initial automised breakpoint forwards to first execution of tls callback. Then you hit p which is step over & over(20-30?) tls callback for a process creation notification will be called at some point.
This point is on stepping over LdrpTlsInitialize(stack is LdrInitializeThunk/LdrpInitialize/_LdrpInitialize/LdrInitializeProcess/).
And the rest of call should be fired up after ZwTestAlert(stack is LdrInitializeThunk/LdrpInitialize/_LdrpInitialize/LdrInitializeProcess/) with additional dozens of step over.

Not only initial bp of a debugger and tls callback execution but lots of crucial stuffs are involved on this LdrInitializeProcess.

Followings are the examples.

  1. Kernel32.dll loading
  2. SysWow64 configuration
  3. .Net metadata configuration
  4. Shim engine initialisation
  5. Statically mapped library loading

Each of them is as substantial as being articled as a separate topic, I cannot cover all of it in a detail now.

What I will emphasise here is this code area is executed not only process initialisation but thread initialisation as APC.

Roughly, the call graph is as follows.

* LdrInitiallizeThunk
 * LdrpInitialize
  * _ LdrpInitialize
    * LdrpInitializeProcess
      * .. 
  * LdtpInitializeThread
    * ..
  * TestAlert
 * NtContinue

you might encounter previous tls callback was called multiple times. This is because loading(perhaps mainly resolving) DLL is done by setting multiple threads on Windows10 and no matter when and what threads are launched, this APC routine will be passed through.

Once you are realised it might be beneficial to examine this APC routine especially process creation, it is nice to know how you dynamically analyse initial process routine because a debugger like windbg will come out on the way not together with its first point. In other words, you can deal with the case where a malware tricks the routine and prevent initial debugger attach.


2. Abusing APC routine

If you start a process with general CreateProcess with its creation flags as CREATE_SUSPENDED, kernel will stop launching a thread and it is before staring its APC luckily.

You can confirm that starting the process which has tls_callback by staring with CREATE_SUSPENDED flags does not execute the tls callback until the thread resumed or another thread is run.

From another point of view, rewriting the flow of this APC potentially will be a very strong defence evasion from an attacker side. You might be able to achieve similar functionality without relying on heaven's gate, for instance.

Now, stepping back to the head of this article, my question is could I have a process without kernel32.dll rewriting this APC routine before process creation. It requires me to be a little bit more familiar with it.

But for now, let us start from much more simpler.

To confirm this routine is called and can be fookable, set a little detour on the head of LdrInitiaizeThunk.

First, check the text area of LdrInitiaizeThunk of a child process letting its main thread suspended.

STARTUPINFO info = {sizeof(info)};
PROCESS_INFORMATION processinfo;

if (CraeteProcess(NULL,"notepad.exe",NULL,NULL,1,CREATE_SUSPENDED,NULL,0,&info,&processinfo)){
void * moduleNtDll = GetModuleHandle("ntdll");
void* ldrInitThunk = GetProcAddress(moduleNtDll, "LdrInitializeThunk");

VirtualProtectEx(processInfo.hProcess, ldrInitThunk - 4, 6, PAGE_EXECUTE_READWRITE)

uint16_t dd = 0;
if (ReadProcessMemory(processinfo.hProcess, moduleNtDll, dd, 2, 0))
    printf("ldrpinitializeThunk:(addresss)%x,(value)%x",dd,*dd)
}

This code assumes NTDLL will be mapped in a same virtual address on a child process with its parent process. You should get first 2 bytes 0x40 0x53 (push rbx) of "LdrInitializeThunk".

then add a tiny dropping in on the way,

uint8_t dd[] = {0xeb,0xfa};
uint8_t ddd[] = {0x40,0x53,0xeb,0x02};

DWORD old = 0;
VirtualProtectEx(processInfo, ldrInitThunk - 4, 6, PAGE_EXECUTE_READWRITE, &old)
WriteProcessMemory(processInfo.hProcess, ldrInitThunk, &dd, 2, 0);
WriteProcessMemory(processInfo.hProcess, ldrInitThunk - 4, &ddd, 4, 0);

On this ntdll, there was a few bytes which had been left for padding above LdrInitializeThunk. 4bytes among of it is jumped on. Now the code is altered as

    push rbx,
    jmp 0x02
LdrInitializeThunk:
    jmp 0xfa(6 instruction back)
    ....

After that, you can confirm

ResumeThread(processInfo.hThread)

or CreateRemoteThread still works.


Since you make sure that APC routine is called, let us add a counter when it is called.

uint8_t ddd[] = {
       
       0x40,0x50,// push rax
       0x65,0x48,0x8b,0x04,0x25,0x60,0x00,0x00,0x00, // mov rax,[gs]:60
       0x80,0x40,0x04,0x01,  // add [rax+0x04],0x01
       0x40,0x58, // pop rax
       0x40,0x53, // push rbx
       0xeb,0x02  // jmp rip + 0x02
};
// jump to the head of the detour 
uint8_t dd[] = {0xeb, 0x100 - sizeof(ddd)};

DWORD old = 0;
VirtualProtectEx(processInfo, ldrInitThunk - sizeof(ddd), sizeof(ddd) + sizeof(dd), PAGE_EXECUTE_READWRITE, &old)
WriteProcessMemory(processInfo.hProcess, ldrInitThunk, &dd, sizeof(dd), 0);
WriteProcessMemory(processInfo.hProcess, ldrInitThunk - sizeof(ddd), &ddd, sizeof(ddd), 0);

This assumes the parent and the child process is instructed as x64. On x64 PEB, there is 4 bytes padding space just after 4bytes from its head for compatibility with x86. This code stores records how many times the APC routine was called.

After launching a thread with CreateRemoteThread, check it out with

PROCESS_BASIC_INFORMATION* pbi = malloc(sizeof(PROCESS_BASIC_INFORMATION));
void* p1 = malloc(8);
NtQueryInformationProcess(processInfo.hProcess, 0, pbi, sizeof(PROCESS_BASIC_INFORMATION), p2);
void* p2 = malloc(8);
ReadProcessMemory(pp1, p1->PebBaseAddress, &p2, 8, 0)
printf("PEB padding byte %x,%x\n", p2,*p2);

(Do Statically link with ntdll for execution for this code) On my environment, it says a thread by CreateRemoteThread called this APC routine 7 times.

I will leave a task to investigate why it was called 7 times for you and further investigation of this APC routine for better understanding.


On previous example, you can observe kernel32.dll was loaded when you run CreateRemoteThread while suspending main thread. This means no matter where a thread comes from, the first thread on user land will be in charge of process initialisation which consequently loads kernel32.dll.

When you dig a little bit more where exactly kernel32.dll was loaded , you can find a static value named on Kernel32InitThreadFunction on ntdll. This represents the address of one of exported function; Kernel32InitThreadFunction on kernel32.dll. In fact, this address is jumped from RtlUserThread which is entry point of every thread. Not be confused with the address of thread you specified when you call NtCreateThreadEx. Your value is passed as an argument of RtlUserThreadStart, and called from Kernel32InitThreadFunction. Anyway, if you do not want to map kernel32.dll, your executable image should be modified to meet 2 requirements.

  1. Do not call APC routine to load kernel32.dll
  2. Do not call when Kernel32InitThreadFunction since kernel32.dll was not loaded.

To meet 1, You can bravely ignore all of procedure of _LdrpInitialize on LdrInitializeThunk and directly calls main thread by ZwContinue. To meet 2, just rewrite the binary of RtlUserThreadStart.

Shell code is presented next.

uint8_t ddd[] = {
       
       0x40,0x50,// push rax
       0x65,0x48,0x8b,0x04,0x25,0x60,0x00,0x00,0x00, // mov rax,[gs]:60
       0x80,0x40,0x04,0x01,  // add [rax+0x04],0x01
       0x40,0x58, // pop rax
       0x40,0x53, // push rbx
+     0xb2,0x01, // added!!
-     0xeb,0x02  // modified!!
+     0xeb,0x13  // modified!!
};

You can directly jump on the call to ZwContinue setting 2nd argument as 1(this is on original code) without stepping in LdrpInitialize.

Then kernel is notified the APC routine had been done, and start the thread from RtlUserThreadStart.

uint8_t dddd[] = {
       
       0x65,0x48,0x8b,0x04,0x25,0x60,0x00,0x00,0x00, // mov rax,[gs]:60
       0x80,0x40,0x04,0x01,  // add [rax+0x04],0x01
       0xb9,0x00,0x00,0x00,0x00, // thread handle which can be 0
       0xba,0x00,0x00,0x00,0x00, // exit status
       0x4c,0x88,0xd1, // mov r10,rcx
       0xb8,0x53,0x00,0x00,0x00,0x00, // set syscall num for NtTerminateThread
       0x0f,0x05, // syscall
       0xc3
};

void* rtlUserThreadStart = GetProcAddress(moduleNtDll, "RtlUserThreadStart");
DWORD old = 0;
VirtualProtectEx(processInfo.hProcess,rtlUserThreadStart , sizef(dddd), PAGE_EXECUTE_READWRITE,&old)
WriteProcessMemory(processInfo.hProcess, rtlUserThreadStart, &dddd, sizeof(dddd), 0);

What this does is incrementing a value of PEB + 0x4 which is blank, and call NtTerminateThread. If you check the behavior of the child process out with ProcessMonitor or something equivalent, You'll realise it does not map kernel32.dll.


Summary

There are lots of cases that create a process letting its main thread resumed, and rewriting its memory from the parent. On this stage, only executable image file and ntdll are mapped. Replacing the value of PE is often called PE injection. But, you should also pay enough attention to replacement of section of ntdll as it can potentially bypass Anti-Virus DLL and alters how executable will behave.

DLL injection with just ntdll

If you want to step back to the days before the high level languages had been prevailed, nevertheless still want to write somehow practical in a sense in this era, writing a piece of injection code is a good target.

Windows supports functionality where you can read or write process memory of other process(ReadProcessMemory/WriteProcessMemory) and set a thread which runs codes on other process (CreateRemoteThread).

If you attempt to set a remote process under your control, injecting your own dll onto the process is one of the easiest way. LoadLibrary & GetProcedureAddress on kernel32/kernelbase will handle nicely.

If you assume the remote process holds virtual address which mapped identically on its own process & kernel32.dll is mapped, calling them will be a piece of cake. But if neither of them met on a process, how do you deal with it?

The answer is you can call LdrLoadDll (& not mandatory butLdrGetProcAddr ) on ntdll instead on the remote process. But how can you call them from entire scratch after setting a thread on the process.

To let the work done, you need to keep in you mind following 2 stuffs.

1. Search a module handle & a function address on it
2. Call a function on pre-built dll properly.

if you do not want to read anymore, but just want to know, take a look at here (https://github.com/Hiroshi123/random)

1. Search a module handle & a function address on it

When I investigated a famous post-exploitation tool named mimikatz(https://github.com/gentilkiwi/mimikatz), I knew their essential functionality(sekurlsa) for LSA dumping was supported by memory reading on a remote process (readprocessmemory).

Roughly, the dumping is done as follows.

  1. Read PEB(process environment block)

    • OpenProcess(query_limited,,)
    • QueryInformationProcess(Basic_Information)
      gets you the base of peb.
  2. Read PEB_LDR_DATA

  3. emulate loaded dll module

  4. Reach each image base & read the image information

lsass.exe holds lots of dll, and some of them are security support providers which have some hidden credential information on memory. After reaching image mapped section, mimikatz steals credentials based on pre-examined.

This scheme is partially useful in terms of searching a loaded module handle & a function address given an ansi-string query. Namely, it means you first access PEB acquired from its address by NtQueryInformationProcess , then go to image base of each mapped dll in this case, ntdll, then go to image export directory to find function address.

Note that unless you are in a condition where particularly not been allowed to write-and set thread on a remote process, searching through ReadProcessMemory is not favoured. This is because the performance is worse in this way. You need to accumulate lots of its calls because every time you come across another pointer access on remote process, you have to issue another call for it.

Setting a or multi remote thread is letting things done faster.

Below is the function _get_ntdll_faddr_1 which serve its purpose when you want to search a specific address of function on ntdll given a string query which might match the function name.

uint8_t* v = _get_ntdll_faddr_1("LdrLoadDll");
printf("module handle : %p\n",v);

You provide an ASCII name of a function as a query on ntdll, then it gets you back the address if it exists. You might think this is almost identical with GetProcAddress . Indeed, the difference is you can execute it in a condition on a remote process where you are not sure when the address of GetProcAddress is.

Let's dig a little bit about its implementation.

It is written by assembly and memory access on static area is not allowed on it for torelation to be executed with a thread on a remote process.

Here is a function tree.

f:id:vrodxda:20190711091922p:plain

That is,

  1. Grab the module handle of ntdll(_get_ntdll_handle)
  2. Get a function address which corresponds to a given query(_get_faddr_from_module_handle )
    • Reach an image export directory from a module handle on an image.(_get_export_entry_from_handle)
    • Get a function address by name combining tables on an image export directory(_get_faddr_by_name)
      • find the matched function name on an export name table and get the index.(_get_findex_by_name)
      • Get the function address accessing both name ordinal table & address table.(_get_faddr_by_index)
_get_ntdll_handle:
    ;; mov rax, [fs:0x30] for 32bit
    ;;; 1
    mov rax,[gs:0x60]
    ;;; 2
    mov rax,[rax+0x18]
    ;;; 3
    mov rax,[rax+0x10]
    ;;; 4
    mov rax,[rax+0x10]
    ;;; 5
    mov rax,[rax+0x20]
    ret

Note this code is only for 64bit.

  1. Get gs register + 0x60. (Get PEB)

  2. Get PEB_LDR_DATA

  3. Get load order search path

  4. Go to next pointer as first one is image of exe itself

  5. Grab the image base of the dll(coming one after image section for exe is always ntdll)

Fairly simple! ,consuming only one register 30bytes written codes, no extra heap allocation.

After you get to the ntdll image base, no more worry to be needed to miss the function address.

Go to image export directory,

_get_export_entry_from_handle:
    mov rax,rcx
    mov rbx,0
    mov ebx,[rax+0x3c]
    add ebx,0x88
    ;; add rax,0x18 + 0x70
    mov ebx,[rax+rbx]
    add rax,rbx
    ret

and get the function address by the provided function string.

iterating through image export name table -> ordinal table -> function table.

2. Call function on pre-built dll properly.

Your assembly can by no means be comprehensive by itself as pre-built codes had been generated by a compiler which follows a set of rules which are named calling conventions. Since most of codes are assumed to be built on MS-build, you need to understand Microsoft calling convention.(even when you directly call system call).

When you call a function on pre-built dll or .sys, what needs to be prepared from caller side depends on how many args are required.

  • No args
    With no arguments things are easy. Just set the function address on the code where you want to jump.
call_no_args
        push rbp
        call rax ;; where rax is the address of callee
        pop rbp
        ret

You do not need to push rbp but rsp needs to be always 0x10 aligned in total. That is because call will shift rsp upper to negative side, and pushwill compensate for the rest of 8byte.

  • Less than 4 args
    With less than 4 arguments, a bit more complicated.

Put 1st on rcx,2nd, on rdx, 3rd on r8, and 4th on r9.
Do keep in mind that you still need to allocate 0x20bytes on stack even though all of registers are passed via registers. This 0x20 bytes are called shadow space where callee will be in charge.

_call64_less_than4_arg:
    push rbp
    mov r9,[_arg4]
    mov r8,[_arg3]
    mov rdx,[_arg2]
    mov rcx,[_arg1]
    sub rsp,0x20
    call [_f_addr]
    mov [_ret],rax
    add rsp,0x20
    pop rbp
    ret
  • More than 5 args
    The rest of args are conventionally allowed to be passed via a register but on a stack.

They should be allocated before shadow space is allocated. Treat that shadow space belongs to callee, but the stack for args belongs caller as the value on shadow space can be modified by callee but the value of arguments are not(memory pointed by them could be though).

In terms of stack allocation, if the number of args are 5, treat it as 6, if 7 then 8.
That means odds args always holds additional blank 8byte filling just before shadow space.

6 args case,

_call64_6_arg:
    push rbp
    sub rsp,0x8
    mov rax,[rsp]
    mov rax,[_arg6]
    sub rsp,0x8
    mov rax,[rsp]
    mov rax,[_arg5]
    mov r9,[_arg4]
    mov r8,[_arg3]
    mov rdx,[_arg2]
    mov rcx,[_arg1]
    sub rsp,0x20
    call [_f_addr]
    mov [_ret],rax
    add rsp,0x20
    pop rbp
    ret

Number of arguments can be long as long as the rest of stack lasts. To generalise it, stack allocation for more args should be done on a loop. Last but not least, use only volatile registers so that other functions assumes the rest of them as non-volatile ; as it was before call.

Codes are here(https://github.com/Hiroshi123/random/blob/master/call64.asm).

x86-64 emulatorを作っていて思ったこと

平成最後の日ということで、てきとーに何か書き残しておく。

マルウェア解析をしていると、マルウェアがマシンを破壊してもよいように、動的解析では仮想環境を使うが、それも面倒な時がある。 その為、自作x86エミュレータを暇な時にせっせと作っていたのだが、ようやく、骨子ができてきた。


作った動機

動機としては、仕事で自分用に使うという他に2つ。

  1. エミュレータを大量のjump文抜きで書きたい。
  2. native用のjvmtiみたいなものを作りたい。

1に関してだが、vmやemulatorのコードは長い関数内に大量の条件文があり、それが嫌だということ。 分岐の連続で書くと、大量のopcodeのパターンマッチとなり、最初にマッチする命令はいいが、あとに定義された命令は、当然処理は遅くなる。 (追記:gccだと-O0でswitch文内のcase文が4つ以上だとData Sectionにjump tableを構築する様です/ですので後にマッチするモノが遅くなる事はないようでした) また、可読性もよくないし、命令の網羅率(どこまで書いたか)もぱっと見わかりずらい。

その為、以下の様に実装した。

  1. 関数テーブルを用意し、各処理を書く。

  2. opcodeがそのアドレスを指定する様に、レジスタの値を書き換える。

  3. 変更されたレジスタを絶対アドレスとしてcallする。

アセンブラで書くと以下の様。

    mov rax,[_context._opcode_table]
    mov rbx,0x00
    mov rdx,[_rip]
    mov bl,[rdx]    
    shl rbx,0x03
    adc rax,rbx 
    call [rax]

[context.opcode_table]は関数テーブルの先頭を指し、ripの命令をfetchしてきて、address byte分(この場合8byte)掛けて,その関数テーブル先頭と足し合わせる。 最後のcall [rax]が絶対callとなり、別途定義している個々のopcodeの処理に移る。

なお、0x0fが付く拡張命令に関しては、[context.opcode_table]を別途設けることで、対応している。

2のjvmtiに関しては、あまり紹介記事も無いのだが、javaのinstrumentationツールで20年近く前からある。JITの特性を生かして、様々なレベルで、コールバックイベントをくれる。例えば、JITがコード生成する度、生成されたnativeの関数が呼ばれる度、などに任意にコールバックイベントをよこしてくれる。初めてこのツールを使った時、nativeのコードに対しても同じような機能があれば、と思った。

この様なinstrumentationは、javaがvmという形態だからこそできること、では無い訳で、nativeでもできる訳だが、これは、生のCPU上で実行する前提だと難しい。 例えば、各関数が呼ばれる度に何かをするということは、デバッガの助けなしにはcではできないだろう。私は、あまりデバッガは好まないので、自分で作ることにした。

今回、emulatorを作る動機として、最終目標として、既存OSを動かす、、というより、多少遅くても、細かいレベルでプログラム解析をすることがあった。 その際、ただのemulatorだと、emulatorが生成するコードに制約が無く、締まりが悪い。そこで、qemuの様に、emulatorがすぐ各命令を実行するのではなく、実行するprimitive命令を用意した。x86-64は特に命令の意味的な重なり(addがmovを含む等)が多く、コード生成型でないと綺麗に書けない気がする。


直交性のある処理をしっかり書く

書いている中で、一番重要だと思ったことは、直交的な処理(例えば、mod_reg_rm)の処理を適切に書くこと。emulatorを書くというと、個々のinstructionに目を奪われがちだが、重要なのは、それらを還元し、その共通化部分が保守性が高くかけているかということだとおもった。 この部分、最初、実は、cで書いたが、デリケートな部分でもあるので、最終的にアセンブラで書いた。

例えば、opcode 0x83の処理のコード。

_0x83_arith_imm:
    
    push rbp
    mov r8,_op01_f_base 
    call _set_imm_op_base
    call _get_mod_op_rm
    call _set_scale_index_base
    call _fetch_displacement_by_mod
    call _load_rm_by_mod
    call _mov_res_to_arg1
    call _fetch8_imm_set_to_arg2
    call [_context._imm_op]
    call _mov_rm_to_arg1
    call _mov_res_to_arg2
    call _store_or_assign_arg1_by_mod
    pop rbp
    ret

個々の関数は長くなるので、割愛するが、多くのopcode(演算系,mov系,shift系)は、

  1. mod/reg/rmを取得
  2. scale index baseを計算
  3. displacementをrmに加算
  4. rmをロード
  5. immidiate値を取得
  6. 演算(opcode自体|regの代わりに)
  7. rmをストア

のような一連の基本的な処理の中で、一部やらない処理があるというパターンだ。そのようなパターンをdissassembleしながら見つけていくと、書き初めは重いが、後で楽になる。


host/guestアドレス変換

エミュレータで特に、面白いhost/guestアドレス変換。基本的に、ページ単位で、alignmentがズレない様に実装というオーソドックスなものになった。 なお、最初、各命令のmod=0b00/0b01/0b10の場合、つまり、メモリアクセスが発生する度に、この変換が必要だと、思ってたが、実は、rsp(esp),rip(eip)を初期状態でホストアドレスに設定しておけば、レジスタ間接のメモリアクセスは、変換が殆ど必要無い。 変換が必要なのは、

  1. 絶対アドレスへのアクセス
  2. guestアドレスがページを跨いで(別のページに相対的に)アクセス

1に関しては、x86(32)/64で絶対アドレスの頻度が変わってくる為、相対アドレスの多いx64の方が、ホストアドレスを予めレジスタ値に設定しておくインセンティブは大きいと思う。

2の場合は、例えば、ページが4096byte単位で、ゲストpage1,ゲストpage2が並んで確保されているが、ホスト上では、対応するページが並んでいないとする。 今、ゲストpage1の最終instructionを読み取り、次のinstructionに進みたいとする。そこで、hostのメモリを前提として、実装していると、次のpageがなかったり、別のpageを踏んだりする。その為、page境界の相対アドレスでは、常に、host address -> guest address -> guest別page -> guest別pageに対応するhostのpageちった処理をする必要がある。


アセンブリについて

最後に、各命令の処理を含め、重要な部分は、アセンブリで書いたが、アセンブリを書く上で、先にやっていた方がよいと思った事が2つあった。

  1. 機械語をそのまま書く
  2. 自作デバッグ関数を作る

1についてだが、私は、先に機械語を直書きして、それを実行するという書き方で、多少プログラムを書いていた。それも中々醍醐味があるのだが、書いていて辛いのが、 相対オフセットの計算を自分で数えなければならないこと。この、"数える"という作業が、間に入ってくると、はっきり申して、思考の中断である。あと、mod/reg/rmのところなど、2進数->16進数も、結構間違えたりする(これはできる人には苦にならないのかもしれないが..)。それに比べると、アセンブリ言語は楽だ。また、高級言語の生成先としてアセンブリをみるだけでなく、機械語の生成方法として使う、という観点が得られると、モチベーションの持ち方が多少変わってくる気はする。

2について、機械語を書いてよくわかったのだが、書いてすぐ検証するまでに、検証ポイントが多くなると、自然と、手が止まり、考えている時間が多くなる。そうすると、私の場合表向き全く進捗がないことが、逆に不安になったりする。人によって、トップダウンで考えて、正しい機械語を、一気に書けるタイプの人はいい。しかし、私の場合、 やはり、下らないミスも多く、やはり、間違えてもincrementalにやって行った方が効率が良いとわかった。そんなこんなで、アセンブリを書き始めた時、真っ先に、必要だと思ったのは、レジスタの中身をプリントする関数だった。これを書いた後で、開発の効率が速くなった。

print:
    push rbp
    lea r10,[_reg_size8+0x0a]
    
    mov r9,r8
    call print1

    mov r8,r9
    sar r8,8
    mov r9,r8
    call print1

    mov r8,r9
    sar r8,8
    mov r9,r8
    call print1

    mov r8,r9
    sar r8,8
    mov r9,r8
    call print1
    call _reg_save
    call _write
    call _reg_regain
    pop rbp
    ret
    
print1:
    push rbp
    sub r10,1
    mov r9b,r8b
    call print1.f1
    mov byte [r10],r8b
    mov r8b,r9b
    sar r8b,4
    call print1.f1
    sub r10,1
    mov byte [r10],r8b
    ;; mov byte [2+reg_size8],0x31
    pop rbp
    ret
    ;; and r8b,0xf0
.f1:
    and r8b,0x0f
    cmp r8b,0x0a
    jae print1.more_than_0x0a
    jmp print1.less_than_0x0a
.more_than_0x0a:
    add r8b,0x57
    ret
.less_than_0x0a:
    add r8b,0x30
    ret

_reg_save:
    mov r12,rax
    mov r13,rdi
    mov r14,rsi
    mov r15,rdx
    ret

_reg_regain:
    mov rax,r12
    mov rdi,r13
    mov rsi,r14
    mov rdx,r15
    ret
    
_write:
    ;; mov rax, 0x2000004   ;write
    mov rax, 0x0000001  ;write
    mov rdi, 1 ; stdout
    lea rsi, [_reg_size8]
    mov rdx, 0x0b
    ;; rcx,r8,r9 is another register
    ;; mov rdx, _reg_size8.len
    syscall
    ret

通常の言語と同じように、どこでもprintを挟めるという安心感があるとないとでは開発スタイル、速度も変わってくる。 アセンブリからprintfなども呼べるが、依存ライブラリの必要性のなさや、カスタマイズ性の高さから、アセンブリを書く場合、自分用utilを多少持っていると、捗ると思われる。

objtestについて

3月からお仕事でmalware解析やるので、役立ちそうなbinary解析ツールを作っている。

その中で、objtestというコマンドが割と自分の中でいいものができたなっという気がするので、 簡単に紹介。

qemuやllvmなど、大きなプロジェクトのソースを読む場合って、それの簡単なunit test書いたりするのすら一見、結構面倒だったりする。 静的にlinkした場合、ものによってはbuildがやたら遅くなったり、動的にlinkする場合でも、わざわざテストしたい.oを.soに替えてlinkし、.soの依存するlibraryを全部loadしなければならない。 大きなプロジェクト程、個々のobject file間の依存性が大きくなり、依存性が大きくなる程、小さな単位のテストが難しくなる。

そこで、build時に生成される静的relocation前のrelocatable objectを依存する関数をmock化することでテストすればいいんじゃないかっと前々から常々思っており、やってみたら、かなり小さい分量である程度の所まで作れた。(https://github.com/Hiroshi123/bin_tools/blob/master/src/tools/objtest.c)

発想としては、何かをcompileする。これは、cでもc++でもrustでもgoでもよい。そして、取り敢えず、relocation前のobjectが欲しい。relocation前とはtargetマシンの命令語の羅列なのだけど、calleeのアドレスが後で埋めて下さいっとブランクになっている形。

通常この後、実行binaryを作る過程で、linkerがこのblankを埋める作業を行うのだけれど、objtestは、その前のrelocatableなobject(test対象と, test & mock用の2つ)を使い、それを"強引に"実行する。"強引"な実行とはつまる所は以下のコード

  asm("call *%0" : : "r"(get_current_meta_addr));
  asm("mov %rax,%rdi");
  asm("call *%0" : : "r"(r));
  asm("mov %rax,%rbx");
  asm("call *%0" : : "r"(get_current_meta_addr));
  asm("mov %rbx,(%rax)");

部分的に書くと分かりにくいのだが、3行目のcallで変数rのアドレスに%eipを設定。このrには、object fileのsymbol tableから見つけた、テスト対象の関数が入る。 その前と後の部分は、テスト関数に、引数(%rdi)のポインタを与え(1,2行目)、テスト関数の戻り値(%rip)を獲得する(5,6行目)。 inline assemblerだと、変数とregister間の受け渡しが出来ないみたいなので、一回heapメモリに渡したい値を置いて、それで、特定のレジスタ(この場合,%rdiと%rax)を読み書きしている。

ところが、relocationしていないと、テスト対象の関数に制御を移したはいいものの、所々が0xe8 0x00 0x00 0x00 0x00みたいなままなので、0x00に%eipを移してしまい、場合によってはfailする。つまり、線路は大枠あるのだが、所々に橋が架かっていない上での見切り発車みたいな雰囲気。 というわけで、橋を架けるのが、relocationだが、ここで、全部正しく橋を架ける(別の正規なobject fileを持ってくる)と、多大な費用(build時間)が掛かるので、適当にでっちあげるのがmock化。 この作業なんだけど、基本的に、relocation前のobject fileが橋を架けてないlistを持っていて、その部分を"適当"に"つぎはぎ"して、その上で実行すればok!(この部分は興味があればcodeを読んで下さいm__m)

因みに、関数のmock化だけでなく、c++などobject指向言語のclassのinstanceのmock化も行える。c++のメンバ変数は、第一引数が、classへのpointerになっており、c言語でそいつを呼び出す場合は、そこを既にallocateされた領域の先頭にし、その先頭からobjectのサイズ分好きな値を書いて関数callの引数に渡せば、関数内のメンバ変数アクセスは全てそいつらを見に行く。

-----test subject ----

class A {
   int a = 1;
   void f1() {return a;}
}

---- test function----

int* a = 0x??;already allocated area
*a = 2;
f1(a); needs to be mangled in reality to discern to which class it belongs

この方法で、テストを書くと大変なのが、全てのcallee分だけmock関数を用意する処。しかし、それが面白い処でもまたある。

これが、個人的には、究極的に柔軟なソフトウェアのデバッグ手法だと思っている。

call graph生成toolについて

年明けてから、ふとした思い付きで、call graph生成ツール(https://github.com/Hiroshi123/bin_tools)を作っている。 目的としては、windowsとmacのsourceのないdllを効率良く理解することを目的としている。 現状はelf向けのもの(中途)のみできてる。

で、ある程度出来上がったら、まとめを書こうと思ったのだけれど、いくつか壁があって、この辺で、作業の途中ログとして、 少し書いておく。(満足いく形で、全部出来たら再更新するつもり)

コアなアイデアとしては、x86で,callという命令があるのだけど、それが、多くの場合、 0xe8 +現状のコード実行位置からの相対offset で表現されるので、binaryをparseして、e8が見つかったら、次の4byteみて,call先となれる対象か否かを確認する。 "call先となれる"というのは、各関数の先頭を意味している。 "各関数"とは、開発者が定義した関数,または、他のdllに定義され、動的linkされる関数としている。

qemuみたいに全部書く(https://github.com/qemu/qemu/blob/master/target/i386/translate.c)のは少々重いが、かといって、 instructionの区切りが分からないと0xe8が求めているopcodeなのか他の命令のoperandなのか分からない。そこで、0xe8の次4つのbyte からcall先のアドレスを算出し、本来callされるアドレスか否かを調べることで、e8がcallを意味していることをかなりの精度で確信できる。 我ながら、うまいこと思い付いたものだ。

関数の頭のbyteをとるには、まず、object formatのシンボルテーブルをみる。 ここで、value(elf,coff,macho共通)という値に、関数のfileからのoffset情報が入っている。 ただ、そのfileから動的linkされる関数の頭のアドレスは当然シンボルテーブルに入ってない。 本当の遷移先はまだ決まってない(実行時にloaderが決める)からだ。 ここで、少し、動的linkのお話しだが、loaderは直接0xe8後の4byteを書き換えるなどという乱暴なことはしない。 そんなことをすれば、コード領域を書き込み可能になってしまう。 そこで、compilerは、別途書き込み可能な形でmapされた領域にあるアドレスに書いてあるデータを次の%ripとして 読むという処理を入れる。0xff 0x25 + 4byte(%rip相対)で表現される。 そして、この理由は実は、よくわかってないのだが、この処理を各呼び出し口が実行するのではなく、この6byteをcallする 処理を各呼び出し口は持っている。 このアドレスを求め、さらに関数の名前と結び付ける手段はobject format毎に異なる。 elfだったら、relocation tableをみて、書き換え前のgotの値が、pltの次のアドレスを指しているか、plt.gotがあったら、 それを直接dynamic link tableと関連付けるという処理だし、mach-oだったら、stubs,__symbol_ptrというsectionを使って 似た様なことをやる。

こうして、シンボルテーブルを先頭アドレスと名前付きで求めたら、あとは、それを元に、実際の関数の先頭アドレスから、binaryを 読んでいき、0xe8を見つけたら、他の先頭とマッチするかを確認すれば良い。

ここで、小さな問題と大きな問題が発生。 小さな問題だが、elfはご親切に関数のサイズをシンボルテーブルに設定しているのだが、coffはaux(補助的、つまり任意な形)として、mach-oのnlistに関しては全くこの値を保持していない。 その為、coff,mach-ohは、各関数の長さを知る為、次の関数から登ってc3を見つけ出す、という処理が必要な模様。 大きな問題だが、これは、formatの話しでは無いのだが、関数のcallにもう一つの0xff + registerの値というのがあり、それを吐き出すcompilerが時々いる。0xffを0xe8と同じ容量で、detectしようとすると、検知ミスと実行時間の増大に繋がる。binaryに現れる0xffの頻度は0xe8より圧倒的に高いし、レジスタの中を確かめる処理も追加する必要がある。幸いこれは、mingw64の一部のdll関数の呼び出しだけなので、今の所は無視しているのだが...

何れにせよ、この静的解析とも動的解析とも付かぬ遊びだが、中々面白い。 もし、ご興味があれば、是非、自分で作ってみることをオススメする。

追記:2019:2/17

静的linkerの仕組みを調べてたら、あるobjectfileにある関数のcaller,calleeの全ての関係って、上記の様なcall先を探す亜流な手法でなくても、compiler(cc -c .c -o .o)が提供してくれるsection情報を元に全て分かる様になっているよう。 というか、そもそも、call graph生成って静的linkerのrelocationの副次産物に過ぎない。

どういうことかというと、静的linkerがrelocatable objectをrelocationする時に、ccが、object file内外含めてcallの遷移先を可能な限りarchitectureに依存しないで探す為に、その関数から呼ばれるrelocation対象関数の一覧をsectionとして提供してくれてる。elfだったら、.rela.text, mac-oならdynsymtab。 だからobject file生成はrelocation前のobject fileがある場合は、それを対象にした方が良いみたい。ただしrelocation後のobject fileしか手元に無ければ、それらのsectionはrelocation後,静的linkerによって書き換えられてしまうので、上記の方法もそれなりに有用かもしれない。

Simple overwriting of GOT

GOT overwriting is a good starting point for making sense of what is all about "relocation". Relocation can be done in two phases; statically or dynamically. Here, I will mention about dynamic relocation.

When you call a function whatever defined on a shared object dynamically, you must set where the instruction pointer heads for to call it. To reach out the point, it needs to be hopped on at least 2 section headers mapped separately on a different program header when you relocate it.

First step is on .plt (procedure linkage table) section which will be usually mapped on so called "text segment" with .text section by libc so.

Second step is on .got(global offset table) section which is distinctly mapped on "data segment" considering a memory gap starting from the first mapping which is set as virtual address offset of second program header.

But note that dynamic linker will not jump on the address where global offset table sits. Procedure linkage table will indicate where the next instruction should come referring to the value of global offset table not the address of global offset table itself on data segment.

What the dynamic loader relocates means is simply rewriting a sequence of bytes on GOT which denotes a memory address. It addresses next instruction of .plt before the relocation so that relocation itself can be optional for a loader. When it is not set, instruction pointer will be set to the next instruction as it often does, and go to the next jump which are the head of global offset table. If you set the value.

Why does it need to be optional? Consider a situation where you loaded a shared library which depends on many functions on other libraries with heavy dependencies, but you won't make use of most of them. For example, if you include a header which was defined on stdlibc++.so, it refers many functions on libc.so; a function on std::Thread class might depend on pthread_create(4) on libc.so. In that case, it is just in vain if you do not call std::Thread on main program to connect std::Thread on stdlibc++.so with pthread_create on libc.so. Loading relocation cost can be postponed in that situation till the actual function call. One example about this is one of the flag;RT_LAZY which can be set as 2nd argument of dlopen(2), which will save computational cost of loading phase.

Coming back to the functionality which is provided by PLT & GOT, you can easily hook a function which bridges to another shared object by rewriting (re(relocating)) address on GOT unless it is set lazily.

First, you get the address of PLT, then go to GOT where you modify the value to wherever you want to jump on instead. Note that when you have GNU_RELRO on program header, it contains the head & tail of post-filled-in address including .got, and set read only mode after relocation is done. But, if something is filled at first by dynamic loader, the memory protection can be gained permission to write calling again mprotect(,PROT_WRITE).

Here is the short snippet of the code to do that.


#include <stdio.h>
#include <sys/mman.h>

// This is a simple example to hook puts() defined on libc.so
// on Global offset table and covert to function f2() defined on this file.

// what this does is as follows.
// 1st, find procedure linkage table(PLT) of puts examining part of text area.
// 2nd, get the pointer of PLT and get the offset of GOT.
// 3rd, get the pointer of global offset table(GOT).
// 4th, rewrite it!
// 5th, test if the hooking is worked out.

// basically this scheme should work out in any 

void f1() {
  // when you call put function,
  // instruction pointer which will be jumped from them will tell you
  // where is PLT.
  puts("hei!");
};

void f2(){
  const char str[] = "world!";
  // default gcc might convert printf(string\n)
  // to puts. be careful as puts had been converted to f2 itself.
  printf("%s \n",str);
};

int main() {
  
  unsigned char* begin = (unsigned char*)&f1;
  unsigned char* end = (unsigned char*)&f2;
  for (;begin!=end;begin++) {
    // assume call instruction is represented as
    // 0xe8 + 4byte(%rip).
    if (*begin == 0xe8) {
      // cast the pointer so you can grab 4 bytes to get offset operand.
      unsigned int* offset1 = (unsigned int*)(begin+1);
      // calculate address of PLT e.g. in assembly asm ( "lea offset(%rip), %rax" ;)
      size_t* tmp_plt_addr = (size_t*) ((size_t)(begin+5)+(long int)(*offset1));      

      // address calculation towards negative side is a bit tricky.
      // you just need to pass the beggining 3byte of original one as previous subtraction altered it,
      unsigned short* plt_addr = (((size_t)tmp_plt_addr)&0x000000ffffff) | (size_t)(begin+4) & 0xffffff000000;
      // below might work out alternatively.
      /* size_t* plt_addr = (size_t*) ((size_t)(begin+5)+(long int)(*ptr_) - 0x100000000) ; */
      
      // after you come to plt, what waits you is not another call, but jump.
      // And the jump is not ordinary jump but jump to the address which was set on GOT.
      // it starts from 0xff,0xx25,4byte(%rip)
      // first 0xff is opcode, the second is determining addressing.
      // what you need to hold is last 4 byte which is offset from current %rip to GOT.

      // since the type is short one step means 2byte forwards.
      plt_addr+=1;
      // get the offset which is 4 byte.
      unsigned int* offset = (unsigned int*)plt_addr;
      // proceed another 2*2(4byte) to reach %rip.
      plt_addr+=2;
      // calculate GOT of this function.
      // it does not hold any instruction but only 8byte address.
      size_t* got_addr = (size_t*)((size_t)plt_addr + (unsigned int)*offset);

      // if there is a GNU_RELRO on program headers,
      // it might be the case that libc protected the part of data segment as read only
      // after loader relocated them.
      // you can reask kernel the mapping can be writable.
      mprotect(got_addr,4096,PROT_WRITE);
      // finally rewrite it to whatever you like to jump.
      *got_addr = (size_t)f2;      
      break;
    }
  }

  // When you call puts after got overwriting,
  // f2 will be instead called.
  // make sure you get "world!" not "hello!"
  puts("hello!");
};

That is simple enough to contain essence of relocation. But, if you want to reach comprehensive understanding, I will recommend you to read musl(https://www.musl-libc.org/) carefully.