jmp rel16
is only encodeable with an operand-size of 16, which truncates EIP to 16 bits. (The encoding requires a 66
operand-size prefix in 32 and 64-bit mode). As described in the instruction-set reference you linked, or in this more up-to-date PDF->HTML conversion of Intel's manual, jmp
does EIP ← tempEIP AND 0000FFFFH;
when the operand-size is 16. This is why assemblers never use it unless you manually request it1, and why you can't use jmp rel16
in 32 or 64-bit code except in the very unusual case where the target is mapped in the low 64kiB of virtual address space2.
避免jmp rel32
你只是向前跳跃,这样你就可以使用call rel32
推送数据的地址,并且因为您希望数据一直位于长填充有效负载的末尾。
您可以在堆栈上构造一个字符串push imm32/imm8/reg
and mov ebx, esp
。 (您已经有一个归零寄存器,可以将其推入终止零字节)。
如果您不想在堆栈上构造数据,而是使用属于有效负载一部分的数据,请对其使用位置无关代码/相对寻址。也许寄存器中有一个与 EIP 的已知偏移量的值,例如如果您的漏洞利用代码是通过jmp esp
或其他 ret-2-reg 攻击。在这种情况下,您也许可以
mov ecx, 0x12345678
/ shr ecx, 16
/ lea ebx, [esp+ecx]
.
或者,如果您必须使用 NOP 雪橇并且您不知道 EIP 相对于任何寄存器值的确切值,您可以通过以下方式获取EIP的当前值call
带负位移的指令。向前跳过去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
.
保存代码字节:
在您的情况下,节省代码大小并不能帮助您避免长跳转位移,但对于其他一些人来说可能会:
您可以使用这些来保存代码字节使用 x86/x64 机器代码打高尔夫球的技巧:
-
xor eax,eax
/ cdq
与 相比节省 1 个字节xor edx,edx
.
-
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
根本不。您可以将任意小的常量放入仅 3 个字节的寄存器中push imm8
/ pop
,或与一个lea
如果您有另一个具有已知值的寄存器。
脚注 1:要求汇编器进行编码jmp rel16
16 位模式之外:
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 '*'
),以及该气体源:.org 1024; addr: .zero 128; jmpw addr
给你
480: 66 ff 25 00 04 00 00 jmpw *0x400 483: R_386_32 .text
See x86中的jmpl指令是什么?- GAS 如何处理 AT&T 语法的这种疯狂的不一致甚至适用于jmpl
。清楚的jmp 0x400
当以 16 位模式进行汇编时,将是相对跳转到该绝对偏移量。
在极不可能的情况下,你想要一个jmp rel16
在其他模式下,你必须自己组装.byte
and .short
。我认为甚至没有办法让汇编器为您发出它。
脚注 2:您不能使用jmp rel16
在 32/64 位代码中,除非您攻击的是映射在低 64kiB 虚拟地址空间中的某些代码,例如也许是在 DOSEMU 或 WINE 下运行的东西。 Linux 的默认设置为/proc/sys/vm/mmap_min_addr是 65536,不是 0,所以通常没有什么可以mmap
即使您愿意,也可以将其文本段加载到该内存,或者可能通过 ELF 程序加载器将其文本段加载到该地址。 (因此 NULL 指针通过偏移段错误取消引用,而不是默默地访问内存)。
您可以确定您的 CTF 目标不会碰巧以 EIP = IP 运行,并且将 EIP 截断为 IP 只会出现段错误。