我试图找出c ++上的gcc内联汇编。以下代码适用于不带%和其他操作数的Visual c ++,但我无法使其与gcc一起使用
void function(const char* text) {
DWORD addr = (DWORD)text;
DWORD fncAddr = 0x004169E0;
asm(
"push %0" "\n"
"call %1" "\n"
"add esp, 04" "\n"
: "=r" (addr) : "d" (fncAddr)
);
}
我在运行时将dll注入进程,并且fncAddr是函数的地址。它永远不会改变。正如我所说的,它可以与Visual C ++一起使用
相当于该功能的VC ++:
void function(const char* text) {
DWORD addr = (DWORD)text;
DWORD fncAddr = 0x004169E0;
__asm {
push addr
call fncAddr
add esp, 04
}
}
编辑: 我将功能更改为此:现在它崩溃了
void sendPacket(const char* msg) {
DWORD addr = (DWORD)msg;
DWORD fncAddr = 0x004169E0;
asm(
".intel_syntax noprefix" "\n"
"pusha" "\n"
"push %0" "\n"
"call %1" "\n"
"add esp, 04" "\n"
"popa" "\n"
:
: "r" (addr) , "d"(fncAddr) : "memory"
);
}
编辑:
004169E0 /$ 8B0D B4D38100 MOV ECX,DWORD PTR DS:[81D3B4]
004169E6 |. 85C9 TEST ECX,ECX
004169E8 |. 74 0A JE SHORT client_6.004169F4
004169EA |. 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4]
004169EE |. 50 PUSH EAX
004169EF |. E8 7C3F0000 CALL client_6.0041A970
004169F4 \> C3 RETN
我正在调用的函数在上面。我将其更改为函数指针转换
char_func_t func = (char_func_t)0x004169E0;
func(text);
像这样,它也崩溃了,但令人惊讶的是它起作用了。我安装了一个调试器,它在一些不存在的地址提供了访问冲突
在调用堆栈上,最后一个调用是这样:
004169EF |. E8 7C3F0000 CALL client_6.0041A970
最后编辑:
我放弃了内联汇编,取而代之的是我写了我想逐字节读取的指令,它就像一个魅力
void function(const char* text) {
DWORD fncAddr = 0x004169E0;
char *buff = new char[50]; //extra bytes for no reason
memset((void*)buff, 0x90, 50);
*((BYTE*)buff) = 0x68; // push
*((DWORD*)(buff + 1)) = ((DWORD)text);
*((BYTE*)buff+5) = 0xE8; //call
*((DWORD*)(buff + 6)) = ((DWORD)fncAddr) - ((DWORD)&(buff[5]) + 5);
*((BYTE*)(buff + 10)) = 0x83; // add esp, 04
*((BYTE*)(buff + 11)) = 0xC4;
*((BYTE*)(buff + 12)) = 0x04;
*((BYTE*)(buff + 13)) = 0xC3; // ret
typedef void(*char_func_t)(void);
char_func_t func = (char_func_t)buff;
func();
delete[] buff;
}
谢谢大家
答案 0 :(得分:1)
您当前使用pusha
/ popa
的版本看起来是正确的(缓慢但安全),除非您的调用约定取决于维持16字节堆栈对齐。
如果崩溃了,您真正的问题在其他地方,因此您应该使用调试器并找出哪里崩溃。
在eax
/ ecx
/ edx
上声明Clobbers,或在其中两个寄存器中请求指针并破坏第三个寄存器,可以避免pusha
/ { {1}}。 (或者您正在使用的呼叫约定所使用的所有被呼叫弄乱的规则。)
您应该删除popa
。您已经依靠使用.intel_syntax noprefix
进行编译了,因为如果AT&T不恢复以前的模式,您将无法恢复。 (不幸的是,我不认为有一种方法可以保存/恢复旧模式,但是有a dialect-alternatves mechanism可以为不同的语法模式使用不同的模板。)
编译器知道使用标准的调用约定时已经进行了函数调用(在这种情况下:通常以默认方式在32位模式下堆叠args)。
将整数转换为函数指针是有效的C ++ ,如果该地址处确实存在函数,这甚至不是未定义的行为。
-masm=intel
作为奖励,使用MSVC进行编译的效率也比您的asm版本高。
如果使用其他默认值进行编译,则可以在函数指针上使用GCC函数属性来明确指定调用约定。例如,__attribute__((cdecl))
使用该函数指针为调用显式指定堆栈args和caller-pops。相当于MSVC仅为__cdecl
。
void function(const char* text) {
typedef void (*char_func_t)(const char *);
char_func_t func = (char_func_t)0x004169E0;
func(text);
}
要查看编译器的asm输出,我将其放在on the Godbolt compiler explorer中。我使用了“ intel-syntax”选项,因此gcc输出来自#ifdef __GNUC__
#define CDECL __attribute__((cdecl))
#define STDCALL __attribute__((stdcall))
#elif defined(_MSC_VER)
#define CDECL __cdecl
#define STDCALL __stdcall
#else
#define CDECL /*empty*/
#define STDCALL /*empty*/
#endif
// With STDCALL instead of CDECL, this function has to translate from one calling convention to another
// so it can't compile to just a jmp tailcall
void function(const char* text) {
typedef void (CDECL *char_func_t)(const char *);
char_func_t func = (char_func_t)0x004169E0;
func(text);
}
gcc -S -masm=intel
此测试调用程序使编译器设置了args,而不仅是尾部调用,而且# gcc8.1 -O3 -m32 (the 32-bit Linux calling convention is close enough to Windows)
# except it requires maintaing 16-byte stack alignment.
function(char const*):
mov eax, 4286944
jmp eax # tail-call with the args still where we got them
可以内联到其中。
function
MSVC的int caller() {
function("hello world");
return 0;
}
.LC0:
.string "hello world"
caller():
sub esp, 24 # reserve way more stack than it needs to reach 16-byte alignment, IDK why.
mov eax, 4286944 # your function pointer
push OFFSET FLAT:.LC0 # addr becomes an immediate
call eax
xor eax, eax # return 0
add esp, 28 # add esp, 4 folded into this
ret
的{{1}}输出基本上是相同的:
-Ox
但是使用内联汇编的版本要糟糕得多:
caller
MSVC内联asm语法基本上很烂,因为与GNU C asm语法不同,输入始终必须在内存中,而不是寄存器或立即数。因此,使用GNU C可以做得更好,但是不能完全避免使用内联汇编,因此不如您做的好。 https://gcc.gnu.org/wiki/DontUseInlineAsm。
通常应避免从内联asm进行函数调用;当编译器知道发生了什么时,它会更加安全,高效。
答案 1 :(得分:-4)
这是使用gcc进行内联汇编的示例。
例程“ vazio”托管例程“ rotina”的汇编代码(vazio和rotina只是标签)。注意通过指令使用Intel语法; gcc默认为AT&T。
我从一个旧的子目录中恢复了此代码;汇编代码中的变量以“ _str”前缀为“ _str”-这是标准的C约定。我承认,从现在到现在,我都不知道为什么编译器会改为接受“ str” ...无论如何:
已正确编译gcc / g ++版本5和7!希望这可以帮助。如果要查看汇编结果,只需调用“ gcc main.c”或“ gcc -S main.c”,并为英特尔输出调用“ gcc -S masm = intel main.c”。
#include <stdio.h>
char str[] = "abcdefg";
// C routine, acts as a container for "rotina"
void vazio (void) {
asm(".intel_syntax noprefix");
asm("rotina:");
asm("inc eax");
// EBX = address of str
asm("lea ebx, str");
// ++str[0]
asm("inc byte ptr [ebx]");
asm("ret");
asm(".att_syntax noprefix");
}
// global variables make things simpler
int a;
int main(void) {
a = -7;
puts ("antes");
puts (str);
printf("a = %d\n\n", a);
asm(".intel_syntax noprefix");
asm("mov eax, 0");
asm("call rotina");
// modify variable a
asm("mov a, eax");
asm(".att_syntax noprefix");
printf("depois: \n a = %d\n", a);
puts (str);
return 0;
}