admin 管理员组

文章数量: 887017

Windows系统调用学习笔记(一)—— API函数调用过程

    • Windows API
    • 实验1:分析ReadProcessMemory
      • 第一步:定位函数
      • 第二步:开始分析
      • 总结
    • 实验2:分析NtReadVirtualMemory
      • 第一步:定位函数
      • 第二步:开始分析
      • 总结
    • 实验3:编写一个ReadProcessMemory函数
      • 第一步:获得变量地址
      • 第二步:构造自定义ReadProcessMemory
      • 第三步:调用自定义ReadProcessMemory

Windows API

描述:

  1. Application Programming Interface,简称 API 函数。

  2. Windows有多少个API?
    主要是存放在 C:\WINDOWS\system32 下面所有的dll

  3. 几个重要的DLL
    Kernel32.dll:最核心的功能模块,比如管理内存进程线程相关的函数等
    User32.dll:是Windows用户界面相关应用程序接口,如创建窗口发送消息
    GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。比如要显示一个程序窗口,就调用了其中的函数来画这个窗口
    Ntdll.dll:大多数API都会通过这个DLL进入内核(0环)

实验1:分析ReadProcessMemory

第一步:定位函数

  1. 使用IDA打开kernel32.dll(ReadProcessMemory函数在这个dll里)
  2. 在左侧函数窗口中使用快捷键ctrl+F搜索ReadProcessMemory
  3. 双击查看反汇编

第二步:开始分析

  • 首先将我们传入的参数进行压栈

  • 调用了其它模块的函数:NtReadVirtualMemory(函数名红色说明不在当前模块)

  • 若返回结果小于0,跳转至loc_7C802204


    loc_7C802204调用了sub_7C809419

  • sub_7C809419内部调用了另一个函数RtlNtStatusToDosError

    这个函数的作用是设置错误号

  • sub_7C809419出来后,将eax清零,跳转到loc_7C802F9

    loc_7C802F9只做了一件事情,那就是返回
    至此,我们可以知道,当调用ReadProcessMemory失败时,返回结果为0

  • NtReadVirtualMemory返回结果大于等于0,返回结果为1

总结

  1. ReadProcessMemory函数在kernel32.dll中实际上并没有做什么事情。它只是调用了另一个函数,然后设置了一些返回值
  2. 如果想知道ReadProcessMemory这个函数到底是怎么执行的,就得找到NtReadVirtualMemory

实验2:分析NtReadVirtualMemory

第一步:定位函数

  1. 查看kernel32的导入表(Imports),找到NtReadVirtualMemory
  2. 观察Library字段,发现NtReadVirtualMemory函数来自ntdll
  3. 使用IDA打开ntdll.dll
  4. 将光标放置在反汇编界面最上方,使用alt+T搜索NtReadVirtualMemory

    这4行代码就是NtReadVirtualMemory的所有代码

第二步:开始分析

  • mov eax, 0BAh
    0BAh编号,对应操作系统内核中某个函数的编号
    我们所用到的API函数,只要进0环,都要提供一个编号

  • mov edx, 7FFE0300h
    7FFE0300h函数地址,该函数决定了我们用什么方式进0环

  • 该函数的功能:提供一个编号和一个函数,通过这个函数进入0环

总结

真正读取进程内存的函数在0环实现,我们所用的函数只是系统提供给我们的函数接口

实验3:编写一个ReadProcessMemory函数

要求:不使用任何DLL,直接调用0环函数
意义:自己实现的API,可以避免3环程序被恶意挂钩

第一步:获得变量地址

运行以下代码(进程1):

#include <stdio.h>

int main()
{
	int num = 0x12345678;

	printf("&num = %x \n", &num);

	getchar();
	return 0;
}

运行结果:

注意:不要让进程结束

第二步:构造自定义ReadProcessMemory

代码如下

void MyReadProcessMemory(
	HANDLE hProcess, 
	LPCVOID lpBaseAddress, 
	LPVOID lpBuffer, 
	DWORD nSize, 
	LPDWORD lpNumberOfBytesRead)
{
	__asm
	{
		lea  eax, [ebp+0x14]
		push eax				; ReturnLength
		push [ebp+0x14]			; BufferLength
		push [ebp+0x10]			; Buffer
		push [ebp+0x0C]			; BaseAddress
		push [ebp+0x08]			; ProcessHandle

		mov  eax, 0BAh
		mov  edx, esp
		int  02eh				; Windows操作系统提供IDT[0x2e]给用户进行内核调用
		
		add  esp, 20
	}
}

第三步:调用自定义ReadProcessMemory

完整代码(进程2):

void MyReadProcessMemory(
	HANDLE hProcess, 
	LPCVOID lpBaseAddress, 
	LPVOID lpBuffer, 
	DWORD nSize, 
	LPDWORD lpNumberOfBytesRead)
{
	__asm
	{
		lea  eax, [ebp+0x14]
		push eax				; ReturnLength
		push [ebp+0x14]			; BufferLength
		push [ebp+0x10]			; Buffer
		push [ebp+0x0C]			; BaseAddress
		push [ebp+0x08]			; ProcessHandle

		mov  eax, 0BAh
		mov  edx, esp
		int  02eh				; Windows操作系统提供IDT[0x2e]给用户进行内核调用
		
		add  esp, 20
	}
}

int main()
{
	DWORD pBuffer;							// 接收数据的缓冲区
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, PID);			// 获得进程句柄,PID修改为进程1的PID
	MyReadProcessMemory(hProcess, (PVOID)0x12ff7c, &pBuffer, 4, 0);		// 调用自定义的ReadProcessMemory

	printf("pBuffer = %x \n", pBuffer);		// 打印从其他进程中读取的数据

	getchar();
	return 0;
}

运行结果:

成功从进程1中读取了变量num的值!

本文标签: 函数 学习笔记 过程 系统 Windows