Найден адрес функции в DLL. Не имеют исходного кода для этой DLL, а не моего. Эта DLL не очень часто менялась, но когда она менялась, это проблема для меня, чтобы ее найти, разобрав. Видел несколько заметок в Интернете о том, как сделать подпись, а затем найти ее с помощью этой сохраненной подписи. Можете ли вы дать некоторые идеи или рабочий пример о том, как это реализовать?Найти функцию по его подписи в Windows DLL
ответ
Вы можете добиться этого путем сканирования подписи кода, что я и делал в прошлом. Концепция в основном работает, полагаясь на то, что функции часто не меняются слишком сильно между обновлениями, а просто перемещаются, потому что они были выдвинуты вперед или назад, когда другие функции расширяются или сокращаются.
Давайте возьмем пример MessageBoxA
, кто разборку выглядит это для меня:
765DEA11 > 8BFF MOV EDI,EDI
765DEA13 55 PUSH EBP
765DEA14 8BEC MOV EBP,ESP
765DEA16 833D 749A5E76 00 CMP DWORD PTR DS:[765E9A74],0
765DEA1D 74 24 JE SHORT USER32.765DEA43
765DEA1F 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
765DEA25 6A 00 PUSH 0
765DEA27 FF70 24 PUSH DWORD PTR DS:[EAX+24]
765DEA2A 68 A49E5E76 PUSH USER32.765E9EA4
765DEA2F FF15 34145876 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange
765DEA35 85C0 TEST EAX,EAX
765DEA37 75 0A JNZ SHORT USER32.765DEA43
765DEA39 C705 A09E5E76 01>MOV DWORD PTR DS:[765E9EA0],1
765DEA43 6A 00 PUSH 0
765DEA45 FF75 14 PUSH DWORD PTR SS:[EBP+14]
765DEA48 FF75 10 PUSH DWORD PTR SS:[EBP+10]
765DEA4B FF75 0C PUSH DWORD PTR SS:[EBP+C]
765DEA4E FF75 08 PUSH DWORD PTR SS:[EBP+8]
765DEA51 E8 73FFFFFF CALL USER32.MessageBoxExA
765DEA56 5D POP EBP
765DEA57 C2 1000 RETN 10
Хитрость заключается в том, чтобы угадать в какой-то блок кода, который вы думаете, вероятно, останется такой же в обновлении, но что более важно, является уникальным для этой функции. Как правило, бесполезно сканировать эпилог/пролог. Я бы, вероятно, взял следующий блок:
765DEA16 833D 749A5E76 00 CMP DWORD PTR DS:[765E9A74],0
765DEA1D 74 24 JE SHORT USER32.765DEA43
765DEA1F 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
765DEA25 6A 00 PUSH 0
765DEA27 FF70 24 PUSH DWORD PTR DS:[EAX+24]
765DEA2A 68 A49E5E76 PUSH USER32.765E9EA4
765DEA2F FF15 34145876 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>;
Вы должны сделать баланс при выборе длины блока. Чем длиннее блок, тем вероятнее однозначно идентифицировать функцию, но также более вероятно, что во время обновления будет вставлен некоторый код, что означает, что он разделен и т. Д. Обратите внимание, что выбранный мной блок имеет несколько модулей памяти Рекомендации. Мы не можем полагаться на любом данные или функциональные адреса, так как они могут быть перемещены на следующем обновлении, поэтому мы заполняем эти байты с групповыми символами:
765DEA16 833D XXXXXXXX 00 CMP DWORD PTR DS:[XXXXXXXX],0
765DEA1D 74 XX JE SHORT XXXXXXXX
765DEA1F 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
765DEA25 6A 00 PUSH 0
765DEA27 FF70 24 PUSH DWORD PTR DS:[EAX+24]
765DEA2A 68 XXXXXXXX PUSH XXXXXXXX
765DEA2F FF15 XXXXXXXX CALL DWORD PTR DS:[XXXXXXXX]
Это означает, что наши байты подпись теперь:
0x83 0x3D 0x? 0x? 0x? 0x? 0x74 0x? 0x64 0xA1 0x18 0x00 0x00 0x00 0x6A 0x00 0xFF 0x70 0x24 0x68 0x? 0x? 0x? 0x? 0xFF 0x15 0x? 0x? 0x? 0x?
Баны 0x?
указывают символы, которые мы ожидаем изменить. Остальные - байты, которые, как мы ожидаем, не изменятся в обновлении. Чтобы использовать байты для определения функции во время выполнения, вам необходимо отсканировать эти байты (с учетом подстановочных знаков). Процесс примерно так:
- Перечислите все исполняемые страницы процесса (
VirtualQueryEx
) - Scan для байта сигнатуры мы обнаружили (с учетом групповых символов - это просто реализовать как
for
цикл, который пропускает подстановочные байты) - Чтобы получить истинную функцию адрес, исправить адрес, который вы получаете с смещением блока от исходной функции (в данном случае,
0x765DEA16 - 0x765DEA11 => 0x5
)
на самом деле, а не enumerat во всех исполняемых страницах часто бывает достаточно найти, какой модуль находится внутри (user32.dll
) в этом случае, и выполнять поиск только в этом модуле.
Интересно, как мы можем назвать эту функцию из c. Назначить его указателю и вызвать его? Это работает? –
@alFReDNSH Если функция не принимает аргументов, да, вы можете это сделать, потому что нормально игнорировать различия в вызовах. В противном случае вы должны быть более осторожны, чтобы убедиться, что вы выбрали его соответствующим образом. –
Если он принимает значение int в качестве первого аргумента и возвращает int, имеет ли это следующее право? int lolz = 1; int (* foo) (int); foo = <адрес функции>; lolz = foo (lolz); –
Я не понимаю, как вы можете работать без файла заголовка или документации. –
Функция, которую я хочу использовать, не экспортируется из DLL. Это внутренняя функция DLL. –
Да, это тонкое сообщение о том, что вы не должны называть эту функцию. –