|
【方法】在Linux C程序中嵌入汇编语言 |
一派護法 十九級 |
首先,编写一个简单的C程序: #include <stdio.h>
void fun(int *num, int *num2);
int main() { int a = 10, b = 32; fun(&a, &b); printf("a=%d, b=%d\n", a, b); return 0; } 其中主函数调用了fun函数。 fun函数我们打算用汇编语言来写,其功能是把*num的值加15, *num2的值加5。
|
一派護法 十九級 |
GLOBAL fun
fun: MOV ECX, [ESP+4] ; 把第一个参数的值送入ECX寄存器,其中存储的是变量a的地址 ADD DWORD[ECX], 15 ; []表示取内存内容,DWORD就是类型int的大小,把ECX指向的内存区域的值加15 MOV ECX, [ESP+8] ; 取第二个参数的值 ADD DWORD[ECX], 5 RET ; 返回
|
一派護法 十九級 |
Makefile文件: # 连接 test: test.o hello.o gcc -m32 test.o hello.o -o test
# 编译test.c,生成32位的目标文件 test.o: test.c gcc -m32 -c test.c
# 编译hello.asm,生成目标文件 hello.o: hello.asm nasm -f elf hello.asm
|
一派護法 十九級 |
编译,运行程序: octopus@pc3:~/Documents/Codes/C/link2$ make gcc -m32 -c test.c nasm -f elf hello.asm gcc -m32 test.o hello.o -o test octopus@pc3:~/Documents/Codes/C/link2$ ./test a=25, b=37 octopus@pc3:~/Documents/Codes/C/link2$
|
一派護法 十九級 |
为了能在64位系统下编译32位的程序,应该为gcc安装: sudo apt-get install libc6-dev-i386
|
一派護法 十九級 |
如果想要fun函数返回值,可以将其放入EAX寄存器里面: MOV EAX, 12345678 ; 返回值 RET ; 返回
C程序部分: #include <stdio.h> int fun(int *num, int *num2); int main() { int a = 10, b = 32; int c = fun(&a, &b); printf("a=%d, b=%d\n", a, b); printf("c=%d\n", c); return 0; } 运行结果: $ ./test a=25, b=37 c=12345678
|
一派護法 十九級 |
返回64位值的方法: GLOBAL fun fun: MOV EDX, 0xabcdef01 ; 返回值的高32位 MOV EAX, 0x12345678 ; 返回值的低32位 RET
#include <stdio.h> long long fun(); int main() { long long v = fun(); printf("返回值 = %lld\n", v); printf("高32位: %#08lx\n", (long)(v >> 32)); printf("低32位: %#08lx\n", (long)(v & 0xffffffff)); return 0; }
运行结果: 返回值 = -6066930335118764424 高32位: 0xabcdef01 低32位: 0x12345678
|
一派護法 十九級 |
返回字符串指针的方法: GLOBAL fun fun: MOV EAX, msg ; 把数据段的地址作为返回值返回 RET
; 数据段 msg: DB "Hello, World!" DB 0
#include <stdio.h> char *fun(); int main() { char *str = fun(); puts(str); return 0; }
程序输出: Hello, World!
|
一派護法 十九級 |
其实,在C语言中可以直接通过引用外部变量的方法引用数据段msg: 【汇编部分】 GLOBAL msg msg: DB "Hello, World!" DB 0 【C语言部分】 #include <stdio.h> extern char msg; int main() { // 变量msg只是第一个字符 printf("第1个字符: %c\n", msg); // 要想输出其他字符,必须先得到msg的地址,然后对这个地址进行加法运算 printf("第2个字符: %c\n", *(&msg + 1)); printf("第3个字符: %c\n", *(&msg + 2)); // 可以用下面的方法输出整个字符串 char *pMsg = &msg; puts(pMsg); return 0; }
【输出】 $ ./test 第1个字符: H 第2个字符: e 第3个字符: l Hello, World!
|
一派護法 十九級 |
如果把msg变量声明为数组的话,也能直接输出字符串: #include <stdio.h> #include <string.h> extern char msg[]; int main() { puts(msg); printf("length: %d\n", strlen(msg)); return 0; } 运行结果: Hello, World! length: 13
但是,不能声明成字符串指针: extern char *msg; 否则会出现段错误。
|
一派護法 十九級 |
汇编语言中的除法指令: 【汇编部分】 GLOBAL div div: MOV AX, [ESP+4] DIV BYTE[ESP+8] RET 【C语言部分】 #include <stdio.h> short div(unsigned char a, unsigned char b); int main() { short num = div(10, 3); printf("%d ... %d\n", num & 0xff, num >> 8); return 0; } 运行结果: 3 ... 1
|
一派護法 十九級 |
在汇编语言中调用C语言函数: 【汇编部分】 GLOBAL fun fun: CALL [ESP+4] RET 【C语言部分】 #include <stdio.h> void fun(void (*f)()); void test() { printf("This is a string.\n"); } int main() { fun(test); return 0; } 输出: This is a string.
|
一派護法 十九級 |
汇编语言创建C语言可写的数组的方法: 【汇编部分】 SECTION .bss GLOBAL arr arr: RESB 20 【C语言部分】 #include <stdio.h> #include <string.h> extern char arr[20]; int main() { strcpy(arr, "This is a string."); puts(arr); return 0; }
输出: This is a string.
|
一派護法 十九級 |
关于汇编语言中的三个段: 【汇编部分】 ; BSS段中专门存放未初始化的变量 SECTION .bss GLOBAL arr arr: RESB 20
; 这个段中不可以定义函数 ;fun3: ; RET
; DATA段中专门存放已初始化的变量 ; 其中的内容可读可写 SECTION .data GLOBAL msg msg: DB "Hello, World!" DB 0
GLOBAL fun2 fun2: RET
; TEXT段中的内容是只读的 SECTION .text GLOBAL str GLOBAL fun str: DB "abcdef" DB 0 fun: MOV EAX, 14 RET 【C语言部分】 #include <stdio.h> #include <string.h> extern char arr[20]; extern char msg[]; extern char str[]; int fun(); void fun2(); int main() { strcpy(arr, "This is a string."); puts(arr); msg[0] = 'I'; puts(msg); printf("%d\n", fun()); //str[0] = 'm'; // 只读!将会引发段错误 puts(str); fun2(); return 0; } 【输出】 This is a string. Iello, World! 14 abcdef
|
一派護法 十九級 |
接下来我们来一点刺激的! 把一个C语言数组拿来执行!
【C语言部分】 #include <stdio.h> #include <string.h>
extern char fun, end; // fun函数的开始和结束位置 char codes[200];
int main() { int size = &end - &fun; // fun函数的机器代码大小 printf("size=%d\n", size); memcpy(codes, &fun, size); // 把fun函数的机器代码复制到codes数组中 // 执行codes数组中所存放的机器代码,并读取代码执行完毕后EAX寄存器中的值 int (*fun)() = (int (*)())codes; int v = fun(); printf("%d\n", v); return 0; } 【汇编部分】 GLOBAL fun GLOBAL end fun: MOV EAX, 48 RET end:
【运行结果】 size=6 48
|
一派護法 十九級 |
int (*fun)() = (int (*)())codes; 这句话的意思就是:先定义一个fun变量,变量的类型为函数指针。 然后把codes变量(本来是一个数组)强行转换成函数指针,赋给fun变量。
|
一派護法 十九級 |
16楼所示的代码有点没写好,因为main函数中有两个fun,所以干脆改一个名字吧,更容易理解: #include <stdio.h> #include <string.h>
extern char fun, end; // fun函数的开始和结束位置 char codes[200];
int main() { int (*fff)(); // 定义一个函数指针 int v; int size = &end - &fun; // fun函数的机器代码大小 printf("size=%d\n", size); memcpy(codes, &fun, size); // 把fun函数的机器代码复制到codes数组中 // 执行codes数组中所存放的机器代码,并读取代码执行完毕后EAX寄存器中的值 fff = (int (*)())codes; v = fff(); printf("%d\n", v); return 0; }
|
一派護法 十九級 |
实际上,不需要定义函数指针,就能直接执行codes数组: v = ((int (*)())codes)(); printf("%d\n", v); 只不过括号比较多而已。
|