jmp rel16
кодируется только с размером операнда 16, который усекает EIP до 16 бит . (Для кодирования требуется префикс размера операнда 66
в 32- и 64-битном режиме). Как описано в ссылке на набор инструкций, которую вы связали, или в этом более современном PDF-> HTML-преобразовании руководства Intel , jmp
делает EIP ← tempEIP AND 0000FFFFH;
, когда размер операнда равен 16. Вот почему ассемблеры никогда не используют его, если вы не запросите его вручную 1 , и почему вы не можете использовать jmp rel16
в 32- или 64-битном коде , за исключением очень необычного случая, когда цель отображается в низком 64 КБ виртуального адресного пространства 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 относительно какого-либо значения регистра , вы можете получить текущее значение 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
Или используйте REA-относительный 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
Сноска 2: Вы не можете использовать jmp rel16
в 32/64-битном коде , если только вы не атакуете какой-то код, отображаемый в 64-килобайтном виртуальном адресном пространстве, например, возможно, что-то работающее подДОСЕМУ или ВИНО.По умолчанию в Linux для /proc/sys/vm/mmap_min_addr
установлено значение 65536, а не 0, поэтому обычно ничто не может mmap
памяти, даже если вы хотите, или предположительно загружать свой текстовый сегмент по этому адресу через загрузчик программ ELF.(Таким образом, NULL-указатель разыменовывается со смещением по умолчанию вместо тихого доступа к памяти).Вы можете быть уверены, что ваша цель CTF не будет работать с EIP = IP, а усечение EIP до IP будет просто segfault.