pmeerw's blog

Thu, 03 Oct 2019

Windows RtlAddFunctionTable, the missing documentation

Microsoft Windows offers the RtlAddFunctionTable function which "adds a dynamic function table to the dynamic function table list."

NTSYSAPI BOOLEAN RtlAddFunctionTable(
  PRUNTIME_FUNCTION FunctionTable,
  DWORD64 BaseAddress
);

A function table is used to associate unwind information to functions in order to help 64-bit Windows unwind the call stack, but also to register an exception handler for the function. For an executable image (PE), the compiler and linker normally put the function table into the .pdata section. In case code is generated at runtime (e.g. by a just-in-time (JIT) compiler) and stack unwinding or exception handling is required, a new function table can be added using the RtlAddFunctionTable API.

A process may be composed of several modules (one executable and zero or more DLLs), each module can provide a function table. In addition, there is a dynamic function table, managed by RtlAddFunctionTable and RtlDeleteFunctionTable. A function table is relative to an (image) base address, as it contains RVAs, relative virtual addresses.

Observations (Windows 10):

  1. RtlAddFunctionTable succeeds, even if the BaseAddress coincides with an image with a static function table.
  2. A static function table (.pdata) takes precedence over a dynamic function table for the entire image size.
  3. Consequently, no runtime info (i.e. exception handler) can be added to a function loaded from an image.

The RtlLookupFunctionEntry function can be used to find the function table entry corresponding to a code address (i.e. program counter, PC, or instruction pointer, IP):

NTSYSAPI PRUNTIME_FUNCTION RtlLookupFunctionEntry(
  DWORD64 ControlPc,
  PDWORD64 ImageBase,
  PUNWIND_HISTORY_TABLE HistoryTable
);

Observations (Windows 10):

  1. ImageBase is an output parameter; it is unmodified if the lookup fails.
  2. HistoryTable can be NULL; it can be used to speed up the lookup and should initially point to a zero-initialized HistoryTable.

// sample code to demonstrate Windows RtlAddFunctionTable API
// (c) 2019 Peter Meerwald-Stadler, pmeerw@pmeerw.net
// 64-bit only, compile with: cl rtlft.c /link /fixed

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "windows.h"

typedef uint8_t UBYTE;
typedef uint16_t USHORT;

typedef union _UNWIND_CODE {
	struct {
		UBYTE CodeOffset;
		UBYTE UnwindOp : 4;
		UBYTE OpInfo   : 4;
	};
	USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

typedef struct _UNWIND_INFO {
	UBYTE Version       : 3;
	UBYTE Flags         : 5;
	UBYTE SizeOfProlog;
	UBYTE CountOfCodes;
	UBYTE FrameRegister : 4;
	UBYTE FrameOffset   : 4;
	UNWIND_CODE UnwindCode[1];
/*	UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
 *	OPTIONAL ULONG ExceptionHandler;
 *	OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;

typedef struct {
	uint8_t code[0x1000];
	RUNTIME_FUNCTION function_table[1];
	UNWIND_INFO unwind_info[1];
} DYNSECTION;

static EXCEPTION_DISPOSITION handler(PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame, PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) {
	printf("handler!\n");
	ContextRecord->Rip += 3;
	return ExceptionContinueExecution;
}

int main() {
	int ret;
	RUNTIME_FUNCTION *q;
	DYNSECTION *dynsection = VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	
	uint8_t *code = dynsection->code;
	size_t p = 0;
	code[p++] = 0xb8; // mov rax, 42
	code[p++] = 0x2a;
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0xc6; // mov byte [rax], 0  -- raises exception!
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0xc3; // ret

	size_t trampoline = p;
	code[p++] = 0x48; // mov rax, 
	code[p++] = 0xb8;
	size_t patch_handler_address = p;
	code[p++] = 0x00; // address to handler patched here
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0x00;
	code[p++] = 0xff; // jmp rax
	code[p++] = 0xe0;
	
	DWORD64 dyn_base = 0;
	q = RtlLookupFunctionEntry((DWORD64) code, &dyn_base, NULL);
	printf("lookup 'code' %p %llx\n", q, dyn_base); // no function table entry

	DWORD64 image_base = 0;
	q = RtlLookupFunctionEntry((DWORD64) main, &image_base, NULL);
	printf("lookup 'main' %p %llx\n", q, image_base); // there is a function table entry

	dyn_base = (DWORD64) dynsection;
	UNWIND_INFO *unwind_info = dynsection->unwind_info;
	unwind_info[0].Version = 1;
	unwind_info[0].Flags = UNW_FLAG_EHANDLER;
	unwind_info[0].SizeOfProlog = 0;
	unwind_info[0].CountOfCodes = 0;
	unwind_info[0].FrameRegister = 0;
	unwind_info[0].FrameOffset = 0;
	*(DWORD *) &unwind_info[0].UnwindCode = (DWORD64) &code[trampoline] - dyn_base;

	RUNTIME_FUNCTION *function_table = dynsection->function_table;
	function_table[0].BeginAddress = (DWORD64) &code[0] - dyn_base; // set RVA of dynamic code start
	function_table[0].EndAddress = (DWORD64) &code[trampoline] - dyn_base; // RVA of dynamic code end
	function_table[0].UnwindInfoAddress = (DWORD64) unwind_info - dyn_base; // RVA of unwind info

	*(DWORD64 *) &code[patch_handler_address] = (DWORD64) handler; // VA of handler

	printf("main VA %016llx\n", (DWORD64) main);	
	printf("code VA %016llx\n", (DWORD64) code);
	printf("function table VA %016llx\n", (DWORD64) function_table);
	printf("unwind info VA %016llx\n", (DWORD64) unwind_info);
	printf("handler VA %016llx\n", (DWORD64) handler);
	printf("RUNTIME_FUNCTION begin RVA %08x, end RVA %08x, unwind RVA %08x\n",
		function_table[0].BeginAddress, function_table[0].EndAddress,
		function_table[0].UnwindInfoAddress);
	printf("UNWIND_INFO handler RVA %08x\n", *(DWORD *) &unwind_info[0].UnwindCode);
	
	if (!RtlAddFunctionTable(function_table, 1, dyn_base)) {
		printf("RtlAddFunctionTable() failed, exit.\n");
		exit(EXIT_FAILURE);
	}

	q = RtlLookupFunctionEntry((DWORD64) code, &dyn_base, NULL);
	printf("lookup 'code' %p %llx\n", q, dyn_base); // should return address of function table entry

	uint64_t (*call)() = (uint64_t (*)()) code;
	uint64_t result = (*call)();
	printf("result = %llx\n", result);	

	if (!RtlDeleteFunctionTable(function_table)){
		printf("RtlDeleteFunctionTable() failed, exit.\n");
		exit(EXIT_FAILURE);
	}

	return EXIT_SUCCESS;
}
The output should show that the exception has been handled and look similar to:
C:\>cl rtlft.c /link /fixed
...
C:\>rtlft.exe
lookup 'code' 0000000000000000 0
lookup 'main' 000000014001F00C 140000000
main VA 0000000140001050
code VA 0000000000020000
function table VA 0000000000021000
unwind info VA 000000000002100c
handler VA 0000000140001000
RUNTIME_FUNCTION begin RVA 00000000, end RVA 00000009, unwind RVA 0000100c
UNWIND_INFO handler RVA 00000009
lookup 'code' 0000000000021000 20000
handler!
result = 2a

The story here is related to how C++ exceptions work on Windows (video)...

posted at: 23:58 | path: /programming | permanent link

Sun, 16 Jun 2019

Assembler at your fingertips: rappel

rappel is a Linux-based assembly REPL (read-eval-print loop) supporting Intel syntax. Quite handy to try out various instructions:

rax: 0x0000000000000001	rbx: 0x0000000000000002	rcx: 0x0000000000000000	rdx: 0x0000000000000000
rsi: 0x0000000000000000	rdi: 0x0000000000000000	r8 : 0x0000000000000000	r9 : 0x0000000000000000
r10: 0x0000000000000000	r11: 0x0000000000000000	r12: 0x0000000000000000	r13: 0x0000000000000000
r14: 0x0000000000000000	r15: 0x0000000000000000
rip: 0x0000000000400006	rsp: 0x00007fffd64d8f10	rbp: 0x0000000000000000
flags: 0x0000000000000202 [cf:0, zf:0, of:0, sf:0, pf:0, af:0, df:0]
> add eax,ebx
rax: 0x0000000000000003	rbx: 0x0000000000000002	rcx: 0x0000000000000000	rdx: 0x0000000000000000
rsi: 0x0000000000000000	rdi: 0x0000000000000000	r8 : 0x0000000000000000	r9 : 0x0000000000000000
r10: 0x0000000000000000	r11: 0x0000000000000000	r12: 0x0000000000000000	r13: 0x0000000000000000
r14: 0x0000000000000000	r15: 0x0000000000000000
rip: 0x0000000000400003	rsp: 0x00007fffd64d8f10	rbp: 0x0000000000000000
flags: 0x0000000000000206 [cf:0, zf:0, of:0, sf:0, pf:1, af:0, df:0]

Under the hood, it just runs nasm and observes register values. FP/XMM is supported as well...

posted at: 23:00 | path: /programming | permanent link

Wed, 12 Jun 2019

Ubuntu 19.04, two monitor setup

Using two 27" monitors now: one Iiyama (3840x2160), one BenQ (1920x1080). The first has way higher DPI. To compensate, I use fractional scaling (175% and 100%). This needs to be enabled for X11, requires Ubuntu 19.04:

gsettings set org.gnome.mutter experimental-features "['x11-randr-fractional-scaling']"
See here for more details.

posted at: 21:28 | path: /configuration | permanent link

Mon, 20 May 2019

Debugging a WWAN USB stick

What to do when a USB WWAN (UMTS) stick doesn't work? That is, network manager shown broadband as not enabled...

The kernel log looks good:

[ 3313.057520] usb 2-1: New USB device found, idVendor=0e8d, idProduct=00a5, bcdDevice= 3.00
[ 3313.057528] usb 2-1: New USB device strings: Mfr=9, Product=10, SerialNumber=0
[ 3313.057536] usb 2-1: Manufacturer: MediaTek Inc
[ 3313.085587] cdc_mbim 2-1:1.0: cdc-wdm0: USB WDM device
[ 3313.086142] cdc_mbim 2-1:1.0 wwan0: register 'cdc_mbim' at usb-0000:00:14.0-1, CDC MBIM, d2:9c:37:32:10:73
[ 3313.086885] option 2-1:1.2: GSM modem (1-port) converter detected
[ 3313.087140] usb 2-1: GSM modem (1-port) converter now attached to ttyUSB0
[ 3313.087520] option 2-1:1.3: GSM modem (1-port) converter detected
[ 3313.087654] usb 2-1: GSM modem (1-port) converter now attached to ttyUSB1
[ 3313.087924] option 2-1:1.4: GSM modem (1-port) converter detected
[ 3313.088050] usb 2-1: GSM modem (1-port) converter now attached to ttyUSB2
[ 3313.088322] option 2-1:1.5: GSM modem (1-port) converter detected
[ 3313.090237] usb 2-1: GSM modem (1-port) converter now attached to ttyUSB3
[ 3313.090693] usb-storage 2-1:1.6: USB Mass Storage device detected
[ 3313.143856] cdc_mbim 2-1:1.0 wwp0s20u1: renamed from wwan0
[ 3314.109400] scsi 3:0:0:0: Direct-Access     MEDIATEK  FLASH DISK      6225 PQ: 0 ANSI: 0 CCS
[ 3314.110360] sd 3:0:0:0: Attached scsi generic sg1 type 0
[ 3314.128902] sd 3:0:0:0: [sdb] 0 512-byte logical blocks: (0 B/0 B)
[ 3314.131013] sd 3:0:0:0: [sdb] Attached SCSI removable disk
The ModemManager has a useful command-line tool, mmcli, that can be used to show status of the modem. Try mmcli -v -m 6 where 6 is an index of the modem:
[20 Mai 2019, 18:56:55] [Debug] ModemManager process found at ':1.2'
[20 Mai 2019, 18:56:55] [Debug] Assuming '6' is the modem index
[20 Mai 2019, 18:56:55] [Debug] Modem found at '/org/freedesktop/ModemManager1/Modem/6'

[20 Mai 2019, 18:56:55] [Debug] Printing modem info...
  --------------------------
  General  |      dbus path: /org/freedesktop/ModemManager1/Modem/6
           |      device id: a67f08189ddc6295ad23160780ebeb06a0925a6c
  --------------------------
  Hardware |   manufacturer: MediaTek Inc
           |          model: Product
           |       revision: UW980_42M_ESMT_V101R01B08
           |   h/w revision: MTK2
           |      supported: gsm-umts
           |        current: gsm-umts
           |   equipment id: 355128006541593
  --------------------------
  System   |         device: /sys/devices/pci0000:00/0000:00:14.0/usb2/2-1
           |        drivers: cdc_mbim, option1
           |         plugin: Generic
           |   primary port: cdc-wdm0
           |          ports: ttyUSB0 (at), ttyUSB1 (at), cdc-wdm0 (mbim), wwp0s20u1 (net)
  --------------------------
  Status   |          state: failed
           |  failed reason: sim-missing
           |    power state: on
           | signal quality: 0% (cached)
  --------------------------
  Modes    |      supported: allowed: 2g, 3g; preferred: none
           |        current: allowed: any; preferred: none
  --------------------------
  IP       |      supported: ipv4, ipv6, ipv4v6
In this case, the SIM card seem to be missing (failed reason: sim-missing); or defective rather.

posted at: 21:26 | path: /configuration | permanent link

Sat, 18 May 2019

New PC build: Ryzen 5 2400G

I got a AMD Ryzen 5 2400G, Asus X370-A Prime mainboard, a pair of G.Skill F4-3000C16D (Aegis) 8 GB DDR4 RAM modules, a AData XPG SX8200 512 GB NVMe SSD (M.2-2280), be quiet Pure Power 11 400W power supply, all in a Chieftec Elox HC-10b case.

So far, so good; RAM is working at 3000MHz, CPU slightly overclocked at 3750 MHz.

The particular mainboard has two PCI slots (hard to find these days :-), and can take up to 4 RAM modules (max. 64 GB).

posted at: 19:03 | path: /review | permanent link

Made with PyBlosxom