win10_arm64 驱动注入dll 到 arm32程序
思路如下:
PsSetLoadImageNotifyRoutine(LoadImageNotifyRoutine);
VOID LoadImageNotifyRoutine
(
__in_opt PUNICODE_STRING FullImageName,
__in HANDLE ProcessId,
__in PIMAGE_INFO ImageInfo
)
{
if ( FullImageName == L"\\SystemRoot\\SysArm32\\ntdll.dll") // arm32程序
InjectProcess(ProcessId, ImageInfo->ImageBase);
}
InjectProcess(ProcessId, NtdllImageBase)
{
GetProcAddress32(hNtdll, "NtTestAlert"); // hook NtTestAlert to shellcode,它在进程入口点前被系统调用
GetProcAddress32(hNtdll, "LdrLoadDll"); // shellcode 用 LdrLoadDll 加载 dll
shellcode_buf = AllocMem(ProcessId); // shellcode_buf 要指定在进程空间用户地址2G范围内,方便跳转
// ZwQueryVirtualMemory ZwAllocateVirtualMemory
KeStackAttachProcess(pEProcess, &ApcState);
MakeShellCode(shellcode_buf);
KeUnstackDetachProcess(&ApcState);
}
shellcode 构造:
win10_arm64 应用层代码,是 thumb & arm 混杂指令模式,
#pragma once
#include <ntddk.h>
typedef struct _D_UNICODE_STRING32 {
USHORT Length;
USHORT MaximumLength;
WCHAR * POINTER_32 Buffer;
} D_UNICODE_STRING32, *P_D_UNICODE_STRING32;
typedef
NTSTATUS (NTAPI * POINTER_32 LdrLoadDll_t)(PWCHAR PathToFile, ULONG *Flags, UNICODE_STRING *ModuleFileName, HANDLE *ModuleHandle);
#pragma pack(push, 1)
typedef struct _InjectShellCodeArm32 * POINTER_32 PInjectShellCodeArm32;
typedef struct _InjectShellCodeArm32
{
USHORT PushR0_R3; // 0F B4
USHORT PushLr; // 00 B5
USHORT MovR0Pc; // 78 46
USHORT SubR0_8; // 08 38
USHORT BlInjectFunc1; // 00 F0
USHORT BlInjectFunc2; // 18 F9
USHORT PopR0; // 01 BC
USHORT MovLrR0; // 86 46
USHORT PopR0_R3; // 0F BC
USHORT align; // 00 00
UCHAR OrigFunc[16];
PInjectLibArm32 next;
D_UNICODE_STRING32 dll; // Dst dll path
WCHAR dllBuf[260];
LdrLoadDll_t pLdrLoadDll;
UCHAR injectFunc[1];
} InjectShellCodeArm32;
#pragma pack(pop)
ULONG_PTR InjectFuncARM32(InjectLibArm32 *InjectParm)
{
while (InjectParm)
{
ULONG_PTR p = 0;
InjectParm->pLdrLoadDll(0, NULL, (PUNICODE_STRING32)&(InjectParm->dll), (HANDLE*)&p);
InjectParm = (InjectShellCodeArm32*)InjectParm->next;
}
return 0;
}
覆盖LdrLoadDll的指令为:(在同事 卡罗特 的帮助下,卡罗特熟悉arm指令)
{
t[0] = 0x78; // bx pc // 不要在这里下断点,
arm PC寄存器值是指向下下一条指令的,在这里下断点windbg会使PC指向当前指令,然后死循环
t[1] = 0x47;
t[2] = 0xc0; // nop
t[3] = 0x46;
t[4] = 0x04; // ldr pc, [pc, #-4]
t[5] = 0xf0;
t[6] = 0x1f;
t[7] = 0xe5;
t[8] = c[0]; // DstAddr
t[9] = c[1];
t[10] = c[2];
t[11] = c[3];
t[8] += 1; // arm处理器指令长度最少为2字节,最后1bit其实是用来指示该地址是thumb模式还是arm模式的,目标处是thumb模式,要置1
}
这种跳转用来实现32位任意地址跳转,实际中间转为arm指令来实现,可见实际由“4条”指令构成;
InjectShellCodeArm32 中,实现一个短跳转,(卡罗特认为无法(1条指令)构造短跳转),我查thumb指令手册发现其实可以实现23bit地址范围的跳转,
USHORT BlInjectFunc1; // 00 F0
USHORT BlInjectFunc2; // 18 F9
计算方法如下(InjectShellCodeArm32):
PC + 564 - 4 := PC + OffsetHigh << 12 + OffsetLow << 1
+12
+16
+4
+8
+260*2 520
+4
-----------
=564 (need - 4) // 因为arm PC寄存器值是指向下下一条指令的,也就是4字节后,所以要-4
=1000110000
11110000 00000000 F000
11111000 00000000
+ 1 00011000
=11111001 00011000 F918
PS : POINTER_32宏很重要,用来实现 64位指针转换为32位指针!
PS : http://armconverter.com arm 指令转 opcode
PS : win10 arm64 驱动编译,需要VS2017,WDK10
PS : win10_arm64 的双机内核调试,可以用 Kdnet over usb的方式,然后用普通usb线(usb2.0即可)连接两台电脑,
host端也要是win10, windbg10x64->内核调试->net->填写port与key.
target端参见https://docs.microsoft.com/en-us/windows-hardware/test/hlk/testref/8424cf29-e2b4-4060-bb90-4ea503ce704b
驱动的代码不放了,放一份应用层shellcode的demo.arm32 release方式编译,会去加载c:\32.dll (必须是arm32 dll)
https://github.com/DeDf/Arm32ShellCode
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-9-27 16:27
被囧囧编辑
,原因: