Imported CodeЧто такое код импорта?Сегодня 5 лет с тех пор, как я придумал эту технику, и вот, наконец, я завершаю ее реализацию.. После написания виртуального кода я пытался найти иной способ заставить операционную систему построить для меня код. На этот раз, я использую таблицу импорта для получения всех значений. На это потребовалась определенная доля изобретательности. Как это работает?Обычно, таблица импорта получает реальные адреса, значения которых угадать невозможно, но я сделал свою таблицу импорта таким образом, чтобы она импортировала адреса, также экспортируемые моим процессом, так что значения мне достоверно известны. Затем я могу создать один экспорт для каждого уникального байта и импортировать все необходимые мне байты. Конечно, недостаточно импортировать байты, потому что размеры импортов - 32 бита, так что нам необходим способ доступа к байтовому значению каждого из них. Мы можем этого достичь, используя небольшой декодер и сделав из импортов выполняемый код, выглядит это вот так: mov esi, offset import_list mov edi, esi import_loop: call esi stos byte ptr [edi] lods dword ptr [esi] lods dword ptr [esi] cmp byte ptr [esi], bl db 75h ;f7 -> import_loop (f7 - первое импортированное значение) import_list: Тогда все импорты приобретут формат: b8 nn xx xx and xx c3 xx xx а nn - это как раз то значение байта, которое нам нужно. Impute.A и B поддерживают эту форму. Мы можем расставить импорты произвольно, чтобы сделать её полиморфной. Мы также можем использовать другие регистры вместо eax. mov esi, offset import_list mov edi, esi import_loop: call esi xchg reg, eax stos byte ptr [edi] lods dword ptr [esi] lods dword ptr [esi] cmp byte ptr [esi], bl db 75h ;f6 -> import_loop (f6 - первое импортированное значение) import_list: Тогда все импорты приобретут формат: bx nn xx xx and xx c3 xx xx кроме того, ecx, edx, ebp также доступны, а nn - значение байта, которое нам нужно. Impute.A и B также поддерживают эту форму. Мы можем поддержать ebx следующим образом: mov esi, offset import_list mov edi, esi import_loop: xchg ebx, eax call esi xchg ebx, eax stos byte ptr [edi] lods dword ptr [esi] lods dword ptr [esi] cmp byte ptr [esi], bl db 75h ;f5 -> import_loop (f5 - первое импортированное значение) import_list: Тогда все импорты приобретут формат: bb nn xx xx and xx c3 xx xx а nn - значение байта, которое нам нужно. Impute.A и B также поддерживают эту форму. Ещё один способ получить значения - использование ret nn. Тогда дельта между старым и новым стеками будет являться значением байта. mov esi, offset import_list mov edi, esi import_loop: mov eax, esp call esi sub esp, eax xchg esp, eax stos byte ptr [edi] lods dword ptr [esi] cmp byte ptr [esi], bl db 75h ;f3 -> import_loop (f3 - первое импортированное значение) import_list: Тогда все импорты приобретут формат: c2 nn xx xx а nn - значение байта, которое нам нужно. Impute.C поддерживает эту форму. Мы даже можем их комбинировать, следующим образом: mov esi, offset import_list mov edi, esi import_loop: mov ecx, esp call esi stos byte ptr [edi] xchg ecx, eax sub esp, eax xchg esp, eax stos byte ptr [edi] lods dword ptr [esi] lods dword ptr [esi] cmp byte ptr [esi], bl db 75h ;f0 -> import_loop (f0 - первое импортированное значение) import_list: Тогда все импорты приобретут формат: b8 n1 xx xx and xx c2 n2 xx and n1 and n2 - значения байта, которые нам нужны. Impute.D и E поддерживают эту форму. Конечно, вместо eax мы также можем использовать другие регистры. mov esi, offset import_list mov edi, esi import_loop: mov eax, esp call esi sub esp, eax xchg esp, eax stos byte ptr [edi] xchg reg, eax stos byte ptr [edi] lods dword ptr [esi] lods dword ptr [esi] cmp byte ptr [esi], bl db 75h ;ef -> import_loop (ef - первое импортированное значение) import_list: Тогда все импорты приобретут формат: bx n2 xx xx and xx c2 n1 xx также доступны ecx, edx, ebp, а n1 и n2 - значения байта, которые нам нужны, однако надо убедиться, что оба значения, предназначенные для хранения, записаны наоборот. Impute.D и E также поддерживают эту форму. Мы можем поддержать ebx следующим образом: mov esi, offset import_list mov edi, esi import_loop: xchg ebx, eax mov ecx, esp call esi xchg ebx, eax stos byte ptr [edi] xchg ecx, eax sub esp, eax xchg esp, eax stos byte ptr [edi] lods dword ptr [esi] lods dword ptr [esi] cmp byte ptr [esi], bl db 75h ;ee -> import_loop (ee - первое импортированное значение) import_list: Тогда все импорты приобретут формат: bb n1 xx xx and xx c2 n2 xx а n1 and n2 - значения байта, которые нам нужны. Impute.D и E также поддерживают эту форму. Мы можем поддержать ASLR, заменив "mov esi" на "call $+5 / pop esi". Если бы была 3-байтная инструкция, безопасная для исполнения в тот момент, когда мы контролируем первый байт, можно было бы поступить следующим образом: b8 nn xx xx and xx ‹inst› xx xx and aa ‹inst› xx xx [последовательность повторяется] Тогда мы могли бы построить целый код, используя только импорты, даже такие полиморфные регистры: bx nn xx xx and xx ‹inst› xx xx and 9x ‹inst› xx xx and aa ‹inst› xx xx [последовательность повторяется] К сожалению, в 32-битном коде такой инструкции нет, так что мы не сможем использовать таким образом полностью импортированный код. Однако, мы можем поступить следующим образом: b8 n5 xx xx and xx aa b8 n6 and xx xx xx aa [последовательность повторяется] Три импорта - "mov eax, n5", "stosb" и "mov eax, n6", а также "stosb". Мы повторяем последние три импорта для всех наших байтов, а затем можем полностью декодировать. Мы даже можем поступить с полиморфным регистром следующим образом: bx n5 xx xx and 9x aa bx n6 and xx xx xx 9x and aa bx n7 xx and xx xx xx aa [последовательность повторяется] Адрес буфера выглядит следующим образом: bf n1 n2 n3 and n4 90 90 90 Адрес буфера - оффсет последнего импорта, так что по окончании декодирования, мы немедленно запускаем его. Для этого необходимо чуть больше памяти, но нам также не нужны никакие другие импорты, кроме этих. Impute.F поддерживает форму b8. Import ForwardingЭто наша последняя проблема. Некоторые DLL'ы могут экспортировать неприменяемую функцию, пересылая ссылку на другой DLL. Обнаруживается она, если адрес импорта указывает внутрь таблицы экспорта. Так как мы хотим подержать выдачу любого значения, нам необходимо избавиться от обнаружения пересылки импорта. Для стационарных версий, это просто. Всё, что нам надо, это таблица экспорта с характеристиками, которые не пересекаются с нашим значением. Impute делает это при помощи значения imagebase, которое пересекает границу в 4Gb, когда добавляется размер таблицы экспорта. Тогда все наши значения либо меньше, чем imagebase, либо больше, чем конец таблицы экспорта. Для ASLR эта проблема не решена. Если есть вероятность того, что Windows загрузит файл на imagebase со значением 0x1xxx0000, тогда может произойти обнаружение, и файл перестанет загружаться, но я такого никогда не видел. Приветствия дружелюбным людям (A-Z):Active - Benny - herm1t - hh86 - jqwerty - Malum - Obleak - pr0mix - Prototype - Ratter - Ronin - RT Fishel - sars - SPTH - The Gingerbread Man - Ultras - uNdErX - Vallez - Vecna - Whitehead Подбронее об авторе и Исходники: sources/rgb/impute
|