assembly 在堆栈上为execve创建一个arg数组

unguejic  于 2023-05-29  发布在  其他
关注(0)|答案(3)|浏览(118)

我想写一个汇编程序,通过EXECVE(syscall#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建议。

ua4mk5z4

ua4mk5z41#

您可以将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.

这也纠正了问题中代码的其他几个问题。由于x86-64不支持4字节的push,因此它使用8字节的push作为文件名,并确保文件名具有空终止符。
这段代码确实使用了一个64位的push和一个4字节的立即数来push“-aal”,因为字符串适合4个字节。这也使得它以null结尾,而不需要在代码中使用null字节。
我在问题中使用了双字符串,以避免代码中的空字节,但我的首选是这样的:

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.

请注意,文件名字符串通过移位得到一个空终止符,避免了额外的推送。这种模式适用于双精度字符不起作用的字符串,也可以用于较短的字符串。

91zkwejq

91zkwejq2#

您可以在NASM中写入push '/bin',以按该顺序将字节放入内存。(用4个字节的零填充,总宽度为qword;在64位模式下,dword推送是不可能的。)无需手动编码ASCII字符;不像某些汇编器,NASM不需要多字符文字,可以让你的生活更轻松。
您可以使用use mov dword [rsp+4], '//ls'来存储高半部分。(或者使用mov r/m64, sign_extended_imm32将其设置为qword存储,以便在此基础上再写入4个字节的零。)或者如果您希望存储8个字节,则在执行mov rsi, '/bin//ls'/push rsi之前使用较早的push将其零终止。
mov eax, '//ls'; shr eax, 8以在寄存器中获得EAX= "/ls\0",准备存储以形成8字节0终止的字符串。
或者使用相同的技巧,在mov r64, imm64之后移出一个字节(就像@prl的答案一样),而不是单独的push / mov。或者NOT你的文字数据,所以你做mov rax, imm64/not rax/push rax,在你的寄存器中产生零,而机器码中没有零。例如:

mov  rsi, ~`/bin/ls\0`   ; mov rsi, 0xff8c93d091969dd0
 not  rsi
 push rsi                 ; RSP points to  "/bin/ls", 0

如果你想让尾部字节保持隐式,而不是显式的\0,你可以写mov rsi, ~'/bin/ls',它组装成相同的mov rsi, 0xff8c93d091969dd0。NASM语法中的反引号处理C风格的转义序列,与单引号或双引号不同。我建议使用\0来提醒自己为什么要麻烦地使用这个NOT和~按位求反汇编时运算符。(在NASM中,多字符文字用作整数常量。)
我相信,建立第一个,这就足够了

push 0x736c2f2f         ;sl//
  push 0x6e69622f         ;nib/
  mov rdi, rsp

否,push 0x736c2f2f是8字节的推送,该值的符号扩展为64位。所以你按了'/bin\0\0\0\0//ls\0\0\0\0'

可能您是从32位代码中复制的,其中push 0x736c2f2f是4字节的推送,但64位代码是不同的。
x86-64不能编码4字节的push,只有2或8字节的操作数大小。标准技术是一次推送8个字节:

mov   rdi, '/bin//ls'     ; 10-byte mov r64, imm64
  push  rdi
  mov   rdi, rsp

如果您有奇数个4字节块,第一个可以是push imm32,然后使用8字节对。如果它不是4的倍数,并且您不能填充像/mov dword [mem], imm32这样的冗余字符,* 部分 * 重叠可能会有所帮助,或者将值放入寄存器并移位以引入零字节。

4bbkushb

4bbkushb3#

将下面的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;
   }  
}

相关问题