使用 CALL 读取 RIP 避免 shellcode 中的 0xFF 字节?
Avoiding 0xFF bytes in shellcode using CALL to read RIP?
我正在尝试编写解码器存根,但我 运行 将 0xFF
限制为不良字符。我正在使用 jmp-call-pop
方法将我编码的 shellcode 的地址放入寄存器中。这是相关的片段:
401012: e8 eb ff ff ff call 0x401002
似乎 call
将始终在其字节中使用 0xFF
。是否有另一条指令在执行时会将 rip
压入堆栈并跳转到另一段代码?我试过手动将地址压入堆栈,但这会导致空字节,因为我的地址有 3 个字节长,需要填充。
我的机器代码中不允许的字节是:
- 00
- FF
call rel32
是唯一的相对编码(间接或远跳转很少有用),所以当然高字节将始终为 00 或 FF,除非你跳转 非常 很远,因为这就是 2 的补码的工作原理。
自修改代码是一种选择(但是您会遇到 chicken/egg 获取代码指针的问题)。 根据漏洞利用机制,您可能有一个指向(靠近)您在 RSP 中的代码的指针。 所以您可能只是 lea rax, [rsp+44]
/ push rax
/ jmp ...
但是 x86-64 不需要 jmp/call/pop 习语。通常,您可以 jmp
处理您的数据,然后使用 RIP 相关的 LEA 和负数 rel32
,但那当然也会有 0xFF
字节。
您可以使用相对于 RIP 的 LEA 和安全的 rel32,然后更正它:
lea rsi, [rel anchor + 0x66666666] ; or [RIP + 0x66666666]
sub rsi, 0x66666666
;...
xor eax,eax
mov al,1 ; __NR_write = 1 x86-64 Linux
mov edi, eax
lea edx, [rax-1 + msglen]
syscall ; write(1, msg, msglen)
lea eax, [rdi-1 + 60] ; __NR_exit
syscall ; sys_exit(1)
anchor:
msg: db "Hello World", 0xa
msglen equ $-msg
用NASM汇编和用objdump -drwC -Mintel
反汇编的机器码:
$ asm-link -dn rel.asm # a helper script to assmble+link and disassemble
+ nasm -felf64 -Worphan-labels rel.asm
+ ld -o rel rel.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
rel: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <anchor-0x1e>:
401000: 48 8d 35 7d 66 66 66 lea rsi,[rip+0x6666667d] # 66a67684 <__bss_start+0x66665684>
401007: 48 81 ee 66 66 66 66 sub rsi,0x66666666
40100e: 31 c0 xor eax,eax
401010: b0 01 mov al,0x1
401012: 89 c7 mov edi,eax
401014: 8d 50 0b lea edx,[rax+0xb]
401017: 0f 05 syscall
401019: 8d 47 3b lea eax,[rdi+0x3b]
40101c: 0f 05 syscall
000000000040101e <anchor>:
40101e: 48 rex.W
... ASCII data that isn't real machine code
401029: 0a .byte 0xa
peter@volta:/tmp$ ./rel
Hello World
$ strace ./rel
execve("./rel", ["./rel"], 0x7ffd09467720 /* 55 vars */) = 0
write(1, "Hello World\n", 12Hello World
) = 12
exit(1) = ?
+++ exited with 1 +++
有趣的是,0x66
是字母 'f'
的 ASCII 码。我在试图避免 0xFF
时并没有故意选择 'f'
:P 但是无论如何,选择你喜欢的任何 4 字节字符串。
rel32
的低字节会更高,这取决于它要达到的距离,所以明智地选择。
实际上正在call
附近的某个地方:
您可以使用上述 RIP 相关的 LEA + 修复技巧来创建自修改代码,例如inc byte [rax]
将 0xFE
变成 0xFF
。或者双字 sub
-立即与 0x11111111
或其他东西可能有助于修复 rel32
call r/m64
and jmp r/m64
都是不能直接使用的,因为操作码本身就是FF /2
和FF /4
如果您想 return,修复 call rel32
或 call rax
可能最简单。但是也可以使用 RIP-relative LEA 计算寄存器中的 return 地址并将其推送,然后 jmp rel8
或 jmp rax
或其他任何内容。
我正在尝试编写解码器存根,但我 运行 将 0xFF
限制为不良字符。我正在使用 jmp-call-pop
方法将我编码的 shellcode 的地址放入寄存器中。这是相关的片段:
401012: e8 eb ff ff ff call 0x401002
似乎 call
将始终在其字节中使用 0xFF
。是否有另一条指令在执行时会将 rip
压入堆栈并跳转到另一段代码?我试过手动将地址压入堆栈,但这会导致空字节,因为我的地址有 3 个字节长,需要填充。
我的机器代码中不允许的字节是:
- 00
- FF
call rel32
是唯一的相对编码(间接或远跳转很少有用),所以当然高字节将始终为 00 或 FF,除非你跳转 非常 很远,因为这就是 2 的补码的工作原理。
自修改代码是一种选择(但是您会遇到 chicken/egg 获取代码指针的问题)。 根据漏洞利用机制,您可能有一个指向(靠近)您在 RSP 中的代码的指针。 所以您可能只是 lea rax, [rsp+44]
/ push rax
/ jmp ...
但是 x86-64 不需要 jmp/call/pop 习语。通常,您可以 jmp
处理您的数据,然后使用 RIP 相关的 LEA 和负数 rel32
,但那当然也会有 0xFF
字节。
您可以使用相对于 RIP 的 LEA 和安全的 rel32,然后更正它:
lea rsi, [rel anchor + 0x66666666] ; or [RIP + 0x66666666]
sub rsi, 0x66666666
;...
xor eax,eax
mov al,1 ; __NR_write = 1 x86-64 Linux
mov edi, eax
lea edx, [rax-1 + msglen]
syscall ; write(1, msg, msglen)
lea eax, [rdi-1 + 60] ; __NR_exit
syscall ; sys_exit(1)
anchor:
msg: db "Hello World", 0xa
msglen equ $-msg
用NASM汇编和用objdump -drwC -Mintel
反汇编的机器码:
$ asm-link -dn rel.asm # a helper script to assmble+link and disassemble
+ nasm -felf64 -Worphan-labels rel.asm
+ ld -o rel rel.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
rel: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <anchor-0x1e>:
401000: 48 8d 35 7d 66 66 66 lea rsi,[rip+0x6666667d] # 66a67684 <__bss_start+0x66665684>
401007: 48 81 ee 66 66 66 66 sub rsi,0x66666666
40100e: 31 c0 xor eax,eax
401010: b0 01 mov al,0x1
401012: 89 c7 mov edi,eax
401014: 8d 50 0b lea edx,[rax+0xb]
401017: 0f 05 syscall
401019: 8d 47 3b lea eax,[rdi+0x3b]
40101c: 0f 05 syscall
000000000040101e <anchor>:
40101e: 48 rex.W
... ASCII data that isn't real machine code
401029: 0a .byte 0xa
peter@volta:/tmp$ ./rel
Hello World
$ strace ./rel
execve("./rel", ["./rel"], 0x7ffd09467720 /* 55 vars */) = 0
write(1, "Hello World\n", 12Hello World
) = 12
exit(1) = ?
+++ exited with 1 +++
有趣的是,0x66
是字母 'f'
的 ASCII 码。我在试图避免 0xFF
时并没有故意选择 'f'
:P 但是无论如何,选择你喜欢的任何 4 字节字符串。
rel32
的低字节会更高,这取决于它要达到的距离,所以明智地选择。
实际上正在call
附近的某个地方:
您可以使用上述 RIP 相关的 LEA + 修复技巧来创建自修改代码,例如inc byte [rax]
将 0xFE
变成 0xFF
。或者双字 sub
-立即与 0x11111111
或其他东西可能有助于修复 rel32
call r/m64
and jmp r/m64
都是不能直接使用的,因为操作码本身就是FF /2
和FF /4
如果您想 return,修复 call rel32
或 call rax
可能最简单。但是也可以使用 RIP-relative LEA 计算寄存器中的 return 地址并将其推送,然后 jmp rel8
或 jmp rax
或其他任何内容。