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
Или используйте относящийся к 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
См. что такое инструкция jmpl в x86? - это безумное несоответствие в том, как GAS обрабатывает синтаксис AT&T, применяется даже к jmpl
. Обычный jmp 0x400
при сборке в 16-битном режиме будет относительным скачком к этому абсолютному смещению.
В крайне маловероятном случае, если вам понадобится jmp rel16
в других режимах, вам придется собрать его самостоятельно с .byte
и .short
. Я не думаю, что есть способ заставить ассемблер выдать его за вас.
Сноска 2: вы не можете использовать jmp rel16
в 32/64-битном коде, если только вы не атакуете какой-либо код, отображенный в низких 64 КБ виртуального адресного пространства, например может что то работает под ДОСЭМУ или ВИН. Настройка Linux по умолчанию для /proc/sys/vm/mmap_min_addr
составляет 65536, а не 0, поэтому обычно ничто не может mmap
что память, даже если вы хотите, или предположительно загрузить ее текстовый сегмент по этому адресу через загрузчик программы ELF. (Таким образом, разыменование нулевого указателя со смещением segfault вместо тихого доступа к памяти).
Вы можете быть уверены, что ваша цель CTF не будет работать с EIP = IP, и что усечение EIP до IP будет просто ошибкой segfault.
person
Peter Cordes
schedule
15.05.2018
jmp rel16
доступен только в 16-битном коде. Если вы можете закодировать его в 32-битном режиме с префиксами, он усечет EIP до 16-битного IP. - person Peter Cordes   schedule 15.05.2018lock
и _2 _ / _ 3 _ / _ 4_ работают так же, как в 16-битном режиме. Префиксы переопределения сегмента тоже такие же, но используются только для локального хранилища потока (gs:
илиfs:
), поскольку в основных операционных системах используется плоская модель памяти для CS / DS / ES / SS. Как показывает ваша собственная ссылка на инструкциюjmp
, с 16-битным размером операнда этоEIP ← tempEIP AND 0000FFFFH;
, поэтому вы не можете использоватьjmp rel16
. - person Peter Cordes   schedule 15.05.2018jmp eax
), но единственный знакомый мне метод получения EIP - это выполнениеcall
и извлечение адреса из стека. Если относительное смещениеcall
не является отрицательным, этот также кодируется нулевыми байтами. Может, я смогу создать что-нибудь подобное. В любом случае, спасибо за урок сборки сегодня вечером. Я многому научился. - person sherrellbc   schedule 15.05.2018jmp
вперед, а затемcall
назад. Работаем над ответом, чтобы завершить этот беспорядок комментариев. Re: смещение: помните, что это знаковое расширениеrel8
, поэтому старшие байты полного смещения должны быть такими же, как старший бит младшего байта. Знак0x80
и выше расширяется доFFFFFFxy
вместо000000xy
- person Peter Cordes   schedule 15.05.2018jmp rel16
? Это вам не поможет, потому что EIP обрезается до IP. NASM может его кодировать, но да, если вы когда-нибудь найдете ему применение, вы можете вручную кодировать его в AT&T или GAS.intel_syntax
. - person Peter Cordes   schedule 15.05.2018