Я действительно надеюсь, что об этом раньше не спрашивали, но мой поиск в Google ничего не дал, поэтому я решил спросить.
У меня есть небольшой сценарий, над которым я работаю, где я внедряю DLL в игру и редактирую определенную строку, которая отображается, когда происходит определенное событие. Я нашел, где эта строка находится в памяти, и написал базовую функцию сканирования, чтобы найти ее во время выполнения. Мой вопрос сейчас заключается в том, что, учитывая начальный адрес, как / можно заменить эту строку конкретной строкой?
Скажем, например, строка «Я люблю блины», и я хотел заменить ее на «Блины ужасны, вафли лучше!». Как можно это сделать?
Спасибо!
Ах да, если это имеет значение, строка в источнике — это константный символ. Я уверен, что это не так, но не мешало бы добавить эту информацию!
Как вы заметили, инструкции по сборке, ссылающиеся на строку, обычно выглядят так:
push offset aString
После сборки и связывания это решается на фактический адрес, скажем:
push 0x00ABCDEF
Это дает вам два варианта:
aString
(т.е. память, на которую указывает 0x00ABCDEF
)aString
Когда исходный код компилируется с использованием стандартных строковых литералов Си (неизменяемый массив символов в памяти), во время выполнения строка обычно отображается на какую-то страницу только для чтения со всеми другими данными только для чтения. Эти данные обычно упаковываются непрерывно, чтобы уменьшить объем памяти программы. Это проблема, которую вы решаете, пытаясь написать большую строку. Вы перезапишете следующий фрагмент данных, и любые ссылки на эти перезаписанные данные теперь будут указывать на середину вашей большой строки.
Написание более длинной строки путем изменения данных нетривиально, потому что, чтобы не потерять исходное функциональное поведение, вы должны сдвинуть все данные после вашей строки вперед. После этого вы должны обновить все ссылки на сдвинутые данные (некоторые из которых могут быть рассчитаны динамически с помощью арифметики указателей). Как я уже сказал, этот процесс нетривиален — вы пытаетесь воспроизвести задачу компоновщика в терминах перемещения без полной (если таковая имеется) символической информации.
Самый простой выход — написать новую строку в произвольном месте. Это может быть неиспользуемая, но уже зарезервированная память в процессе (обычно называемая «пещерами кода»), или это может быть строковый литерал, который вы отображаете, когда вводите свою DLL. В качестве альтернативы, вы можете выделить это динамически во время выполнения после внедрения.
Следующий шаг — найти все ссылки на aString
и замените их ссылками на новую строку.
Поскольку вы изучаете реверс-инжиниринг на этом уровне, скорее всего, вы столкнулись с концепцией обходных путей / перехватов / измерительных приборов. Подобный подход может быть применен здесь для перехвата всех ссылок и перенаправления их во время выполнения. Это приведет к более сильному снижению производительности, чем описанный выше метод «Write Code», но гарантирует, что все обращения будут перехвачены и перенаправлены.
Аппаратная точка останова при доступе устанавливается на данные, на которые указывает строка. Когда точка останова срабатывает, какой-то регистр будет содержать адрес строки. В сборке это может выглядеть примерно так:
mov esi, 0x00ABCDEF
...
Если к первому символу обращаются, код может сделать это:
mov al, byte ptr ds:[esi]
Когда ваша точка останова достигнута, вы можете установить контекст потока (SetThreadContext
в Windows), чтобы изменить значение esi
чтобы указать на вашу новую строку.
Если строка, которую вы хотите заменить, имеет длину не менее тривиальной: замените ее на месте. В противном случае вы не сможете достоверно заявить о своей ненависти к блинам и любви к вафлям.