Как инициализируется память перед начальной точкой входа программы?

#c #assembly #memory #binary #elf

Вопрос:

Я работаю над проектом бинарного анализа. У меня есть файл elf в качестве входных данных, который я разбираю через objdump. Моя цель состоит в том, чтобы выполнить динамическое символьное выполнение с использованием z3 каждой инструкции в ассемблерном коде, следуя заданному графику потока управления. Для имитации эффектов каждой инструкции. Я реализовал свою собственную модель памяти и регистров в виде объектов карты C (MEM и REG соответственно). Для простоты давайте предположим, что данные внутри регистров и памяти хранятся в десятичном формате(не в реальном случае), и давайте пока рассмотрим операции z3 как обычные математические операции.

Объявляю свою память и регистрирую модели:

 map<string,int> MEM; //string address as key, int data as value
map<string,int> REG; //string register name as key, int data as value
 

например, инструкция по хранению str r3, [fp, #-8] в коде будет выглядеть следующим образом MEM[REG[fp]-8]=REG[r3]

и инструкция по добавлению add fp, sp, #4 в коде будет выглядеть так REG[fp]=REG[sp] 4

Я моделирую влияние каждой инструкции на свою модель памяти и регистрирую модель таким же образом для всех инструкций в ассемблерном коде.

когда я запускаю свой инструмент. Я начинаю с инициализации модели памяти с использованием данных в файле сборки. Ниже приведен ассемблерный код функции _ZSt9terminatev программы, которую я анализирую:

 0000bf6c <_ZSt9terminatev>:

bf6c:   b508        push    {r3, lr}
bf6e:   4b03        ldr r3, [pc, #12]   ; (bf7c <_ZSt9terminatev 0x10>)
bf70:   6818        ldr r0, [r3, #0]
bf72:   f3bf 8f5b   dmb ish
bf76:   f7ff ffe5   bl  bf44 <_ZN10__cxxabiv111__terminateEPFvvE>
bf7a:   bf00        nop
bf7c:   00021bc0    .word   0x00021bc0
 

Например, последняя строка функции bf7c: 00021bc0 .word 0x00021bc0 _ZSt9terminatev реализована в моем коде как MEM["0000bf7c"]= 138176 \138176 is decimal conversion of 0x21bc0 just for simplicity . Я делаю то же самое для всех данных в файле сборки, чтобы заполнить свою модель памяти перед запуском (должен ли я делать то же самое со всеми другими строками, такими как даже инструкции кода, такие как инструкция nop по адресу 0000bf7a(т. Е. MEM[«0000bf7a»]=десятичное число(«0000bf00»))? .

Я думал, что этого достаточно, чтобы инициализировать мою модель памяти, однако, когда я начинаю выполнять первую инструкцию в точке входа моей программы, я столкнулся с проблемой «то, что я называю ключевой ошибкой» (ниже приведен фрагмент первых 3 строк функции точки входа моей программы).:

 000080ec <_mainCRTStartup>:
80ec:   4b13        ldr r3, [pc, #76]   ; (813c <_mainCRTStartup 0x50>)
80ee:   2b00        cmp r3, #0
80f0:   bf08        it  eq
 

похоже, что первая инструкция ldr r3, [pc, #76] (т. е. REG[r3]=MEM[REG[pc] 76]) загружается из неинициализированной области памяти.

итак, наконец, к моему вопросу, как я могу правильно инициализировать свою модель памяти, прежде чем перейти к точке входа в мою программу?. Я понимаю роль загрузчика компоновщика(например, /lib/ld-linux.so), который загружает библиотеки, от которых зависит мой elf, но как я могу реализовать это в коде, учитывая, что у меня есть только файл elf программы для начала. Возможно, в моем файле elf есть раздел, показывающий, какие данные поступают на какие адреса памяти, прежде чем перейти к точке входа программы?

Комментарии:

1. Заголовок программы ELF сообщает ядру, какие диапазоны виртуальной памяти сопоставляются с какими частями файла, или заполняется нулями, если файл не поддерживается. (Или, если memsiz > filesiz для сопоставления, более поздняя часть обнуляется, например .bss , раздел>, смежный с .data одним сегментом компоновщика.) Используйте readelf -a для просмотра сегментов (и сопоставления разделов->сегментов, если вам интересно, но обратите внимание, что загрузчик программ ELF ядра >заботится только о сегментах программы в заголовке программы.) Вы, вероятно, захотите использовать libbfd или какую-либо другую библиотеку для анализа эльфов.