Технически возможно ли это, зависит от нескольких вещей:
jne
/ je
инструкции бывают разной длины:
- one-байтовый код операции (
0x74
/ 0x75
) плюс 8-битное смещение со знаком (т.е. всего два байта) - двухбайтовый код операции (
0xf0
плюс 0x84
/ 0x85
,) плюс подпись32-разрядное смещение (т.е. всего шесть байтов)
то же самое для инструкции jmp
(в 64-битном режиме, всего пять различных кодировок между двумя и девятью байтами, в зависимости от того, является ли jmp
является относительным / абсолютным).
Это означает, что вы можете заменить jne
/ je
на jmp
, только если цель вашего jmp
"достаточно близка", чтобы выбрать код операции, совместимый с размером.Для 32-битного относительного jne
/ je
вы обычно можете заменить, но для почти (8-битного), который обычно не возможен, так как не будет «свободного места для инструкций» в пределах +/- 128 байт.
, если у вас есть «совместимая возможность» в отношении.в соответствии с размером инструкции у вас есть две задачи для решения, о которых вы уже упоминали:
- перехватывает загрузку разделяемого объекта, чтобы вы могли заменить его до использования библиотеки.
- поиск адреса функции, которую вы хотите исправить (при условии, что она изменится).
Как это можно сделать, зависит от вашей операционной системы;в UN * X-like вы можете попробовать использовать LD_PRELOAD
для загрузки пользовательской библиотеки вашей собственной , которая не делает ничего, кроме dlopen()
библиотеки, которую вы хотите изменить, затем dlsym()
, чтобы найти подпрограмму, затем подставьте код, но никогда не вызывайте dlclose()
, чтобы убедиться, что измененная копия хранится (то есть не выгружается) вместо оригинала, когда позже (через динамическое связывание) доступ к библиотеке снова.
Системный администратор обычно может смягчить (или даже отключить) LD_PRELOAD
(и для исполняемых файлов setuid, запускаемых от имени root, он всегда игнорируется), поскольку он имеет очевидные последствия для целостности / безопасности системы.То, может ли эта техника использоваться или нет, зависит от конкретной установки.
Если все вышеперечисленные условия выполнены, то что-то вроде:
int patch_je_jne(void *instr, void *tgt_address)
{
char *je_jne = (char*)instr;
char *iaddr = je_jne;
switch (je_jne[0]) {
case JE_8BIT_OP:
case JNE_8BIT_OP:
iaddr += 2;
tgt_address -= iaddr; /* adjust pc-relative */
if ((char*)tgt_address > (char*)0xFF)
return CANNOT_PATCH_LARGE_OFFSETS;
je_jne[1] = (char)tgt_address;
je_jne[0] = JMP_OP;
return PATCH_SUCCESS;
case 0xF0: /* marker for a 32bit-relative JE/JNE */
if (je_jne[1] != JE_32BIT_OP && je_jne[1] != JNE_32BIT_OP))
break;
iaddr += 6;
tgt_address -= iaddr; /* adjust pc-relative */
*((char**)(je_jne + 2)) = tgt_address;
je_jne[1] = JMP_32BIT_OP;
return PATCH_SUCCESS;
}
return CANNOT_PATCH_THIS_INSTR;
}
может сделать это.Как уже говорилось, в этом коде нет проверки / подтверждения, кроме проверки совместимости кодов команд по адресу, который должен быть исправлен.
Основная работа, как уже упоминалось, заключается в , надежно определяющем где / как вам нужно применить ловушку.
Как уже было предложено, комментарий по исправлению бинарной библиотеки вместо работающей (загруженной) библиотеки: Ключевой вопрос здесь: Какова цель jmp
, вы можете знать это заранее ?
В двоичном исправлении необходимо определить это во время изменения двоичного файла, который вместе с поведением операционной системы, например ASLR (макет адресного пространстварандомизация) трудно или невозможно - вам нужен заранее известный неизменяемый целевой адрес в вашем исполняемом файле, но обычно у вас есть только относительный jmp
, т. е. заранее неизвестно, какое расположение кода это 'в конечном итоге из-за ASLR.Все зависит от , где вы хотите, чтобы jmp
пошел.