В Perl предусмотрен механизм фильтров через которые пропускаюся исходные
файлы перед тем, как они будут обработаны интерпретатором. Одним из таких
фильтров является Decrypt, который расшифровывает предварительно зашифрованный
файл. Таким образом в Perl можно реализовать скрытие исходных текстов.
По-умолчанию применяется обычный XOR, однако разработчик имеет возможность
подключить любой алгоритм. Сам модуль, включая алгоритм шифрования, как правило,
статически линкуется с интерпретатором, что немного усложняет расшифровку. В
статье будет показано как можно получить исходный текст из зашифрованного файла
независимо от того, чем изначально этот файл зашифрован. Об алгоритме шифрования
нам не нужно знать ничего, это может быть самый стойкий криптографический
алгоритм с бесконечной длиной ключа. Вот пример такого файла (часть строк
вырезана):
use Filter::decrypt;
>>>>>>
Fingerprint: C1B9-A752-E2DF-C5AA-9C09-531D-7507-BE40-374D-36D4
n1KxoeHEBwZFQHiWGOiMVHeLb9E1m9BVF1QH2w/UsaPM2cmg2Z7gPi7kKy30eMmu
X6iGP0jRSb/kx/lvZvUMt7pj2azLUbTm5hUHlwxwoVBRj2NnqbTA69mLikfrwuxq
ma8TdqOu5oewwJBIexm05yfsmb1+Lrva60NBkCBWda5Aetr005baT6gHJSXTgGox
...
KYNVtmXuvUYhH/9tL9ZR6QkYmc0kn2r1Gc034mRQOHP+6xXw8nvetRshlC9AMc9J
Bauu6o0CEyJCafDaUe3Ojbk=
Что это за алгоритм – неважно, хотя видно, что сверху он накрыт base64. Итак,
берем интерпретатор и копируем его в домашниюю директорию. В моем случае это
mod_perl, что заметно облегчит расшифровку, поскольку динамический объект можно
открыть через dlopen или линковаться с ним. От интерпретатора нам нужна функция
filter_decrypt, которая производит расшифровку. Эта callback-функция
регистрируется вместе с фильтром при инициализации perl и затем вызывается для
каждой строки файла:
$ nm ./libperl.so | grep filter_decrypt
00050100 t filter_decrypt
Можно "добывать" эту функцию через dlopen, а можно просто слинковать
программу вместе с этой библиотекой. Но есть проблема. Чтобы dlopen (или
программа) могла увидеть функцию, она должна находиться в dynamic-секции
открываемой библиотеки. А в нашем случае функция даже не является глобальной.
Если бы достаточно было глобализовать функцию, то можно было бы воспользоваться
objcopy --globalize-symbol=filter_decrypt и на этом успокоиться. А вот
для того, чтобы засунуть символ в dynamic-секцию у objcopy аргументов нет.
Поступить можно так: взять любой ненужный символ в dynamic-секции и подменить
его адрес на адрес функции filter_decrypt.
Что касается адреса функции, то его мы можем получить с помощью указанной
выше команды nm. Но что делать, если из библиотеки удалены символы (т.е. она
стрипнута)? Определить адрес filter_decrypt в общем случае становится достаточно
трудно. Для этого дизассемблируем библиотеку в районе вызова функции
XS_Filter__decrypt_import:
$ objdump -d ./libperl.so | less
...
0004fd60 <XS_Filter__decrypt_import>
...
4fe57: 8d 93 fc 3e fe ff lea 0xfffe3efc(%ebx),%edx
4fe5d: 57 push %edi
4fe5e: 52 push %edx
4fe5f: 50 push %eax
4fe60: e8 8f ad fb ff call abf4 <Perl_filter_add@plt>
...
В ней вызывается Perl_filter_add для добавления нашего фильтра. Эти функции
мы увидим даже в стрипнутой библиотеке. На i386 адрес filter_decrypt передается
в стеке. В данном случае через регистр edx. В ebx находится адрес Global Offset
Table, найти который можно так:
$ readelf -S ./libperl.so | awk '/\.got\.plt/{print $4}'
0006c204
Поскольку размер edx == 32 бита, то адрес filter_decrypt равен (u32) 0x6c204
+ 0xfffe3efc = 0x50100. Теперь, зная адрес filter_decrypt, нужно подставить его
вместо адреса любого символа из dynamic-секции. Я выбрал символ XS_Apache_last.
Теперь необходимо рассчитать смещение в файле по которому расположен адрес этого
символа. Символ расположен в секции .dynsym, узнаем его номер в этой секции и
смещение самой секции:
$ readelf -S ./libperl.so | grep '.dynsym'
[ 2] .dynsym DYNSYM 00000dac 000dac 003550 10 A 3 12 4
$ readelf -s ./libperl.so | grep XS_Apache_last
775: 00021710 503 FUNC GLOBAL DEFAULT 10 XS_Apache_last
Смещение секции равно 0xdac, номер XS_Apache_last равен 775. Так же
необходимо знать размер структуры символа в секции .dynsym. На твоей архитектуре
это значение равно sizeof(Elf32_Sym). На i386 это значение равно 16. Вычисляем
смещение:
offset = 0xdac + 775 * 16 + 4 = 0xdac + 0x307 * 0x10 + 0x4 = 0x3e20
Последняя цифра 4 – это смещение по которому находится адрес в самой symbol
entry. К слову, по смещению 0 находится имя символа (точнее индекс имени в
строковой таблице). Итак, открываем файл библиотеки в hex редакторе, находим
смещение 0x3e20 и вставляем туда адрес символа filter_decrypt, не забывая о
порядке следования байтов. Для этой цели я использовал vim + xxd.
Было:
...
0003e10: b082 0100 3802 0000 1200 0a00 341f 0000 ....8.......4...
0003e20: 1017 0200 f701 0000 1200 0a00 972d 0000 .............-..
0003e30: 8047 0400 ca04 0000 1200 0a00 dc37 0000 .G...........7..
...
Стало:
...
0003e10: b082 0100 3802 0000 1200 0a00 341f 0000 ....8.......4...
0003e20: 0001 0500 f701 0000 1200 0a00 972d 0000 .............-..
0003e30: 8047 0400 ca04 0000 1200 0a00 dc37 0000 .G...........7..
...
Библиотека готова. Можно приступать к написанию программы раскодирования.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "perlapi.h"
#include <stdio.h>
#include <dlfcn.h>
// Определяем символы заглушки, чтобы не ругался dlopen
int core_module;
void top_module(void) {}
void ap_table_merge(void) {}
void ap_null_cleanup(void) {}
void ap_table_add(void) {}
void ap_server_argv0(void) {}
#define ERR(msg) err(msg,__LINE__)
void err(char *msg,int line) {
fprintf(stderr,"Error: %s
[%d]\n",msg,line);
exit(1);
}
#define BS 480000
int main(int argc,char *argv[]) {
void *h;
I32 (*f)(pTHX_ int,SV *, int);
SV *sv,*my_sv;
PerlInterpreter *my_perl;
int idx=0,rv,pdsv=0;
char *lib,*fname;
if (argc != 3) ERR("Usage: ./prog
<lib> <fname>");
lib = argv[1];
fname = argv[2];
// Открываем нашу модифицированную библиотеку
h = dlopen(lib,RTLD_LAZY);
if (!h) ERR("open lib");
// На самом деле получаем filter_decrypt
f = dlsym(h,"XS_Apache_last");
if (!f) ERR("Symbol enrty");
// Инициализируем внутренние структуры и переменные Perl'а
my_perl = perl_alloc();
if (!my_perl) ERR("perl struct alloc");
perl_construct(my_perl);
PL_rsfp = PerlIO_open(fname,PERL_SCRIPT_MODE);
if (!PL_rsfp) ERR("perlIO");
PL_rsfp_filters = newAV();
sv = newSV(BS);
// Добавляем наш фильтр
Perl_filter_add(((PerlInterpreter
*)pthread_getspecific((*Perl_Gthr_key_ptr(((void *)0))))), f,Nullsv);
// Инициализируем буферы для входного и выходного файла
IoLINES_LEFT(sv) = TRUE ;
my_sv = FILTER_DATA(idx);
IoTOP_GV(my_sv) = (GV*) newSV(BS) ;
IoLINES_LEFT(my_sv) = TRUE;
// В цикле для каждой строки зашифрованного файла вызываем функцию
фильтра
while(1) {
pdsv = ((XPV*) (sv)->sv_any)->xpv_cur;
rv = ((I32 (*)(pTHX_ int,SV *, int))IoANY(my_sv))(my_perl,idx,sv,48);
if (pdsv && (pdsv == ((XPV*) (sv)->sv_any)->xpv_cur)) break;
}
// Выводим результат:
printf("%s\n",((XPVIO *)sv->sv_any)->xpv_pv);
return;
}
Собираем и запускаем программу:
$ PL_DIR=/usr/lib/perl5/5.8.8/i386-linux-thread-multi/CORE
$ gcc -rdynamic -ldl go.c -lperl -L$PL_DIR -I$PL_DIR -o go
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PL_DIR
$ ./go ./libperl.so File_to_decrypt.pm
Получаем расшифрованный файл на stdout. Это гораздо эффективнее, чем пытаться
сдампить выполняющийся файл из памяти, поскольку позволяет расшифровать целый
массив файлов в независимости от того, находятся они в памяти или нет.
|