call rel32
- единственное относительное кодирование (а косвенные или дальние jmp редко бывают полезны), так что да, конечно, старший байт (ы) всегда будет 00 или FF, если вы не прыгаете очень далеко, потому что именно так работает дополнение 2.
Самоизменяющийся код будет одним из вариантов (но тогда у вас есть проблема курицы / яйца с получением указателя на ваш код). В зависимости от механизма эксплойта у вас может быть указатель (рядом) с вашим кодом в RSP. Таким образом, вы можете просто lea rax, [rsp+44]
/ push rax
/ jmp ...
Но x86-64 не нуждается в идиоме jmp / call / pop. Обычно вы можете просто jmp
над вашими данными и затем использовать REA-относительный LEA с отрицательным rel32
, но это, конечно, также будет 0xFF
байт.
Вы можете использовать REA-относительный 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
- это код ASCII для буквы 'f'
. Я намеренно не выбрал 'f'
, пытаясь избежать 0xFF
: P Но в любом случае, выберите любую 4-байтовую строку, которая вам нравится.
Младший байт rel32
будет выше в зависимости от того, как далеко он должен пройти, поэтому выбирайте мудро.
На самом деле делает call
где-то поблизости:
Вы можете использовать вышеупомянутый RIP-относительный трюк исправления LEA + для создания самоизменяющегося кода, например inc byte [rax]
чтобы превратить 0xFE
в 0xFF
. Или dword sub
- сразу с 0x11111111
, или что-то может быть полезно для исправления rel32
call r/m64
и jmp r/m64
оба непригодны для непосредственного использования, поскольку сами коды операций FF /2
и FF /4
Если вы хотите вернуться, вероятно, проще всего исправить call rel32
или call rax
. Но было бы также возможно использовать REA-относительный LEA для вычисления адреса возврата в регистре и нажать его, затем jmp rel8
или jmp rax
или что-то еще.