Может ли программа C изменить свой исполняемый файл?
У меня было слишком много времени, и я начал задаваться вопросом, Могу ли я написать самомодифицирующуюся программу. С этой целью я написал " Hello World "в C, а затем использовал шестнадцатеричный редактор, чтобы найти местоположение строки" Hello World " в скомпилированном исполняемом файле. Можно ли изменить эту программу, чтобы открыть себя и перезаписать строку "Hello World"?
char* str = "Hello Worldn";
int main(int argc, char* argv) {
printf(str);
FILE * file = fopen(argv, "r+");
fseek(file, 0x1000, SEEK_SET);
fputs("Goodbyewrldn", file);
fclose(file);
return 0;
}
Это не работает, я предполагаю, что что-то мешает ему открыть себя, так как я могу разделить это на два отдельные программы ("Hello World" и что-то, чтобы изменить его), и он работает нормально.
EDIT: я понимаю, что при запуске программы она полностью загружается в ОЗУ. Таким образом, исполняемый файл на жестком диске является, по сути, копией. Почему для него было бы проблемой изменить себя?
есть ли обходной путь?
спасибо
9 ответов
в Windows, когда программа запускается полностью и соотнесенные в память с помощью функции сопоставленных с памятью файлов в Windows. Это означает, что файл не обязательно загружается все сразу, но вместо этого страницы файла загружаются по требованию при доступе к ним.
когда файл отображается таким образом, другое приложение (включая себя) не может записать в тот же файл, чтобы изменить его во время работы. (Кроме того, на Windows работает исполняемый файл также не может быть переименован, но он может быть в Linux и других системах Unix с файловыми системами на основе индексов).
можно изменить биты, отображаемые в память, но если вы это сделаете, ОС сделает это с помощью семантики "копировать на запись", что означает, что базовый файл не изменяется на диске, но копия страницы(страниц) в памяти производится с вашими изменениями. Однако, прежде чем разрешить это, вам обычно приходится возиться с битами защиты в рассматриваемой памяти (например, VirtualProtect
).
в свое время это было распространено для низкоуровневых сборочных программ, которые были в очень ограниченных средах памяти, чтобы использовать самоизменяющийся код. Однако никто больше не делает этого, потому что мы не работаем в тех же ограниченных средах, и современные процессоры имеют длинные конвейеры, которые очень расстраиваются, если вы начинаете изменять код из-под них.
если вы используете Windows, вы можете сделать следующее:
шаг за шагом пример:
- вызов
VirtualProtect()
на кодовых страницах, которые вы хотите изменить, с помощьюPAGE_WRITECOPY
защита. - изменить код страницы.
- вызов
VirtualProtect()
на измененных кодовых страницах, с помощьюPAGE_EXECUTE
защита. - вызов
FlushInstructionCache()
.
дополнительные сведения см. В разделе Как изменить исполняемый код в памяти (архив: Август. 2010)
Это очень зависит от конкретной операционной системы. Некоторые операционные системы блокируют файл, поэтому вы можете попытаться обмануть, сделав новую копию его где-нибудь, но вы просто запускаете еще один компи программы.
другие операционные системы проверяют безопасность файла, например iPhone, поэтому написание его будет большой работой, плюс он находится как файл только для чтения.
с другими системами вы можете даже не знать, где расположен файл.
все присутствующие ответы более или менее вращаются вокруг того факта, что сегодня вы не можете легко сделать самомодифицирующийся машинный код больше. Я согласен, что это в основном верно для сегодняшних ПК.
, Если вы действительно хотите увидеть собственный самомодифицирующийся код в действии, у вас есть некоторые возможности:попробуйте микроконтроллеры, более простые из них не имеют расширенной конвейеризации. Самый дешевый и быстрый выбор, который я нашел-это и MSP430 USB-Stick
Если эмуляция подходит для вас, вы можете запустить эмулятор для более старой платформы без конвейера.
Если вы хотели самомодифицирующийся код только для удовольствия от него, вы можете иметь еще больше удовольствия с саморазрушающимся кодом (точнее, уничтожающим врага) в Corewars.
Если вы готовы перейти от C, чтобы сказать Lisp диалект, код, который пишет код очень естественно там. Я бы предложите схемы который намеренно держится маленьким.
Если мы говорим об этом в среде x86, это не должно быть невозможно. Его следует использовать с осторожностью, потому что инструкции x86 имеют переменную длину. Длинная инструкция может перезаписать следующую инструкцию(инструкции), а более короткая оставит остаточные данные из перезаписанной инструкции, которая должна быть noped (инструкция NOP).
когда x86 впервые стал защищенным, справочные руководства intel рекомендовали следующий метод отладки доступа к XO (выполнить только) области:
- создайте новый пустой селектор ("высокая" часть указателей far)
- установите его атрибуты в область XO
- свойства доступа нового селектора должны быть установлены данные RO, если вы только хотите посмотреть, что в нем
- если вы хотите изменить данные, свойства доступа должны быть установлены в RW DATA
таким образом, ответ на проблему находится на последнем шаге. RW необходим, если вы хотите иметь возможность вставьте инструкцию точки останова, что и делают отладчики. Более современные процессоры, чем 80286, имеют внутренние отладочные регистры для включения функций неинтрузивного мониторинга, которые могут привести к выдаче точки останова.
Windows сделала доступными строительные блоки для этого, начиная с Win16. Вероятно, они все еще на месте. Я думаю, что Microsoft называет этот класс манипуляции указателем " thunking."
однажды я написал очень быструю 16-битную базу данных двигатель в PL / M-86 для DOS. Когда Windows 3.1 прибыл (работает на 80386s), я портировал его в среду Win16. Я хотел использовать 32-разрядную память, но не было PL / M-32 (или Win32, если на то пошло).
для решения проблемы моя программа использовала thunking следующим образом
- определены 32-битные указатели far (sel_16: offs_32) с использованием структур
- выделенные 32-разрядные области данных (>размер 64KB) с использованием глобальной памяти и получили их в 16-битный дальний указатель (sel_16:offs_16) формат
- заполните данные в структурах, скопировав селектор, затем вычислите смещение с помощью 16-битного умножения с 32-битными результатами.
- загрузил указатель / структуру в es: ebx, используя префикс переопределения размера инструкции
- доступ к данным с помощью комбинации префиксов размера инструкции и размера операнда
Как только механизм был свободен от ошибок, он работал без сучка и задоринки. Наибольший зоны памяти моей программе были 2304*2304 двойной точности, которая выходит около 40МБ. Даже сегодня я бы назвал это "большим" блоком памяти. В 1995 году это было 30% от типичной SDRAM-карты (128 MB PC100).
есть непереносимые способы сделать это на многих платформах. В Windows вы можете сделать это с помощью WriteProcessMemory()
, например. Однако в 2010 году это, как правило, очень плохая идея. Это не те дни DOS, когда вы кодируете в сборке и делаете это для экономии места. Это очень трудно сделать правильно, и вы в основном просите проблемы стабильности и безопасности. Если вы не делаете что-то очень низкоуровневое, как отладчик, я бы сказал, Не беспокойтесь об этом, проблемы, которые вы введете, не являются стоит того, что ты можешь получить.
Самомодифицирующийся код используется для изменений в памяти, а не в файле (например, распаковщики во время выполнения, как UPX). Кроме того, представление файла программы сложнее работать из-за относительных виртуальных адресов, возможных перемещений и изменений заголовков, необходимых для большинства обновлений (например. путем изменения Hello world!
до longer Hello World
вам нужно будет расширить сегмент данных в файле).
Я предлагаю вам сначала научиться делать это в памяти. Для обновления файлов самый простой и более общий подход будет запускать копию программы, чтобы она изменила оригинал.
EDIT: и не забывайте об основных причинах, по которым используется самоизменяющийся код:
1) запутывание, так что код, который фактически выполняется, не является кодом, который вы увидите с помощью простого статического анализа файла.
2) производительность, что-то вроде JIT.
никто из них не выигрывает от изменения исполняемого файла.
Если вы работаете в Windows, я считаю, что он блокирует файл, чтобы предотвратить его изменение во время его запуска. Вот почему вам часто нужно выйти из программы, чтобы установить обновление. То же самое не верно для системы linux.
в более новых версиях Windows CE (по крайней мере 5.x более новый), где приложения работают в пользовательском пространстве (по сравнению с более ранними версиями, где все приложения работали в режиме супервизора), приложения не могут даже прочитать собственный исполняемый файл.