Упаковщики исполняемых файлов в LINUX-BSD

       

дизассемблерный листинг


Что за чертовщина?! Каким образом расшифровщик может существовать в extern, когда здесь прямым текстом прописаны фактические адреса динамически загружаемых функций! Но тот факт, что файл все-таки работает, убеждает нас, что да — может! Просто IDA Pro в попытке "эмуляции" загрузки elf-файла пихает в extern всякую хрень, которой там нет. ### то, чего там в действительности нет

Здесь мы подходим к одной из самых любопытных особенной строения elf-файлов. В отличии от Windows, где заполнение extern'а происходит на стадии загрузки файла в память, в UNIX это делает стартовый код, причем делает он это очень хитрым способом. Ниже показан протокол трассировки программы под отладчиком с моими комментариями, отмеченными знаком ';' и содержимым дизассемблерного листинга IDA Pro, отмеченным знаком '#'. Как говориться — сравните и почувствуйте разницу!

Для облегчения понимая возьмем незашифрованную программу, необработанную ELFCrypt'ом:

(gdb) b _start                    ; устанавливаем точку останова на начало стартового кода

Breakpoint 1 at 0x80482c0: file ../sysdeps/i386/elf/start.S, line 47.

(gdb) r                           ; запускаем программу на выполнение

Breakpoint 1, _start () at ../sysdeps/i386/elf/start.S:47

(gdb) x 0x80495DC          ; ок, мы в точке входа. смотрим на extern

# extern:80495DC     7F 01 00 00   extern puts@@GLIBC_2_0:near

0x80495dc:           0x00000000

; IDA Pro нас уверяет, что extern содержит адрес 0000017Fh, но в действительности

; область extern на момент запуска файла девственно чиста и забита нулями

#.text:080482C0                   _start proc near



#.text:080482C0 31 ED                    xor    ebp, ebp

1: x/i $pc  0x80482c0 <_start>
:   xor    %ebp,%ebp

; // незначащие машинные инструкции пропущены

#.text:080482D7 68 90 83 04 08    push   offset main

1: x/i $pc  0x80482d7 <_start+23>
: push   $0x8048390

#.text:080482DC E8 CF FF FF FF    call   ___libc_start_main


1: x/i $pc  0x80482dc <_start+28>
: call   0x80482b0 <_init+56>

; но вот стартовый код вызывает библиотечную функцию ___libc_start_main,

; поскольку компилятор еще не знает ее фактического адреса,

; он вставляет переходник к секции .plt, содержащей переходники

; к секции .got, заполняемой динамическим загрузчиком

#.plt:080482B0       ___libc_start_main   proc near

#.plt:080482B0 FF 25 D0 95 04 08  jmp    ds:off_80495D0

1: x/i $pc  0x80482b0 <_init+56>
: jmp    *0x80495d0

; IDA Pro

корректно отобразила plt-переходник, вызывающий функцию,

; указатель на которую расположен в двойном слове по адресу 80495D0h

#.got:080495D0 E8 95 04 08 off_80495D0   dd offset __libc_start_main

1: x/i $pc  0x80482b6 <_init+62>
: push   $0x8

1: x/i $pc  0x80482bb <_init+67>
: jmp    0x8048290 <_init+24>

; а вот тут уже начались расхождения...

; IDA Pro

уверяет, что здесь расположено смещение функции __libc_start_main

; в то время как отладчик показывает, что здесь находится специальный код

; push 08h/jmp

8048290h. посмотрим, что покажет IDA Pro по адресу 8048290h

# .plt:08048290 ?? ?? ?? ?? ?? ?? dd 4 dup(?)

1: x/i $pc  0x8048290 <_init+24>
: pushl  0x80495c4

1: x/i $pc  0x8048296 <_init+30>
: jmp    *0x80495c8

; парад различий продолжается!!! IDA Pro

вообще не показывает ничего!!!

; отладчик же показывает код, засылающий в стек смещение первого (считая от нуля)

; элемента таблицы .got и передающего управление по адресу, записанного во втором

; элементе таблицы .got. как следует из спецификации elf-формата, первые три элемента

; секции .got зарезервированы для служебных целей и вторая из них хранит адрес функции

; _dl_map_object_deps, которая, получив в качестве аргумента адрес начала .got'а

; читает его содержимое (а содержатся там ссылки на библиотечные функции)

; и заполняет extern фактическими адресами

0x4000bbd0 in _dl_map_object_deps () from /lib/ld-linux.so.2

1: x/i $pc  0x4000bbd0 <_dl_map_object_deps+4384>
:     push %eax

; ага! вот эта функция, расположенная на моей машине по адресу 4000BBD0h,

; принадлежащему библиотеке libc.so.6 (на других машинах этот адрес может быть иным)

; она-то и выполняет всю работу по инициализации extern'а, в котором находится

; наш расшифровщик, уже расшифровавший программу, а затем вызывает __libc_start_main,

; так что загрузка динамической библиотеки происходит совершенно прозрачно


Содержание раздела