在堆栈上为 execve 创建一个 arg 数组
Create an arg array for execve on the stack
我想编写一个汇编程序,通过 EXECVE(系统调用 #0x3C)程序 /bin/ls 和开关 -al 来执行。
手册页 (man 2 execve) 指出调用需要三个值:
int execve(const char *filename, char *const argv[], char *const envp[]);
我不太明白如何构建三个参数。据我所知,第一个参数进入 RDI
,第二个进入 RSI
,第三个进入 RDX
。我相信要设置第一个,这样做就足够了
push 0x736c2f2f ;sl//
push 0x6e69622f ;nib/
mov rdi, rsp
对于第三个,事情很简单:
xor r11, r11
mov rdx, r11
我的问题是我不知道如何构建第二个参数,它应该是一个包含 ['/bin//ls', '-aal']
的数组
我需要为 x86-64 编写它,所以请不要 int 0x80
建议。
将以下 C 示例(如果需要修改)加载到 Godbolt 编译器资源管理器中,您可以看到各种编译器通常如何生成程序集以调用 AMD64(或其他)架构上的 execve
。
#include <stdio.h>
#include <unistd.h>
int
main(void) {
char* argv[] = { "/bin/ls", "-al", NULL };
// char* argv[] = { "-al", NULL };
// char* argv[] = { "/bin/lsxxx", "-al", NULL };
// char* argv[] = { "", "-al", NULL };
char* envp[] = { "PATH=/bin", NULL };
if (execve("/bin/ls", argv, envp) == -1) {
perror("Could not execve");
return 1;
}
}
你可以把argv
数组压栈,然后把它的地址载入rsi
。 argv
的第一个成员是指向程序名称的指针,因此我们可以使用我们加载到 rdi
.
中的相同地址
xor edx, edx ; Load NULL to be used both as the third
; parameter to execve as well as
; to push 0 onto the stack later.
push "-aal" ; Put second argument string onto the stack.
mov rax, rsp ; Load the address of the second argument.
mov rcx, "/bin//ls" ; Load the file name string
push rdx ; and place a null character
push rcx ; and the string onto the stack.
mov rdi, rsp ; Load the address of "/bin//ls". This is
; used as both the first member of argv
; and as the first parameter to execve.
; Now create argv.
push rdx ; argv must be terminated by a NULL pointer.
push rax ; Second arg is a pointer to "-aal".
push rdi ; First arg is a pointer to "/bin//ls"
mov rsi, rsp ; Load the address of argv into the second
; parameter to execve.
这也纠正了问题中代码的其他几个问题。它对文件名使用 8 字节推送,因为 x86-64 不支持 4 字节推送,并且它确保文件名具有空终止符。
此代码确实使用 64 位推送和 4 字节立即数来推送“-aal”,因为该字符串适合 4 个字节。这也使得它在代码中不需要空字节的情况下以空终止。
我使用了带有双字符的字符串,因为它们在问题中是为了避免代码中出现空字节,但我的偏好是这样的:
mov ecx, "X-al" ; Load second argument string,
shr ecx, 8 ; shift out the dummy character,
push rcx ; and write the string to the stack.
mov rax, rsp ; Load the address of the second argument.
mov rcx, "X/bin/ls" ; Load file name string,
shr rcx, 8 ; shift out the dummy character,
push rcx ; and write the string onto the stack.
请注意,文件名字符串通过移位得到一个空终止符,避免了额外的推送。此模式适用于双字符不起作用的字符串,也适用于较短的字符串。
您可以在 NASM 中写入 push '/bin'
以按该顺序将字节放入内存。 (用 4 个字节的零填充,总宽度为 qword;双字推送在 64 位模式下是不可能的。)无需手动编码 ASCII 字符;与某些汇编器不同 NASM 不会吸收多字符文字,并且可以让您的生活更轻松。
使用mov dword [rsp+4], '//ls'
存储高半部分。 (或者使它成为一个 qword 存储,用 mov r/m64, sign_extended_imm32
写入另外 4 个字节的零。或者只是用另一个推零终止它。
或mov eax, '//ls'
; shl eax, 8
在准备存储的寄存器中获取 EAX="/ls[=15=]"
以生成 8 字节的 0 终止字符串。
或者使用相同的技巧在 mov r64, imm64
之后移出一个字节(就像@prl 的回答)而不是单独的 push / mov。或者不是你的文字数据,所以你做 mov rax, imm64
/ not rax
/ push rax
,在你的寄存器中产生零而不在机器代码中产生零。
我想编写一个汇编程序,通过 EXECVE(系统调用 #0x3C)程序 /bin/ls 和开关 -al 来执行。
手册页 (man 2 execve) 指出调用需要三个值:
int execve(const char *filename, char *const argv[], char *const envp[]);
我不太明白如何构建三个参数。据我所知,第一个参数进入 RDI
,第二个进入 RSI
,第三个进入 RDX
。我相信要设置第一个,这样做就足够了
push 0x736c2f2f ;sl//
push 0x6e69622f ;nib/
mov rdi, rsp
对于第三个,事情很简单:
xor r11, r11
mov rdx, r11
我的问题是我不知道如何构建第二个参数,它应该是一个包含 ['/bin//ls', '-aal']
我需要为 x86-64 编写它,所以请不要 int 0x80
建议。
将以下 C 示例(如果需要修改)加载到 Godbolt 编译器资源管理器中,您可以看到各种编译器通常如何生成程序集以调用 AMD64(或其他)架构上的 execve
。
#include <stdio.h>
#include <unistd.h>
int
main(void) {
char* argv[] = { "/bin/ls", "-al", NULL };
// char* argv[] = { "-al", NULL };
// char* argv[] = { "/bin/lsxxx", "-al", NULL };
// char* argv[] = { "", "-al", NULL };
char* envp[] = { "PATH=/bin", NULL };
if (execve("/bin/ls", argv, envp) == -1) {
perror("Could not execve");
return 1;
}
}
你可以把argv
数组压栈,然后把它的地址载入rsi
。 argv
的第一个成员是指向程序名称的指针,因此我们可以使用我们加载到 rdi
.
xor edx, edx ; Load NULL to be used both as the third
; parameter to execve as well as
; to push 0 onto the stack later.
push "-aal" ; Put second argument string onto the stack.
mov rax, rsp ; Load the address of the second argument.
mov rcx, "/bin//ls" ; Load the file name string
push rdx ; and place a null character
push rcx ; and the string onto the stack.
mov rdi, rsp ; Load the address of "/bin//ls". This is
; used as both the first member of argv
; and as the first parameter to execve.
; Now create argv.
push rdx ; argv must be terminated by a NULL pointer.
push rax ; Second arg is a pointer to "-aal".
push rdi ; First arg is a pointer to "/bin//ls"
mov rsi, rsp ; Load the address of argv into the second
; parameter to execve.
这也纠正了问题中代码的其他几个问题。它对文件名使用 8 字节推送,因为 x86-64 不支持 4 字节推送,并且它确保文件名具有空终止符。
此代码确实使用 64 位推送和 4 字节立即数来推送“-aal”,因为该字符串适合 4 个字节。这也使得它在代码中不需要空字节的情况下以空终止。
我使用了带有双字符的字符串,因为它们在问题中是为了避免代码中出现空字节,但我的偏好是这样的:
mov ecx, "X-al" ; Load second argument string,
shr ecx, 8 ; shift out the dummy character,
push rcx ; and write the string to the stack.
mov rax, rsp ; Load the address of the second argument.
mov rcx, "X/bin/ls" ; Load file name string,
shr rcx, 8 ; shift out the dummy character,
push rcx ; and write the string onto the stack.
请注意,文件名字符串通过移位得到一个空终止符,避免了额外的推送。此模式适用于双字符不起作用的字符串,也适用于较短的字符串。
您可以在 NASM 中写入 push '/bin'
以按该顺序将字节放入内存。 (用 4 个字节的零填充,总宽度为 qword;双字推送在 64 位模式下是不可能的。)无需手动编码 ASCII 字符;与某些汇编器不同 NASM 不会吸收多字符文字,并且可以让您的生活更轻松。
使用mov dword [rsp+4], '//ls'
存储高半部分。 (或者使它成为一个 qword 存储,用 mov r/m64, sign_extended_imm32
写入另外 4 个字节的零。或者只是用另一个推零终止它。
或mov eax, '//ls'
; shl eax, 8
在准备存储的寄存器中获取 EAX="/ls[=15=]"
以生成 8 字节的 0 终止字符串。
或者使用相同的技巧在 mov r64, imm64
之后移出一个字节(就像@prl 的回答)而不是单独的 push / mov。或者不是你的文字数据,所以你做 mov rax, imm64
/ not rax
/ push rax
,在你的寄存器中产生零而不在机器代码中产生零。