jmp rel16 只能使用 16 的操作数大小进行编码,这会将 EIP 截断为 16 位。 (编码需要 32 位和 64 位模式下的 66 操作数大小前缀)。如您链接的指令集参考中所述,或in this more up-to-date PDF->HTML conversion of Intel's manual,jmp 在操作数大小为 16 时执行EIP ← tempEIP AND 0000FFFFH;。这就是为什么汇编程序从不使用它的原因,除非您手动请求它1,以及为什么您不能在 32 位或 64 位代码中使用 jmp rel16,除非在非常罕见的情况下,目标映射到低 64kiB 的虚拟地址空间2.
避免jmp rel32
您只是向前跳跃,因此您可以使用call rel32 推送数据的地址,并且因为您希望您的数据一直位于长填充负载的末尾。
您可以使用push imm32/imm8/reg 和mov ebx, esp 在堆栈上构造一个字符串。 (您已经有一个归零的寄存器,您可以推送终止的零字节)。
如果您不想在堆栈上构造数据,而是使用属于有效负载一部分的数据,请使用与位置无关的代码/相对寻址。 也许您的寄存器中有一个值与 EIP 有一个已知的偏移量,例如如果您的漏洞利用代码通过 jmp esp 或其他 ret-2-reg 攻击 到达。在这种情况下,您也许可以
mov ecx, 0x12345678 / shr ecx, 16 / lea ebx, [esp+ecx]。
或者,如果您必须使用 NOP 雪橇并且您不知道 EIP 相对于任何寄存器值的确切值,您可以使用 call 获取 EIP 的当前值负位移指令。 向前跳过call 目标,然后call 回到它。您可以在call 之后放置数据。 (但避免数据中的零字节是不方便的;一旦获得指向它的指针,您就可以存储一些。)
# Position-independent 32-bit code to find EIP
# and get label addresses into registers
# and insert zeros into data that we jumped over.
jmp .Lcall
.Lget_eip:
pop ebx
jmp .Lafter_call # jmp rel8
.Lcall: call .Lget_eip # backward rel32 = 0xffffff??
# execution never returns here
.Lmsg: .ascii "/path/to/fs/file/" # last byte to be overwritten
msglen = . - .Lmsg
.Loffset_data2: .long .Ldata2 - .Lmsg # relative offset to other data, or make this a 16-bit int to avoid zeros
# max data size 127 - 5 bytes
.Lafter_call:
# EBX = OFFSET .Lmsg just from the call + pop
# Insert a zero at runtime because the data wasn't at the end of the payload
mov byte ptr [ebx+ msglen - 1], al # with al=0
# ESI = OFFSET .Ldata2 using an offset loaded from memory
mov esi, ebx
add esi, [ebx + .Loffset_data2 - .Lmsg] # [ebx + disp8]
# with an immediate displacement, avoiding zero bytes
mov ecx, ((.Ldata3 - .Lmsg) << 17) | 0xffff
shr ecx, 17 # choose shift count to avoid high zeros
lea edi, [ebx + ecx] # edi = OFFSET .Ldata3
# if disp8 doesn't work but 8 * disp8 does: small code size
push (.Ldata3 - .Lmsg)>>8 # push imm8
pop ecx
lea edi, [ebx + ecx*8 + (.Ldata3 - .Lmsg)&7] # disp8 of the low 3 bits
...
# at the end of your payload
.Ldata2:
whatever you want, arbitrary size
.Ldata3:
在 64 位代码中,这要容易得多:
# In 64-bit code
jmp .Lafter_data
.Lmsg1: .ascii "/foo/bar/" # last bytes to be replaced
.Lmsg2: .ascii "/bin/sh/"
.Lafter_data:
lea rdi, [RIP + .Lmsg1] # negative rel32
lea rsi, [rdi + .Lmsg2 - .Lmsg1] # disp8
xor eax,eax
mov byte ptr [rsi - 1], al # insert zeros
mov byte ptr [rsi + len], al
或者使用相对于 RIP 的 LEA 来获取标签地址,并使用一些避免零的方法向其添加立即常量,以获取有效负载末尾的标签地址。
.Lbase:
lea rdi, [RIP + .Lbase]
xor ecx,ecx
mov cx, .Lpath - .Lbase
add rdi, rcx # RDI = .Lpath address
...
syscall
... # more than 128 bytes
.Lpath:
.asciz "/foo/bar"
如果您真的需要跳得更远,而不仅仅是对远处的“静态”数据进行与位置无关的寻址。
一连串的短前跳跃会起作用。
或者使用上述任何一种方法在寄存器中查找后面标签的地址,并使用jmp eax。
保存代码字节:
在您的情况下,节省代码大小并不能帮助您避免跳远位移,但可能对其他人来说会:
您可以使用这些Tips for golfing in x86/x64 machine code 保存代码字节:
-
xor eax,eax / cdq 比 xor edx,edx 节省 1 个字节。
-
xor ecx, ecx / mul ecx 将 4 个字节中的三个寄存器归零(ECX 和 EDX:EAX)
- 实际上,
int 0x80 设置的最佳选择可能是
xor ecx,ecx (2B) / lea eax, [ecx+5] (3B) / cdq (1B),并且根本不要使用 mov al,5 .您可以使用push imm8 / pop 或一个lea 将任意小的常量放入寄存器中,只需3 个字节,如果您有另一个具有已知值的寄存器。
脚注 1:要求您的汇编程序在 16 位模式之外对 jmp rel16 进行编码:
NASM(在 16、32 或 64 位模式下)
addr:
; times 256 db 0 ; padding to make it jump farther.
o16 jmp near addr ; force 16-bit operand-size and near (not short) displacement
AT&T 语法:
objdump -d 将其解码为jmpw:对于上述 NASM 源组装成 32 位静态 ELF 二进制文件,objdump -drwC foo 显示 EIP 的截断:
0000000000400080 <addr>:
400080: 66 e9 fc ff jmpw 80 <addr-0x400000>
但 GAS 似乎认为助记符仅用于间接跳转(这意味着 16 位负载)。 (foo.S:5: Warning: indirect jmp without '*'),这个 GAS 来源:.org 1024; addr: .zero 128; jmpw addr 给你
480: 66 ff 25 00 04 00 00 jmpw *0x400 483: R_386_32 .text
参见what is jmpl instruction in x86? - GAS 如何处理 AT&T 语法的这种疯狂的不一致甚至适用于jmpl。在 16 位模式下组装时,纯 jmp 0x400 将是相对跳转到该绝对偏移量。
在极不可能的情况下,您需要在其他模式下使用 jmp rel16,您必须自己使用 .byte 和 .short 组装它。我认为甚至没有办法让汇编器为你发出它。
脚注 2:您不能在 32/64 位代码中使用 jmp rel16,除非您正在攻击映射在低 64kiB 虚拟地址空间中的某些代码,例如也许在 DOSEMU 或 WINE 下运行的东西。 Linux 对/proc/sys/vm/mmap_min_addr 的默认设置是 65536,而不是 0,因此即使您想通过 ELF 程序加载器将其文本段加载到该地址,通常也没有任何东西可以mmap 该内存。 (所以 NULL 指针取消引用偏移段错误,而不是静默访问内存)。
您可以确定您的 CTF 目标不会碰巧在 EIP = IP 的情况下运行,并且将 EIP 截断为 IP 只会出现段错误。