Skip to main content

syscall学习

·972 words·5 mins

记一下近期逆向看到的一些好玩的东西

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
}