OllyBonEを読みます
概要
OllyBonEはページテーブルを操作してメモリの属性を変更することで、ヒューリスティックにアンパックを行うプログラムです。ページテーブル操作によるアンパッキング技法の先駆けとして有名です。
ソースは以下からダウンロードできます。
http://www.joestewart.org/ollybone/
PTEを操作している様子を確認する
DriverEntryです。
DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = PagefaultDispatch; DriverObject->DriverUnload = PagefaultUnload;
特に面白いことはやっていません。ハンドラを設定しているので、そのうちのPagefaultDispatchを見てみます。
switch (irpStack->MajorFunction) { case IRP_MJ_CREATE: break; case IRP_MJ_SHUTDOWN: break; case IRP_MJ_CLOSE: break; case IRP_MJ_DEVICE_CONTROL: if(IOCTL_TRANSFER_TYPE(ioControlCode) == METHOD_NEITHER) { outputBuffer = Irp->UserBuffer; } ntstatus = PagefaultDeviceControl( irpStack->FileObject, TRUE, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, ioControlCode, &Irp->IoStatus, DeviceObject ); break; } IoCompleteRequest( Irp, IO_NO_INCREMENT ); return ntstatus;
実質的な仕事は、IRP_MJ_DEVICE_CONTROL時にPagefaultDeviceControlを呼び出すだけです。したがってPagefaultDeviceControlを見ます。
switch ( IoControlCode ) { case IOCTL_OLLYBONE_SET: //DebugPrint(("**** Got OLLYBONE_SET IOCTL\n")); // ... break; case IOCTL_OLLYBONE_RESTORE: //DebugPrint(("**** Got OLLYBONE_RESTORE IOCTL\n")); // ... break; default: IoStatus->Status = STATUS_INVALID_DEVICE_REQUEST; break; }
IOCTLはIOCTL_OLLYBONE_SETとIOCTL_OLLYBONE_RESTOREを受け入れるようです。IOCTL_OLLYBONE_SETを見てみます。
case IOCTL_OLLYBONE_SET: //DebugPrint(("**** Got OLLYBONE_SET IOCTL\n")); if ((InputBufferLength < sizeof(int) * 3) || (InputBuffer == NULL)) { IoStatus->Status = STATUS_INVALID_BUFFER_SIZE; break; } params = (IOCTLPARAMS *)InputBuffer; pid = params->pid; va = params->va; size = params->size; if (PsLookupProcessByProcessId(pid, &eproc) == STATUS_SUCCESS) { KeAttachProcess(eproc); sprintf(buf,"Attached to PID %x, hooking %x (size=%x)\n", pid, va, size); DebugPrint(buf); g_pidsaved = pid; for (i = va; i < va+size; i+=4096) HookMemoryPage((void *)i); KeDetachProcess(); } else { IoStatus->Status = STATUS_UNSUCCESSFUL; } break;
IOCTLPARAMS 構造体経由でPID、仮想メモリアドレス、サイズを受け取ってます。KeAttachProcessを使ってPIDのメモリ空間にアクセスして、ページ毎にHookMemoryPageを呼び出しています。PsLookupProcessByProcessIdがハンドルリークさせていたり、IOCTLPARAMS 構造体の検証が弱いのが気になりますが、コンセプトコードみたいなもんなのでどうでもよいです。ナンセンスというものです(キリッ
HookMemoryPageを見ます。
void HookMemoryPage( PVOID pPage ) { char buf[100]; void *pReadWritePte; pReadWritePte = GetPteAddress( pPage ); sprintf(buf, "Hooking page va:%x ppte:%x pte:%x ", pPage, pReadWritePte, *(DWORD *)pReadWritePte); DebugPrint(buf); __asm { mov eax, pPage // access page in case it is paged out mov eax, [eax] cli } if( hooked == FALSE ) { InstallHandler(); hooked = TRUE; } pReadWritePte = GetPteAddress( pPage ); __asm { mov eax, pPage invlpg [eax] } // Pages marked Copy-on-Write won't work, change them to normal perms FixCoW( pReadWritePte ); //Mark the page supervisor mode only MarkPageSupervisorOnly( pReadWritePte ); __asm { mov eax, pPage invlpg [eax] sti //reenable interrupts } sprintf(buf, " New pte:%x\n", *(DWORD *)pReadWritePte); DebugPrint(buf); }
GetPteAddressは指定したアドレスに対応するPTEへのアドレスを取得する関数です。これは次回見ていきます。
eax = *pPage に相当するコードを実行しています。これによってpPageのアドレスへのアクセスが発生するため、pPageがページインされていなかった場合でもページインされます。あるいはまだマップされていないページであれば、新規にページが割り当てられ、PTEも割り当てられます。言い換えると、これをしておかないと、対象ページのPTEがまだ割り当てられていないためにPTEを操作できない可能性があります。
あと、このコードはIOCTLPARAMS 構造体経由で(0-0x1000のような)アクセス不能なページを受け取った場合BSODします。たぶん。
cliで割り込みを禁止しているのは、後にInstallHandlerを実行したりPTEを操作する際に一貫性のある状態を保つためですね。ただマルチプロセッサへの配慮は無いようですね。
次にif文で一度だけInstallHandlerを実行します。これは次回見ていきます。
その後、再度GetPteAddressでPTEを取得しinvplgでTLBをフラッシュしています。それぞれ必要なのか、不明です。
次はコアのロジックですね。抜粋です。
// Pages marked Copy-on-Write won't work, change them to normal perms FixCoW( pReadWritePte ); //Mark the page supervisor mode only MarkPageSupervisorOnly( pReadWritePte ); __asm { mov eax, pPage invlpg [eax] sti //reenable interrupts }
コメントによれば、ページがCopy on Writeページなら通常ページにし、その後ページをsupervisorのみアクセス可能にしています。最後に、invplgでTLBをフラッシュし、割り込みを許可して作業完了です。
一連の流れで、OllyBonEが指定されたアドレスのPTE(ページ属性)を操作していることがわかりました。次回はこれらの実装細部を見ていきます。
ただし次回はない。