syscall学习
·972 words·5 mins
Table of Contents
记一下近期逆向看到的一些好玩的东西
syscall实现openprocess #
逆向 #
逆向KernelBase.dll找openprocess源码
HANDLE __stdcall OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)
{
NTSTATUS v3; // eax
unsigned __int128 v5; // [rsp+20h] [rbp-48h] BYREF
struct _OBJECT_ATTRIBUTES v6; // [rsp+30h] [rbp-38h] BYREF
HANDLE v7; // [rsp+88h] [rbp+20h] BYREF
memset(&v6.Attributes + 1, 0, 20);
v6.RootDirectory = 0LL;
v5 = (unsigned __int64)(int)dwProcessId;
*(_QWORD *)&v6.Length = 48LL;
v7 = 0LL;
v6.Attributes = bInheritHandle ? 2 : 0;
v6.ObjectName = 0LL;
v3 = NtOpenProcess(&v7, dwDesiredAccess, &v6, (PCLIENT_ID)&v5);
if ( v3 >= 0 )
return v7;
BaseSetLastNTError((unsigned int)v3);
return 0LL;
}
随便找个程序调用一下openprocess,跟到NtOpenProcess里面,可以看到使用的0x26号系统调用,参数和寄存器的对应暂时不管
00007FF9846236E0 | 4C:8BD1 | mov r10,rcx |
00007FF9846236E3 | B8 26000000 | mov eax,26 | 26:'&'
00007FF9846236E8 | F60425 0803FE7F 01 | test byte ptr ds:[7FFE0308],1 |
00007FF9846236F0 | 75 03 | jne ntdll.7FF9846236F5 |
00007FF9846236F2 | 0F05 | syscall |
00007FF9846236F4 | C3 | ret |
00007FF9846236F5 | CD 2E | int 2E |
00007FF9846236F7 | C3 | ret |
编写汇编
.code
asm_OpenProcess proc
mov r10,rcx
mov eax,26h
syscall
ret
asm_opp endp
end
在ida中查找NtOpenProcess的定义
.idata:0000000180297290 ; NTSTATUS (__stdcall *NtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
.idata:0000000180297290 extrn __imp_NtOpenProcess:qword
修改一下定义,需要使其用纯C的语境下使用
extern "C" NTSTATUS asm_OpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);
处理参数 #
ida9对结构体的处理很舒服,可以直接复制出来用
PHANDLE不用管,ACCESS_MASK是dword
从ida把POBJECT_ATTRIBUTES和PCLIENT_ID的结构体复制出来,
00000008 typedef struct _OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
00000000 struct _OBJECT_ATTRIBUTES // sizeof=0x30
00000000 { // XREF: PerfpRegisterCounterSet+56/w
00000000 // PerfpRegisterCounterSet+99/w ...
00000000 ULONG Length; // XREF: BaseRegOpenKeyInternal+11B/w
00000000 // BaseRegOpenKeyInternal+153/w ...
00000004 // padding byte
00000005 // padding byte
00000006 // padding byte
00000007 // padding byte
00000008 HANDLE RootDirectory; // XREF: BaseRegOpenKeyInternal+158/w
00000008 // BaseRegOpenKeyInternal+15D/w ...
00000010 PUNICODE_STRING ObjectName; // XREF: BaseRegOpenKeyInternal+123/w
00000010 // BaseRegOpenKeyInternal+283/w ...
00000018 ULONG Attributes; // XREF: BaseRegOpenKeyInternal+26F/w
00000018 // BaseRegOpenKeyInternal+2D3/w ...
0000001C // padding byte
0000001D // padding byte
0000001E // padding byte
0000001F // padding byte
00000020 PVOID SecurityDescriptor; // XREF: BaseRegOpenKeyInternal+12B/w
00000020 // BaseRegOpenKeyInternal+28E/w ...
00000028 PVOID SecurityQualityOfService; // XREF: QueryRegDefaultSortingVersion+50/w
00000028 // QueryRegSortVersionDll+61/w ...
00000030 };
00000008 typedef struct _CLIENT_ID *PCLIENT_ID;
00000000 struct _CLIENT_ID // sizeof=0x10
00000000 {
00000000 HANDLE UniqueProcess; // XREF: GetProcessVersion+A3/w
00000000 // ProcessIdToHandle+32/w
00000008 HANDLE UniqueThread; // XREF: GetProcessVersion+81/w
00000008 // ProcessIdToHandle+3A/w
00000010 };
把逆出来的东西修改一下
typedef struct _CLIENT_ID // sizeof=0x10
{
HANDLE UniqueProcess;
HANDLE UniqueThread;
} * PCLIENT_ID;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
char * ObjectName; // PUNICODE_STRING 直接用char * 存放
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} * POBJECT_ATTRIBUTES;
然后开始声明参数并初始化
使用 {} 初始化后,每个成员会被初始化为其类型的"零值":
Length (ULONG) → 0
RootDirectory (HANDLE) → nullptr
ObjectName (char ) → nullptr
Attributes (ULONG) → 0
SecurityDescriptor (PVOID) → nullptr
SecurityQualityOfService (PVOID) → nullptr
HANDLE hProcess{};
_OBJECT_ATTRIBUTES ObjectAttributes{};
memset(&ObjectAttributes.Attributes + 1, 0, 20);
v6.RootDirectory = 0LL;
v6.Length = 48LL;
v6.Attributes = 0 ? 2 : 0; // bInheritHandle一般填false
v6.ObjectName = 0LL;
_CLIENT_ID v5{};
v5.UniqueProcess = (HANDLE)pid;
//
asm_OpenProcess(&hProcess, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, &v6, &v5);
效果 #
src:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <iostream>
typedef struct _CLIENT_ID // sizeof=0x10
{
HANDLE UniqueProcess; // XREF: GetProcessVersion+A3/w
HANDLE UniqueThread; // XREF: GetProcessVersion+81/w
} * PCLIENT_ID;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
char * ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} * POBJECT_ATTRIBUTES;
// idata:0000000180297290 ; NTSTATUS (__stdcall *NtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
extern "C" NTSTATUS asm_OpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);
int main() {
// 目标进程的PID
DWORD pid = 47128; // 替换为实际的进程ID
// 要读取的内存地址
LPCVOID address = (LPCVOID)0x0007FF9844C0000; // 替换为实际的内存地址
// 打开目标进程,获取进程句柄
HANDLE hProcess{};
_OBJECT_ATTRIBUTES ObjectAttributes{};
memset(&ObjectAttributes.Attributes + 1, 0, 20);
v6.RootDirectory = 0LL;
v6.Length = 48LL;
v6.Attributes = 0 ? 2 : 0;
v6.ObjectName = 0LL;
_CLIENT_ID v5{};
v5.UniqueProcess = (HANDLE)pid;
//
asm_OpenProcess(&hProcess, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, &v6, &v5);
if (hProcess == NULL) {
std::cerr << "无法打开进程。错误代码:" << GetLastError() << std::endl;
return 1;
}
// 读取的数据缓冲区
SIZE_T bytesRead;
int buffer = 0;
// 从目标进程中读取内存
BOOL success = ReadProcessMemory(hProcess, address, &buffer, sizeof(buffer), &bytesRead);
if (!success) {
std::cerr << "读取内存失败。错误代码:" << GetLastError() << std::endl;
CloseHandle(hProcess);
return 1;
}
std::cout << "读取到的数据:" << std::hex << buffer << std::endl;
// 关闭进程句柄
CloseHandle(hProcess);
return 0;
}
asm
.code
asm_OpenProcess proc
mov r10,rcx
mov eax,26h
syscall
ret
asm_opp endp
end
去读一个进程的baseadd开头几个字节
读取到的数据:905a4d
4d 5a 90 00 刚好是mz魔数
kctf2025 day4知识点 #
int3触发vsh #
AddVectoredExceptionHandler(1, KiTrapActions);//优先级 1
int 3写法,int 3字节码写在数组里面,用VirtualProtectEx给执行权限直接调用
char dynamicCode[50] = {
0xCC,
0xC3
};
VirtualProtectEx(getCurrentProcessProc(), dynamicCode, 50, PAGE_EXECUTE_READWRITE, &oldProtect);
FARPROC trup = (FARPROC) & dynamicCode;
trup();
会自动将异常的相关信息封装在一个 _EXCEPTION_POINTERS
结构体中,这里用来清空调试信息
ExceptionInfo->ContextRecord->Dr0 = 0;
ExceptionInfo->ContextRecord->Dr1 = 0;
ExceptionInfo->ContextRecord->Dr2 = 0;
ExceptionInfo->ContextRecord->Dr3 = 0;
利用设置rip使运行逻辑有问题的尝试退出
else if (KiDebugType == KiDebugTypeDef::EndThread) {
ExceptionInfo->ContextRecord->Rip = 0x0;
}
读取ntdll并利用syscall调用 #
callssn.asm中定义了两个主要的函数
.data
nextCallSSN qword 000h
public nextCallSSN
funcAddr qword callssn
public funcAddr
.code
align 16
setNextCall proc
mov nextCallSSN,0
or nextCallSSN,rcx
ret
setNextCall endp
callssn proc
mov r10,0h
or r10,rcx
mov rax,0h
or rax,nextCallSSN
syscall
ret
callssn endp
nextCallSSN用于设置下一次syscall的系统号
callssn额外的部分不知道在干啥
完整调用
// 加载资源字符串,获取函数名 "ZwQueryInformationProcess" 的加密形式
CHAR zwQueryInformationProcessFuncName[ResourceLoadMaxLength];
length = LoadStringA(getModuleHandleProc(NULL), IDS_ZwQueryInformationProcessFuncName, zwQueryInformationProcessFuncName, ResourceLoadMaxLength);
// 解密函数名,生成明文字符串
vector<uint8_t>* funcName = DecryptText(string(zwQueryInformationProcessFuncName, zwQueryInformationProcessFuncName + length), *key);
string funcNameStr(funcName->begin(), funcName->end());
// 从 Nt_SSNTable 中查找解密后的函数名对应的系统服务号 (SSN)
int ssn = Nt_SSNTable->find(funcNameStr.c_str())->second;
// 释放解密后的函数名内存
delete funcName;
// 设置下一次调用的目标函数地址为 SSN 对应的函数
setNextCall(ssn);
// 将 callssn 转换为 ZwQueryInformationProcess 类型的函数指针
ZwQueryInformationProcess zwQueryInformationProcess = (ZwQueryInformationProcess)callssn;
// 定义调试标志和实际长度变量
DEBUGFLAG debugflag;
ULONG trueLength;
// 调用 ZwQueryInformationProcess 检查当前进程是否处于调试状态
NTSTATUS status = zwQueryInformationProcess(getCurrentProcessProc(), ntdef::NTPROCESSINFOCLASS::ProcessDebugPort, &debugflag, sizeof(debugflag), &trueLength);
// 如果调用成功并且进程处于调试状态,则设置 KiDebugType 为 NoAction
if (NT_SUCCESS(status)) {
if (debugflag == DebugFlag_IsDebug) {
KiDebugType = KiDebugTypeDef::NoAction;
}
}
主要起作用的相当于
NTSTATUS status = zwQueryInformationProcess(getCurrentProcessProc(), ntdef::NTPROCESSINFOCLASS::ProcessDebugPort, &debugflag, sizeof(debugflag), &trueLength);
.code
zwQueryInformationProcess proc
mov r10,0h
or r10,rcx
mov rax,0h
or rax,19h // ZwQueryInformationProcess 是 0x19
syscall
ret
zwQueryInformationProcess endp
end
其实还是把syscall封装了几层
指令cpuid
调用:
__asm
{
mov eax,fn_id ; 将参数赋值给eax
cpuid ; 执行cpuid指令
mov deax,eax ; 将寄存器值赋值给临时变量
mov debx,ebx
mov decx,ecx
mov dedx,edx
}
原型 __MACHINEX86_X64(void __cpuid(int[4], int))
int cpuid[4];
__cpuid(cpuid,1);
cpuid[2]的地32表示是否在虚拟机中运行
vmp里面用cpuid实现反调的代码
// hardware detection
int cpu_info[4];
__cpuid(cpu_info, 1);
if ((cpu_info[2] >> 31) & 1) {
#ifndef VMP_GNU
// check Hyper-V root partition
cpu_info[1] = 0;
cpu_info[2] = 0;
cpu_info[3] = 0;
__cpuid(cpu_info, 0x40000000);
if (cpu_info[1] == 0x7263694d && cpu_info[2] == 0x666f736f && cpu_info[3] == 0x76482074) { // “Microsoft Hv”
cpu_info[1] = 0;
__cpuid(cpu_info, 0x40000003);
if (cpu_info[1] & 1)
return false;
}
#endif
}