从可执行文件中调用函数
Call function from executable
我想从可执行文件中调用函数。到达该进程的唯一方法是在父进程中注入一个 dll。我可以在父进程中注入一个 dll,但我如何从子进程中调用一个函数?
像
_asm
{
call/jmp address
}
不起作用。我希望你明白我的意思。
执行基础
要调用一个函数,您需要一个地址或一个中断号。地址被载入程序计数器寄存器并转移执行。一些处理器允许 "Software Interrupts",其中程序执行调用软件中断的特殊指令。这是执行功能的基础。
更多背景知识 -- 相对地址
可执行文件有两种常见形式:绝对寻址和相对寻址(或位置无关代码,PIC)。在绝对寻址中,函数位于硬编码地址。函数不会移动。通常用于嵌入式系统。
在相对寻址模型中,地址与程序计数器寄存器中的值相关。例如,您的函数可能距离 1024 字节,因此编译器将发出 1024 字节(距离)的相对分支指令。
操作系统和移动目标
许多操作系统在每次调用时在不同的地方加载程序。这意味着您的可执行文件可能从地址 1000 开始,下一次从地址 127654 开始。在这些操作系统中,不能保证可执行文件每次都会在同一位置启动。
在您的程序中执行
在您的程序中执行功能很容易。链接器决定所有函数的位置并决定如何执行它们;是否使用绝对寻址、PIC 或混合使用。
在另一个可执行文件中执行函数
根据以上知识,在另一个程序中执行函数时会出现问题:
- 函数在外部可执行文件中的位置
- 正在确定可执行文件是否处于活动状态
- 可执行文件的调用协议
大多数可执行文件不包含有关其函数位置的任何信息,因此您需要知道它的位置。您还需要知道函数是绝对寻址还是 PIC。您还需要知道函数在您需要时是否在内存中,或者 OS 是否已将函数 分页 到硬盘。
了解功能位置是必要的。但是,如果 OS 尚未加载可执行文件,则该位置无用。在调用另一个可执行文件中的函数之前,您需要知道执行调用时它是否存在于内存中。
最后,您需要知道用于外部函数的协议。例如,值是通过寄存器传递的吗?他们在堆栈上吗?它们是通过指针(地址)传递的吗?
解决方案:共享库
操作系统 (OS) 已经发展到允许动态共享功能。这些函数存在于动态链接库 (DLL) 或共享库 (.SO) 中。您的程序告诉 OS 将库加载到内存中,然后您告诉 OS 通过给它函数名来执行该函数。
需要注意的是,您需要的函数必须在库中。如果可执行文件不使用共享库或者您需要的函数不在库中,那么您的任务就会更加困难。
如果您在进程内 运行,您需要知道您要调用的函数相对于包含该函数的模块(exe)的基址的偏移量。然后,你只需要制作一个函数指针并调用它。
// assuming the function you're calling returns void and takes 0 params
typedef void(__stdcall * voidf_t)();
// make sure func_offset is the offset of the function when the module is loaded
voidf_t func = (voidf_t) (((uint8_t *)GetModuleHandle('module_name')) + func_offset);
func(); // the function you located is called here
如果您知道函数的地址,您的解决方案将适用于 32 位系统(64 位不允许内联汇编),但您需要确保正确实现调用约定。上面的代码使用 GetModuleHandle 来解析您要调用其函数的模块的当前加载基。
一旦您将模块注入到 运行 进程中,ASLR 就不是真正的问题,因为您只需向 windows 询问包含您希望的代码的模块基础称呼。如果你想找到exe运行当前进程的基址,你可以调用参数为NULL的GetModuleHandle。如果您确信函数偏移量不会改变,则可以在反汇编程序或其他工具中找到偏移量后,对要调用的函数的偏移量进行硬编码。假设包含该函数的 exe 文件未更改,则该偏移量将保持不变。
如评论中所述,调用约定在函数 typedef 中很重要,请确保它与您正在调用的函数的调用约定相匹配。
我想从可执行文件中调用函数。到达该进程的唯一方法是在父进程中注入一个 dll。我可以在父进程中注入一个 dll,但我如何从子进程中调用一个函数? 像
_asm
{
call/jmp address
}
不起作用。我希望你明白我的意思。
执行基础
要调用一个函数,您需要一个地址或一个中断号。地址被载入程序计数器寄存器并转移执行。一些处理器允许 "Software Interrupts",其中程序执行调用软件中断的特殊指令。这是执行功能的基础。
更多背景知识 -- 相对地址
可执行文件有两种常见形式:绝对寻址和相对寻址(或位置无关代码,PIC)。在绝对寻址中,函数位于硬编码地址。函数不会移动。通常用于嵌入式系统。
在相对寻址模型中,地址与程序计数器寄存器中的值相关。例如,您的函数可能距离 1024 字节,因此编译器将发出 1024 字节(距离)的相对分支指令。
操作系统和移动目标
许多操作系统在每次调用时在不同的地方加载程序。这意味着您的可执行文件可能从地址 1000 开始,下一次从地址 127654 开始。在这些操作系统中,不能保证可执行文件每次都会在同一位置启动。
在您的程序中执行
在您的程序中执行功能很容易。链接器决定所有函数的位置并决定如何执行它们;是否使用绝对寻址、PIC 或混合使用。
在另一个可执行文件中执行函数
根据以上知识,在另一个程序中执行函数时会出现问题:
- 函数在外部可执行文件中的位置
- 正在确定可执行文件是否处于活动状态
- 可执行文件的调用协议
大多数可执行文件不包含有关其函数位置的任何信息,因此您需要知道它的位置。您还需要知道函数是绝对寻址还是 PIC。您还需要知道函数在您需要时是否在内存中,或者 OS 是否已将函数 分页 到硬盘。
了解功能位置是必要的。但是,如果 OS 尚未加载可执行文件,则该位置无用。在调用另一个可执行文件中的函数之前,您需要知道执行调用时它是否存在于内存中。
最后,您需要知道用于外部函数的协议。例如,值是通过寄存器传递的吗?他们在堆栈上吗?它们是通过指针(地址)传递的吗?
解决方案:共享库
操作系统 (OS) 已经发展到允许动态共享功能。这些函数存在于动态链接库 (DLL) 或共享库 (.SO) 中。您的程序告诉 OS 将库加载到内存中,然后您告诉 OS 通过给它函数名来执行该函数。
需要注意的是,您需要的函数必须在库中。如果可执行文件不使用共享库或者您需要的函数不在库中,那么您的任务就会更加困难。
如果您在进程内 运行,您需要知道您要调用的函数相对于包含该函数的模块(exe)的基址的偏移量。然后,你只需要制作一个函数指针并调用它。
// assuming the function you're calling returns void and takes 0 params
typedef void(__stdcall * voidf_t)();
// make sure func_offset is the offset of the function when the module is loaded
voidf_t func = (voidf_t) (((uint8_t *)GetModuleHandle('module_name')) + func_offset);
func(); // the function you located is called here
如果您知道函数的地址,您的解决方案将适用于 32 位系统(64 位不允许内联汇编),但您需要确保正确实现调用约定。上面的代码使用 GetModuleHandle 来解析您要调用其函数的模块的当前加载基。
一旦您将模块注入到 运行 进程中,ASLR 就不是真正的问题,因为您只需向 windows 询问包含您希望的代码的模块基础称呼。如果你想找到exe运行当前进程的基址,你可以调用参数为NULL的GetModuleHandle。如果您确信函数偏移量不会改变,则可以在反汇编程序或其他工具中找到偏移量后,对要调用的函数的偏移量进行硬编码。假设包含该函数的 exe 文件未更改,则该偏移量将保持不变。
如评论中所述,调用约定在函数 typedef 中很重要,请确保它与您正在调用的函数的调用约定相匹配。