当下一个(跳过的)指令是变量定义时,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
您正在尝试写入只读内存。代码无法修改
mov rdi/rax可能没有对齐
如果该代码成功,它将覆盖您在 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
是您的文件)并使用 OBJCOPY 和 HEXDUMP生成Cshell代码串:
objcopy -j.text -O binary execShell execShell.bin
hexdump -v -e '"\""x" 1/1 "%02x" ""' execShell.bin
objcopy
命令获取 execShell
可执行文件并仅提取 .text
部分(使用 -j.text
选项)并将二进制数据输出到文件 execShell.bin
。 hexdump
命令只是重新格式化二进制文件并以可在 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 个字节负责代码不是目标可执行文件中预期的 运行。
目的:我试图在 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
您正在尝试写入只读内存。代码无法修改
mov rdi/rax可能没有对齐
如果该代码成功,它将覆盖您在
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
是您的文件)并使用 OBJCOPY 和 HEXDUMP生成Cshell代码串:
objcopy -j.text -O binary execShell execShell.bin
hexdump -v -e '"\""x" 1/1 "%02x" ""' execShell.bin
objcopy
命令获取 execShell
可执行文件并仅提取 .text
部分(使用 -j.text
选项)并将二进制数据输出到文件 execShell.bin
。 hexdump
命令只是重新格式化二进制文件并以可在 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 个字节负责代码不是目标可执行文件中预期的 运行。