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(ページ属性)を操作していることがわかりました。次回はこれらの実装細部を見ていきます。


ただし次回はない。