当下一个(跳过的)指令是变量定义时,Shellcode 中的 JMP 意外行为

JMP unexpected behavior in Shellcode when next(skipped) instruction is a variable definition

目的:我试图在 x86-64 中利用 RIP 模式。尽管程序集本身按预期执行,但 shellcode 却没有。

问题:简而言之,我尝试的是这个,

jmp l1
str1: db "some string"
l1:
   other code
   lea rax, [rel str1]

我在很多地方都用过上面的,只是在某些地方失败了,在其他地方成功了。我试着玩,失败时找不到任何模式。当变量(str1:db 指令)位置在访问它的指令之后时,它从未失败(在我的观察中)。但是,我想删除空值,因此我在访问它之前放置了变量定义。

调试发现
在调试时,我发现失败的 jmp 指向一些不正确的指令地址。 例如:(在 gdb 中)

(code + 18) jmp [code +27] //jmp pointing incorrectly to in-between 2
(code + 22) ... (this part has label)
(code + 24) some instruction // this is where I intended the jmp
(code + 28) some other instruction

代码 这是一个示例代码,我试图生成一个 Execve Shell。它很大,所以我确定了罪魁祸首 JMP 的位置。

global _start
section .text
_start: 
    xor rax,rax
    mov rsi,rax
    mov rdi,rsi
    mov rdx,rdi
    mov r8,rdx
    mov rcx,r8
    mov rbx,rcx
    jmp gg //failing (jumping somewhere unintended)
    p2: db "/bin/sh"        
gg:
    xor rax,rax
    lea rdi, [rel p2]
    mov [rdi+7], byte al //null terminating using 0x00 from rax
    mov [rdi+8], rdi
    mov [rdi+16],rax


    lea rsi,[rdi+8]
    lea rdx,[rdi+16]
    mov al,59
    syscall

编辑:1 已修改代码以包含失败的指令

编辑:2 Shell我使用的 C 代码。

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x48\x31\xc0\x48\x89\xc6\x48\x89\xf7\x48\x89\xfa\x49\x89\xd0\x4c\x89\xc1\x48\x89\xcb\xeb\x07\x2f\x62\x69\x6e\x2f\x73\x68\x48\x31\x48\x31\xc0\x48\x8d\x3d\xef\xff\xff\xff\x88\x47\x07\x48\x89\x7f\x08\x48\x89\x47\x10\x48\x8d\x77\x08\x48\x8d\x57\x10\xb0\x3b\x0f\x05";
main()
{

    printf("Shellcode Length:  %d\n", (int)strlen(code));

    int (*ret)() = (int(*)())code;

    ret();

}

编辑 3 我将通过将以下代码放置在 Bash 文件中并通过将文件名作为参数传递 运行 来获取 Hexdump。从 ShellStorm 中获取。

`for i in $(objdump -d $1 -M intel |grep "^ " |cut -f2);做 echo -n '\x'$i`;

在将 // 更改为 ; 后,我可以使用 nasm 编译您的代码,但我没有尝试执行它。

nasm -f elf64 a.s -o a

然后我可以查看代码:

objdump -d a

你问的那部分代码看起来像(即 jmp

  12:   48 89 cb                mov    %rcx,%rbx
  15:   eb 07                   jmp    1e <gg>

0000000000000017 <p2>:
  17:   2f                      (bad)  
  18:   62                      (bad)  
  19:   69                      .byte 0x69
  1a:   6e                      outsb  %ds:(%rsi),(%dx)
  1b:   2f                      (bad)  
  1c:   73 68                   jae    86 <gg+0x68>

000000000000001e <gg>:
  1e:   48 31 c0                xor    %rax,%rax

但是,下面有几个问题:

    mov [rdi+7], byte al ;null terminating using 0x00 from rax
    mov [rdi+8], rdi
    mov [rdi+16],rax
  1. 您正在尝试写入只读内存。代码无法修改

  2. mov rdi/rax可能没有对齐

  3. 如果该代码成功,它将覆盖您在 gg: 的代码。

此外,最好在 gg: 之前放置一个 by alignment 像这样:

p2: "..."
align   16
gg:
   xor rax,rax

因此,由于代码是只读的,您必须手动将零放入其中。

p2: db "..."
    db 0
    db 0, 0, 0, 0, 0, 0, 0, 0
    db 0, 0, 0, 0, 0, 0, 0, 0

如果您知道它是对齐的,dq 也可以。

但是请注意,您也没有对齐 p2,因此您无法确定(即,如果您的代码发生变化,对齐方式也很可能也会发生变化。)您可能想要这样做:

    align 16
p2: db "..."
    db 0
    dq 0
    dq 0

最后一点,[rel p2] 非常有限。据我所知,这些指令中的相对偏移量仅限于 -127 和 +128。不过,在我的 64 位处理器上,它使用 32 位偏移量。您可能 运行 遇到过这样的问题,这取决于您的汇编程序,它认为它太过分了。在您的情况下,一种解决方案是将 lea 指令放在 jmp 之前的数据上。我想如果偏移量溢出,编译器应该会产生一个错误。另一种可能性是某些东西以某种方式得到了优化,并且 jmp 没有得到正确更新。

作为旁注,以下内容没有得到很好的优化:

    xor rax,rax
    mov rsi,rax
    mov rdi,rsi
    mov rdx,rdi
    mov r8,rdx

对所有寄存器使用 xor 或每次重复使用 rax 而不是切换会更好(更有可能并行工作)。现在,您将所有指令与前一个指令相关联(即,在您可以将 rsi 复制到 rdi 之前,您需要将 rax 复制到 rsi。有 mov rdi,rax 会消除这种依赖性。)我认为在这种情况下您不会看到任何差异,但为了实现良好的优化,请记住这一点。

通过对齐并在代码中添加零,我得到:

0000000000000000 <_start>:
   0:   48 31 c0                xor    %rax,%rax
   3:   48 89 c6                mov    %rax,%rsi
   6:   48 89 f7                mov    %rsi,%rdi
   9:   48 89 fa                mov    %rdi,%rdx
   c:   49 89 d0                mov    %rdx,%r8
   f:   4c 89 c1                mov    %r8,%rcx
  12:   48 89 cb                mov    %rcx,%rbx
  15:   eb 29                   jmp    40 <gg>
  17:   90                      nop
  18:   90                      nop
  19:   90                      nop
  1a:   90                      nop
  1b:   90                      nop
  1c:   90                      nop
  1d:   90                      nop
  1e:   90                      nop
  1f:   90                      nop

0000000000000020 <p2>:
  20:   2f                      (bad)  
  21:   62                      (bad)  
  22:   69 6e 2f 73 68 00 00    imul   [=18=]x6873,0x2f(%rsi),%ebp
        ...
  35:   00 00                   add    %al,(%rax)
  37:   00 90 90 90 90 90       add    %dl,-0x6f6f6f70(%rax)
  3d:   90                      nop
  3e:   90                      nop
  3f:   90                      nop

0000000000000040 <gg>:
  40:   48 31 c0                xor    %rax,%rax
  43:   48 8d 3d d6 ff ff ff    lea    -0x2a(%rip),%rdi        # 20 <p2>
  4a:   48 8d 77 08             lea    0x8(%rdi),%rsi
  4e:   48 8d 57 10             lea    0x10(%rdi),%rdx
  52:   b0 3b                   mov    [=18=]x3b,%al
  54:   0f 05                   syscall

请注意,在反汇编时,如果您的数据代表一条指令,它最终可能会使用有效代码中的字节,因此不会显示您原本期望的内容。即它最终可能找不到目标标签。不过这里我们很好。

TL;DR :您用来将独立 shell 代码程序 shellExec 转换为 shell 代码利用字符串的方法是越野车。


根据提供的信息,我怀疑问题出在您使用反汇编输出生成最终字节流并转换为您的 shell 代码字符串的方式。可能反汇编输出有令人困惑的输出和可能重复的值。在尝试反汇编数据(与代码混合)时,它尝试输出最短的可编码指令以完成所有数据的消耗,然后发现您有一个 JMP 目标并在备份以重新同步时复制了一些字节。无论使用什么过程将反汇编转换为二进制文件,都没有考虑到此类问题。

不要使用反汇编输出生成二进制文件。使用 shell 代码生成您的独立可执行文件(我相信 shellExec 是您的文件)并使用 OBJCOPYHEXDUMP生成Cshell代码串:

objcopy -j.text -O binary execShell execShell.bin
hexdump -v -e '"\""x" 1/1 "%02x" ""' execShell.bin

objcopy 命令获取 execShell 可执行文件并仅提取 .text 部分(使用 -j.text 选项)并将二进制数据输出到文件 execShell.binhexdump 命令只是重新格式化二进制文件并以可在 C 字符串中使用的形式输出它。此过程不涉及解析任何令人困惑的反汇编输出,因此不会遇到您遇到的问题。 hexdump 的输出应如下所示:

\x48\x31\xc0\x48\x89\xc6\x48\x89\xf7\x48\x89\xfa\x49\x89\xd0\x4c\x89\xc1\x48\x89\xcb\xeb\x07\x2f\x62\x69\x6e\x2f\x73\x68\x48\x31\xc0\x48\x8d\x3d\xef\xff\xff\xff\x88\x47\x07\x48\x89\x7f\x08\x48\x89\x47\x10\x48\x8d\x77\x08\x48\x8d\x57\x10\xb0\x3b\x0f\x05

这与您的略有不同:

\x48\x31\xc0\x48\x89\xc6\x48\x89\xf7\x48\x89\xfa\x49\x89\xd0\x4c\x89\xc1\x48\x89\xcb\xeb\x07\x2f\x62\x69\x6e\x2f\x73\x68\x48\x31\x48\x31\xc0\x48\x8d\x3d\xef\xff\xff\xff\x88\x47\x07\x48\x89\x7f\x08\x48\x89\x47\x10\x48\x8d\x77\x08\x48\x8d\x57\x10\xb0\x3b\x0f\x05

我已经强调了不同之处。在字节串 /bin/sh 之后,您的输出引入了一个额外的 \x48\x31 。 shell 代码字符串中的额外 2 个字节负责代码不是目标可执行文件中预期的 运行。