Assembly 气体汇编程序不使用2字节相对JMP位移编码(仅1字节或4字节)
我正在尝试为不允许0x00字节(它将被解释为终止符)的CTF质询编写外壳代码。由于挑战的限制,我必须这样做:Assembly 气体汇编程序不使用2字节相对JMP位移编码(仅1字节或4字节),assembly,x86,gnu-assembler,shellcode,machine-code,Assembly,X86,Gnu Assembler,Shellcode,Machine Code,我正在尝试为不允许0x00字节(它将被解释为终止符)的CTF质询编写外壳代码。由于挑战的限制,我必须这样做: [shellcode bulk] [(0x514 - sizeof(shellcode bulk)) filler bytes] [fixed constant data to overwrite global symbols] [shellcode data] 看起来像这样 .intel_syntax noprefix .code32 shellcode: jmp sc_d
[shellcode bulk]
[(0x514 - sizeof(shellcode bulk)) filler bytes]
[fixed constant data to overwrite global symbols]
[shellcode data]
看起来像这样
.intel_syntax noprefix
.code32
shellcode:
jmp sc_data
shellcode_main:
#open
xor eax, eax
pop ebx //file string
xor ecx, ecx //flags
xor edx, edx //mode
mov al, 5 //sys_OPEN
int 0x80
... // more shellcode
.org 514, 0x41 // filler bytes
.long 0xffffffff // bss constant overwrite
sc_data:
call shellcode_main
.asciz "/path/to/fs/file"
如果sc_数据
在shell代码
的127字节范围内,则此功能运行良好。在这种情况下,汇编程序(GAS)将输出一个短跳转格式:
Opcode Mnemonic
EB cb JMP rel8
但是,由于我有一个硬限制,即大容量外壳代码和填充字节需要0x514字节,因此此相对偏移量将至少需要2个字节。这也是可行的,因为jmp
指令有一个2字节的相对编码:
Opcode Mnemonic
E9 cw JMP rel16
不幸的是,GAS没有输出这种编码。而是使用4字节偏移量编码:
Opcode Mnemonic
E9 cd JMP rel32
这将导致两个MSB字节的零。类似于:
e9 01 02 00
我的问题是:GAS可以被迫输出jmp
指令的2字节变量吗?我曾尝试过多个较小的1字节jmp
s,但GAS一直输出4字节变量。我还尝试使用-Os
调用GCC来优化大小,但它坚持使用4字节相对偏移量编码
英特尔跳转操作码定义供参考。
jmp rel16
仅可在操作数大小为16时进行编码,这会将EIP截断为16位。(在32位和64位模式下,编码需要66
操作数大小前缀)。如您链接的指令集参考中所述,或者,jmp
doesEIP← 坦佩普和0000FFFFH代码>当操作数大小为16时。这就是为什么汇编器从不使用它,除非您手动请求它1,以及为什么您不能在32位或64位代码中使用jmp rel16
,除非在非常罕见的情况下,目标映射到虚拟地址空间2的低64kiB
避免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相对于任何寄存器值的确切值,您可以使用带负位移的调用指令获得EIP的当前值向前跳过调用
目标,然后调用
返回目标。您可以将数据放在该调用
之后。(但是在数据中避免零字节是不方便的;一旦得到指向它的指针,就可以存储一些字节。)
或者使用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
保存代码字节:
在您的情况下,保存代码大小并不能帮助您避免跳远位移,但对于其他一些人可能会:
您可以使用以下方法保存代码字节:
xor-eax,eax
/cdq
比xor-edx,edx
节省1个字节
xor ecx,ecx
/mul ecx
将三个寄存器归零为4个字节(ecx和EDX:EAX)
- 实际上,对于
int0x80
设置,您的最佳选择可能是
xor ecx,ecx
(2B)/leaeax,[ecx+5]
(3B)/cdq
(1B),并且根本不要使用mov al,5
。您可以使用push imm8
/pop
,或使用一个lea
,如果您有另一个具有已知值的寄存器,则可以在寄存器中以仅3个字节的时间放置任意小常量
脚注1:要求汇编程序在16位模式之外编码jmp rel16
:
NASM(在16、32或64位模式下)
AT&T语法:
objdump-d
将其解码为jmpw
:对于组装成32位静态ELF二进制的上述NASM源,objdump-drwC foo
显示EIP的截断:
0000000000400080 <addr>:
400080: 66 e9 fc ff jmpw 80 <addr-0x400000>
请参阅-GAS处理AT&T语法的这种疯狂的不一致性甚至适用于jmpl
。在16位模式下组装时,普通的jmp 0x400
将相对跳转到该绝对偏移量
在极不可能的情况下,您需要在其他模式下使用jmprel16
,您必须自己使用.byte
和.short
组装它。我认为甚至没有办法让汇编程序为您发出它
脚注2:您不能在32/64位代码中使用jmp rel16
,除非您正在攻击某些映射到低64kiB虚拟地址空间的代码,例如,可能是在DOSEMU或WINE下运行的代码。Linux的默认设置是65536,而不是0,因此通常情况下,即使您想加载内存,也无法mmap
,或者可能无法加载其文本segm
.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"
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
0000000000400080 <addr>:
400080: 66 e9 fc ff jmpw 80 <addr-0x400000>
480: 66 ff 25 00 04 00 00 jmpw *0x400 483: R_386_32 .text