ASLR时代的用户态汇编寻址

2024-08-27

为防止恶意程序攻击已知地址,现代编译器默认会开启地址空间随机配置(ASLR)。用汇编语言写程序的时候,ASLR会带来一些额外的麻烦。

这里我们用nasm汇编器。

例如下面的代码:

	;; hello.asm
	extern printf

	global main

	section .data

	fmtStr db "The answer is %d.", 0xa, 0
	ans dd 42

	section .bss

	section .text
main:
	push rbp
	mov rbp, rsp
	mov rdi, fmtStr
	mov rsi, [ans]
	mov rax, 0
	call printf
	
	mov rsp, rbp
	pop rbp
	ret

上述代码所等价的C语言代码为:

#include <stdio.h>

const int ans = 42;

int main() {
	printf("The answer is %d.\n", ans);
	return 0;
}

但是如果尝试用下面的命令编译:

nasm -f elf64 -g main.asm
gcc main.o -o main

会出现如下报错信息:

/usr/bin/ld: main.o: relocation R_X86_64_32S against `.data' can not be used when making a PIE object; recompile with -fPIE
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

这是因为地址空间随机分配之后,链接器无法再直接确定某段数据或者函数代码所在的位置。如果想要编译这段代码,必须在链接时关闭ASLR:

nasm -f elf64 -g main.asm
gcc main.o -o main -no-pie

但是这样会引入安全问题。实际上,在ASLR时代,寻址都应该是相对寻址。

对于非ASLR的程序,如果要获取地址,原本只需要:

mov rdi, fmtStr

现在则需要:

lea rdi, [rel fmtStr]

如果要从内存中加载数据,原本只需要:

mov rsi, [ans]

现在则需要改成:

mov rsi, [rel ans]

如果要调用外部的函数,原本只需要直接调用:

call printf

现在则需要在Procedure Lookup Table (PLT)中查找函数地址:

call printf wrt ..plt

因此,上面的程序需要修改为:

	;; hello.asm
	extern printf

	global main

	section .data

	fmtStr db "The answer is %d.", 0xa, 0
	ans dd 42

	section .bss

	section .text
main:
	push rbp
	mov rbp, rsp
	lea rdi, [rel fmtStr]
	mov rsi, [rel ans]
	mov rax, 0
	call printf wrt ..plt
	
	mov rsp, rbp
	pop rbp
	ret

这样就可以用gcc默认配置链接了:

nasm -f elf64 -g main.asm
gcc main.o -o main

运行结果为:

The answer is 42.


Email: i (at) mistivia (dot) com