Filter::Decrypt: розшифровка наосліп
У Perl передбачений механізм фільтрів, через які пропускаюся вихідні файли перед тим, як вони будуть оброблені інтерпретатором. Одним із таких фільтрів є Decrypt, який розшифровує заздалегідь зашифрований файл. Таким чином Perl можна реалізувати приховування вихідних текстів. За замовчуванням застосовується звичайний XOR, однак розробник має можливість підключити будь-який алгоритм. Сам модуль, включаючи алгоритм шифрування, як правило, статично лінується з інтерпретатором, що трохи ускладнює розшифровку. У статті 1 буде показано як можна отримати вихідний текст із зашифрованого файлу незалежно від того, чим спочатку цей файл зашифрований. Про алгоритм шифрування нам не потрібно знати нічого, це може бути найстійкіший криптографічний алгоритм з нескінченною довжиною ключа. Ось приклад такого файлу (частина рядків вирізана):
>>>>>> Fingerprint: C1B9-A752-E2DF-C5AA-9C09-531D-7507-BE40-374D-36D4 n1KxoeHEBwZFQHiWGOiMVHeLb9E1m9BVF1QH2w/UsaPM2 jRSb/kx/lvZvUMt7pj2azLUbTm5hUHlwxwoVBRj2NnqbTA69mLikfrwuxq ma8TdqOu5oewwJBIexm05yfsmb1+Lrva60NBkCBWda5Aetr005baT> KYNVtmXuvUYhH/9tL9ZR6QkYmc0kn2r1Gc034mRQOHP+6xXw8nvetRshlC9AMc9J Bauu6o0CEyJCafDaUe3Ojbk=
Що це за алгоритм - неважливо, хоча видно, що зверху він накритий base64. Отже, беремо інтерпретатор і копіюємо його в домашню директорію. У моєму випадку це mod_perl, що помітно полегшить розшифровку, оскільки динамічний об'єкт можна відкрити через dlopen або лінкуватися з ним. Від інтерпретатора нам потрібна функція filter_decrypt, яка розшифровує. Ця callback-функція реєструється разом з фільтром при ініціалізації perl і потім викликається для кожного рядка файлу:
$ nm ./libperl.so grep filter_decrypt 00050100 t filter_decrypt
$ objdump -d ./libperl.so less . 0004fd60 . 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
$ readelf -S ./libperl.so awk '/\.got\.plt/' 0006c204
$ 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
. 0003e10: b082 0100 3802 0000 1200 0a00 341f 0000 . 8. 4. 0003e20:1017 0200f701 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 0500f701 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 #include
// Визначаємо символи заглушки, щоб не лаявся 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 > char *lib, *fname;
if (argc != 3) ERR("Usage: ./prog "); 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);
// Виводимо результат: 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 $ exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PL_DIR $./go./libperl.so File_to_decrypt.pm
Отримуємо розшифрований файл на stdout. Це набагато ефективніше, ніж намагатися здампувати файл, що виконується з пам'яті, оскільки дозволяє розшифрувати цілий масив файлів незалежно від того, знаходяться вони в пам'яті чи ні.