Отладка PHP кода защищен­ного упа­ков­щиком SourceGuardian

hacking tools logo

Как видишь, интер­пре­татор инс­трук­ций мож­но заг­рузить в отладчик и трас­сировать шитый код с его помощью. В нашем слу­чае он встро­ен в Apache HTTP Server, находя­щий­ся в модуле httpd.exe. Нель­зя прос­то так взять и запус­тить его из отладчи­ка. Более того, к его про­цес­су нель­зя даже при­соеди­нить­ся пря­мым спо­собом — он работа­ет в фоновом режиме и скрыт в спис­ке активных про­цес­сов для при­соеди­нения x64dbg. Но есть малень­кая хит­рость: если зай­ти в парамет­ры x64dbg и в самой даль­ней, обыч­но скры­той вклад­ке «Про­чее» выб­рать режим «Уста­новить x64dbg опе­ратив­ным отладчи­ком (JIT)», то мож­но отла­живать даже скры­тые про­цес­сы. Для это­го в окне дис­петче­ра задач жмем пра­вой кноп­кой мыши на Apache HTTP Server и выбира­ем «Отладка».

Итак, мы влез­ли в самое ядро вир­туаль­ной машины Zend. Во вклад­ке «Отла­доч­ные сим­волы» в спис­ке заг­ружен­ных биб­лиотек мы видим модуль php7ts.dll, в котором рас­полага­ется эта машина, и модуль ixed.7.2ts.win, реали­зующий ее рас­ширение защиты SourceGuardian. Мес­то внед­рения защиты выг­лядит так:

00007FFCBBE35B20 | jne ixed.7.2ts.7FFCBBE35B92             |
00007FFCBBE35B22 | cmp qword ptr ss:[rsp+1C0],0            |
00007FFCBBE35B2B | je ixed.7.2ts.7FFCBBE35B92              |
00007FFCBBE35B2D | call qword ptr ds:[<&zend_get_executed_ |
00007FFCBBE35B33 | mov rcx,qword ptr ss:[rsp+1C0]          |
00007FFCBBE35B3B | mov qword ptr ds:[rcx+10],rax           |
00007FFCBBE35B3F | call qword ptr ds:[<&tsrm_get_ls_cache> |
00007FFCBBE35B45 | mov ecx,dword ptr ds:[7FFCBBE4C95C]     |
00007FFCBBE35B4B | dec ecx                                 |
00007FFCBBE35B4D | movsxd rcx,ecx                          |
00007FFCBBE35B50 | mov rax,qword ptr ds:[rax]              |
00007FFCBBE35B53 | mov rax,qword ptr ds:[rax+rcx*8]        |
00007FFCBBE35B57 | mov dword ptr ds:[rax+18],1             |
00007FFCBBE35B5E | mov rdx,qword ptr ss:[rsp+13E0]         |
00007FFCBBE35B66 | mov rcx,qword ptr ss:[rsp+1C0]          |
00007FFCBBE35B6E | call qword ptr ds:[<&zend_execute>]     |
00007FFCBBE35B74 | mov rcx,qword ptr ss:[rsp+1C0]          |
00007FFCBBE35B7C | call qword ptr ds:[<&destroy_op_array>] |
00007FFCBBE35B82 | mov rcx,qword ptr ss:[rsp+1C0]          |
00007FFCBBE35B8A | call qword ptr ds:[<&_efree@@8>]        |
00007FFCBBE35B90 | jmp ixed.7.2ts.7FFCBBE35BA7             |
00007FFCBBE35B92 | mov rax,qword ptr ss:[rsp+13E0]         |

Здесь рас­шифро­ван­ный и рас­пакован­ный код из стро­ки‑аргу­мен­та уже забот­ливо пре­обра­зован в пос­ледова­тель­ность инс­трук­ций и подан на вход интер­пре­тато­ру, реали­зован­ному фун­кци­ей zend_execute.

Для удобс­тва нач­нем с того, что сос­тавим спи­сок команд нашей вир­туаль­ной машины. В интерне­те гуг­лится мно­го раз­ных вари­антов, силь­но и не очень отли­чающих­ся друг от дру­га, но нам нуж­на имен­но наша, кон­крет­но ском­пилиро­ван­ная для нас вер­сия. Для это­го мы откры­ваем дизас­сем­блер IDA и при помощи него ищем в биб­лиоте­ке php7ts.dll фун­кцию zend_get_opcode_name. Фун­кция сов­сем прос­тая — по опко­ду Zend-инс­трук­ции воз­вра­щает имя. Реали­зация ее тоже пре­дель­но прос­та, она все­го‑нав­сего берет имя из сле­дующе­го мас­сива строк (не буду при­водить весь спи­сок пол­ностью, каж­дый может лег­ко сге­нери­ровать его самос­тоятель­но):

.rdata:00000001806F2160 off_1806F2160   dq offset aZendNop      ; DATA XREF: zend_get_opcode_name+3^o
.rdata:00000001806F2160                                         ; "ZEND_NOP"
.rdata:00000001806F2168                 dq offset aZendAdd      ; "ZEND_ADD"
.rdata:00000001806F2170                 dq offset aZendSub      ; "ZEND_SUB"
.rdata:00000001806F2178                 dq offset aZendMul      ; "ZEND_MUL"
.rdata:00000001806F2180                 dq offset aZendDiv      ; "ZEND_DIV"
.rdata:00000001806F2188                 dq offset aZendMod      ; "ZEND_MOD"
.rdata:00000001806F2190                 dq offset aZendSl       ; "ZEND_SL"
.rdata:00000001806F2198                 dq offset aZendSr       ; "ZEND_SR"
.rdata:00000001806F21A0                 dq offset aZendConcat   ; "ZEND_CONCAT"
.rdata:00000001806F21A8                 dq offset aZendBwOr     ; "ZEND_BW_OR"
.rdata:00000001806F21B0                 dq offset aZendBwAnd    ; "ZEND_BW_AND"
.rdata:00000001806F21B8                 dq offset aZendBwXor    ; "ZEND_BW_XOR"
.rdata:00000001806F21C0                 dq offset aZendBwNot    ; "ZEND_BW_NOT"
.rdata:00000001806F21C8                 dq offset aZendBoolNot  ; "ZEND_BOOL_NOT"

Этот спи­сок отли­чает­ся от нагуг­ленных вари­антов, зато теперь мы име­ем пред­став­ление, какая из команд исполня­ется в дан­ный момент вре­мени. Что­бы ты не рас­слаб­лялся, добав­лю лож­ку дег­тя: некото­рые обфуска­торы могут под­менять исполни­тель­ные адре­са хэн­дле­ров команд собс­твен­ными, поэто­му при рас­шифров­ке коман­ды все вре­мя надо дер­жать ухо вос­тро, не полага­ясь пол­ностью на опкод. Как минимум нуж­но сле­дить за тем, что­бы адрес хэн­дле­ра попадал в адресное прос­транс­тво php7ts.dll.

Итак, спи­сок команд у нас есть, мож­но прис­тупить к отладке. Что­бы не возить­ся с обвязкой интер­пре­тато­ра, ста­вим точ­ку оста­нова пря­мо в основной цикл интер­пре­тато­ра шитого кода внут­ри фун­кции execute_ex:

00007FFCB6236280 | 48:895C24 08             | mov qword ptr ss:[rsp+8],rbx            | execute_ex
00007FFCB6236285 | 57                       | push rdi                                |
00007FFCB6236286 | 48:83EC 20               | sub rsp,20                              |
00007FFCB623628A | 6548:8B0425 58000000     | mov rax,qword ptr gs:[58]               |
00007FFCB6236293 | 48:8BD9                  | mov rbx,rcx                             |
00007FFCB6236296 | 8B15 A4758200            | mov edx,dword ptr ds:[7FFCB6A5D840]     |
00007FFCB623629C | B9 18000000              | mov ecx,18                              |
00007FFCB62362A1 | 48:8B3CD0                | mov rdi,qword ptr ds:[rax+rdx*8]        |
00007FFCB62362A5 | 8B05 2DAE8200            | mov eax,dword ptr ds:[

Как вид­но из это­го фраг­мента, в дан­ной реали­зации интер­пре­татор устро­ен пре­дель­но прос­то. На вхо­де в RDI у нас ука­затель на струк­туру сле­дующе­го вида:

struct _zend_execute_data {
// Текущая инструкция
const zend_op *opline;
// кадр стека текущей функции
zend_execute_data *call;
// Возвращаемые данные
zval *return_value;
zend_function *func;
zval This; /* this + call_info + num_args */
// Вызываемый кадр стека текущей функции
zend_execute_data *prev_execute_data;
// таблица символов
zend_array *symbol_table;
#if ZEND_EX_USE_RUN_TIME_CACHE
void **run_time_cache;
#endif
#if ZEND_EX_USE_LITERALS
 // постоянный массив констант
 zval *literals;
#endif
};

В этой струк­туре нам инте­рес­ны счет­чик команд opline и ука­затель на мас­сив кон­стант literals. Во вре­мя цик­ла исполне­ния счет­чик команд заг­ружен в регистр rbx, из него в регистр rax извле­кает­ся хэн­длер текущей инс­трук­ции, по которо­му и дела­ется вызов. При этом в коман­ду в регис­тре rcx в качес­тве парамет­ра переда­ется ука­затель на струк­туру _zend_execute_data.

Есть еще один полез­ный малодо­кумен­тирован­ный момент. В струк­туре zend_execute_data есть параметр func (в моем слу­чае по отно­ситель­ному сме­щению 0x18). На самом деле, он ука­зыва­ет на порож­дающую струк­туру

zend_op_array (струк­тура может менять­ся в зависи­мос­ти от вер­сии PHP).
struct _zend_op_array {
  zend_uchar type;
  zend_uchar arg_flags[3];
  uint32_t fn_flags;
  zend_string *function_name; /* Имя функции */
  zend_class_entry *scope;
  zend_function *prototype;
  uint32_t num_args;
  uint32_t required_num_args;
  zend_arg_info *arg_info;
  HashTable *attributes;
  int cache_size;
  int last_var;       /* количество компилируемых переменных */
  uint32_t T;         /* количество временных переменных */
  uint32_t last;      /* количество опкодов */
  zend_op *opcodes;   /* указатель на наш массив zend_op */
  ZEND_MAP_PTR_DEF(void **, run_time_cache);
  ZEND_MAP_PTR_DEF(HashTable *, static_variables_ptr);
  HashTable *static_variables;
  zend_string **vars; /* names of CV variables */
  uint32_t *refcount;
  int last_live_range;
  int last_try_catch;
  zend_live_range *live_range;
  zend_try_catch_element *try_catch_array;
  zend_string *filename; /* Полный путь с именем файла активного скрипта */
  uint32_t line_start;  /* Номер первой строки */
  uint32_t line_end;    /* Номер последней строки */
  zend_string *doc_comment;
  int last_literal; /* количество констант */
  uint32_t num_dynamic_func_defs;
  zval *literals;  ./* массив констант */
  zend_op_array **dynamic_func_defs;
  void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

Порождающая структураПо­рож­дающая струк­тура

Как видишь, струк­тура содер­жит мно­го полез­ной информа­ции, к при­меру, c ее помощью мож­но ори­енти­ровать­ся, какой модуль исполня­ется в текущий момент. Но про­дол­жим про­цесс изу­чения нашего веб‑при­ложе­ния. Рас­смот­рим фраг­мент шитого кода по ука­зате­лю opline.
Фрагмент шитого кода по указателю oplineФраг­мент шитого кода по ука­зате­лю opline

Для понима­ния его струк­туры сно­ва покурим докумен­тацию, из которой сле­дует, что это мас­сив ском­пилиро­ван­ных инс­трук­ций вир­туаль­ной машины, каж­дая из которых опи­сыва­ется сле­дующей конс­трук­цией:

struct _zend_op {
const void * handler; // указатель на функцию выполнения текущей инструкции
znode_op op1; // Операнд 1
znode_op op2; // Операнд 2
znode_op result; // Возвращаемое значение
uint32_t extended_value; // расширенный
uint32_t lineno; // номер строки
zend_uchar opcode; // Тип инструкции
zend_uchar op1_type; // тип операнда 1 (этот тип не представляет типы данных, такие как строки и массивы; он указывает, что этот операнд является константой, временной переменной, скомпилированной переменной и т. д.)
zend_uchar op2_type; // тип операнда 2
zend_uchar result_type; // тип возвращаемого значения
};

Каж­дая запись занима­ет 0x20 байт, поэто­му на рисун­ке раз­биение на инс­трук­ции выг­лядит дос­таточ­но наг­лядно. Зеленым под­чер­кнут хэн­длер инс­трук­ции, то есть абсо­лют­ный 64-бит­ный адрес исполня­емо­го кода обра­бот­чика, крас­ным обве­дены опко­ды, а сбо­ку при­веде­на их рас­шифров­ка исхо­дя из выше­опи­сан­ной таб­лицы. Синим отме­чены дей­ству­ющие опе­ран­ды инс­трук­ции и их типы:

// константа
#define IS_CONST (1<<0)
// Временные переменные виртуальной машины, не объявленные в коде PHP. Используются, например, для возврата значений из функции $a = time().
// Данный тип временной переменной отличается от IS_VAR фактически только тем, что операнд данного типа может содержать ссылки
#define IS_TMP_VAR (1<<1)
//
#define IS_VAR (1<<2)
// операнд, который либо фактически не используется, либо используется как 32-битное числовое значение (так называемый непосредственный операнд). Например, инструкция перехода будет хранить адрес перехода в операнде UNUSED.
#define IS_UNUSED (1<<3) /* Unused */
// Компилируемые переменные, то есть переменные, объявленные в PHP;
#define IS_CV (1<<4) /* Compiled variable */

Су­дя по име­ни модуля и номерам строк, это откры­тый незашиф­рован­ный код, пред­шес­тву­ющий sg_load. Поп­робу­ем пот­рениро­вать­ся на нем в вос­ста­нов­лении логики прог­раммы, пос­коль­ку у нас есть воз­можность срав­нить его с исходным нес­компи­лиро­ван­ным тек­стом скрип­та. С име­нами инс­трук­ций мы уже разоб­рались, поп­робу­ем разоб­рать­ся с парамет­рами. Пер­вая инс­трук­ция ZEND_INIT_FCALL, вто­рой опе­ранд у нее — кон­стан­та (тип IS_CONST=1) име­ет зна­чение 0, суть его — сме­щение в таб­лице литера­лов, на которую ука­зыва­ет literals.
Таблица литераловТаб­лица литера­лов

Эле­мен­тами этой таб­лицы явля­ются зна­чения типа zval. Это базовый тип вир­туаль­ной машины Zend, и для опи­сания его струк­туры пот­ребу­ется отдель­ная статья. По счастью, подоб­ные статьи уже есть на Хаб­ре, и жела­ющие могут их нагуг­лить. Мы же огра­ничим­ся общим опи­сани­ем струк­туры при­мени­тель­но к нашему слу­чаю PHP7:

struct _zval_struct {
    zend_value value;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar type,
                zend_uchar type_flags,
                zend_uchar const_flags,
                zend_uchar reserved)
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t var_flags;
        uint32_t next;                 // hash collision chain
        uint32_t cache_slot;           // literal cache slot
        uint32_t lineno;               // line number (for ast nodes)
        uint32_t num_args;             // arguments number for EX(This)
        uint32_t fe_pos;               // foreach position
        uint32_t fe_iter_idx;          // foreach iterator index
    } u2;
};

Выг­лядит этот код страш­новато, но реаль­но струк­тура име­ет три поля: собс­твен­но value (оно в PHP7 64-бит­ное), 32-бит­ное type_info, пред­став­ляющее собой тип зна­чения, и третье 32-бит­ное поле широко­го при­мене­ния, которое в дан­ный момент нас не инте­ресу­ет.
Ти­пы зна­чений в нашем слу­чае быва­ют сле­дующи­ми:

// обычные типы данных
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10
// константные выражения
#define IS_CONSTANT                 11
#define IS_CONSTANT_AST             12
// внутренние типы
#define IS_INDIRECT                 15
#define IS_PTR                      17

В нашей таб­лице литера­лов содер­жатся в основном стро­ковые кон­стан­ты (IS_STRING=6) и длин­ные целочис­ленные (IS_LONG=4). Иско­мая кон­стан­та, на которую ука­зыва­ет наш опе­ранд, — стро­ковая, а зна­чит, 64-бит­ное value интер­пре­тиру­ется как ука­затель на zend_string. Обе­щаю, что это пос­ледняя внут­ренняя струк­тура, опи­сыва­емая в сегод­няшней статье. Если ты обра­тил вни­мание, она уже упо­мина­лась выше: это базовая струк­тура для хра­нения строк в дан­ной вир­туаль­ной машине — в час­тнос­ти, она слу­жит для хра­нения пол­ного име­ни скрип­та и исполня­емой фун­кции. Итак, ткнем­ся в отладчи­ке в эту ссыл­ку.
Продолжаем отладкуПро­дол­жаем отладку

На­конец‑то появи­лась какая‑то наг­лядность — вмес­то сле­пых цифр вид­ны осмыслен­ные стро­ки. Для пущего понима­ния сно­ва смот­рим опи­сание струк­туры:

struct _zend_string {
    zend_refcounted   gc;
    zend_ulong        h;        /* hash value */
    size_t            len;
    char              val[1];
};

Струк­тура пре­дель­но прос­тая: пер­вые 8 байт ее занима­ет объ­ект refcounted, его вве­ли недав­но для сис­темно­го сбор­щика мусора и для нас он инте­реса не пред­став­ляет. Так же, как и сле­дующие 8 байт — 64-бит­ный хеш. А вот даль­ше идет собс­твен­но стро­ка с 64-бит­ным счет­чиком: в нашем слу­чае это function_exists.

С кон­стан­тами разоб­рались, но как быть с перемен­ными? А с перемен­ными дело обсто­ит весь­ма сурово. Дело в том, что все типы перемен­ных, как ком­пилиро­ван­ных, так и вре­мен­ных, хра­нят­ся вo фрей­ме сте­ка пря­мо сле­дом за струк­турой zend_execute:

+----------------------------------------+
| zend_execute_data                      |
+----------------------------------------+
| VAR[0]                =         ARG[1] | arguments
| ...                                    |
| VAR[num_args-1]       =         ARG[N] |
| VAR[num_args]         =   CV[num_args] | remaining CVs
| ...                                    |
| VAR[last_var-1]       = CV[last_var-1] |
| VAR[last_var]         =         TMP[0] | TMP/VARs
| ...                                    |
| VAR[last_var+T-1]     =         TMP[T] |
| ARG[N+1] (extra_args)                  | extra arguments
| ...                                    |
+----------------------------------------+

Спер­ва адре­суют­ся аргу­мен­ты фун­кции, затем ком­пилиру­емые перемен­ные, затем вре­мен­ные. Лег­ко уви­деть, что опе­ран­ды, соот­ветс­тву­ющие перемен­ным в инс­трук­ции, — суть сме­щения отно­ситель­но фрей­ма сте­ка. Беда зак­люча­ется в том, что в этой области, условно раз­мечен­ной под 16-бай­товые струк­туры zval, нет ука­заний, какой имен­но перемен­ной она при­над­лежит. При­мер­ное пред­став­ление об этом мож­но получить, вспом­нив, что в порож­дающей струк­туре zend_op_array есть поле vars (отме­чено на рисун­ке), которое пред­став­ляет собой ука­затель на мас­сив ука­зате­лей имен перемен­ных.

Ра­зуме­ется, это каса­ется толь­ко ком­пилиро­ван­ных перемен­ных CV (тип 0x10), ибо у вре­мен­ных перемен­ных типов 2 и 4 никаких имен нет. Соот­ветс­твен­но, эмпи­ричес­кий спо­соб получе­ния име­ни перемен­ной из опе­ран­да инс­трук­ции выг­лядит так: берем сме­щение (в слу­чае при­веден­ной на рисун­ке инс­трук­ции ZEND_ASSIGN пер­вый параметр 0x50), отни­маем от него раз­мер струк­туры zend_execute_data (в нашем слу­чае тоже 0x50), резуль­тат делим на раз­мер струк­туры zval (0x10). Получен­ное чис­ло исполь­зуем как индекс в таб­лице имен vars — перей­дя по соот­ветс­тву­ющей ссыл­ке, получа­ем имя нулевой ком­пилиру­емой перемен­ной «__v». Cоот­ветс­твен­но, опе­ранд 0x60 сле­дующей коман­ды ZEND_ASSIGN будет перемен­ной с индексом 1 в этой таб­лице и наз­вани­ем «__x» и так далее.

Итак, при­сово­купив к инс­трук­циям их парамет­ры, получим сле­дующую пос­ледова­тель­ность псев­докода:

ZEND_INIT_FCALL "function_exists"
ZEND_SEND_VAL "sg_load"
ZEND_DO_ICALL ~0
ZEND_BOOL_NOT ~0,!1
ZEND_JMPZ !1,->2C80
ZEND_INIT_FCALL "phpversion"
ZEND_DO_ICALL ~2
ZEND_ASSIGN $__v,~2
ZEND_INIT_FCALL "explode"
ZEND_SEND_VAL "."
ZEND_SEND_VAR $__v
ZEND_DO_ICALL ~4
ZEND_ASSIGN $__x,~4
ZEND_FETCH_DIM_R $__x,0,~6
ZEND_CONCAT ~6,".","~7"
...

Смот­рим на то, как это выраже­ние выг­лядит в исходном скрип­те:

<?php if(!function_exists('sg_load')){$__v=phpversion();$__x=explode('.',$__v);$__v2=-$__x[0].'.'...

Бин­го! Путем титани­чес­ких уси­лий мы поч­ти вос­ста­нови­ли логику малень­кой час­ти уже извес­тной нам стро­ки кода. Одна­ко мы это сде­лали руками без вся­ких дам­перов, средс­тва­ми отладчи­ка x64dbg, попут­но получив пред­став­ление о фун­кци­они­рова­нии вир­туаль­ной машины PHP пря­мо внут­ри нее.

Ка­ковы наши даль­нейшие дей­ствия? Разуме­ется, пошаго­во тащить­ся по извес­тно­му нам коду доволь­но скуч­но, поэто­му мы вре­мен­но бло­киру­ем точ­ку оста­нова на execute_ex и уста­нав­лива­ем ее непос­редс­твен­но перед вызовом рас­шифро­ван­ного кода внут­ри модуля ixed.7.ts2.win. Как толь­ко оста­нов­ка нас­тупит, бло­киру­ем этот брек­поинт и сно­ва вклю­чаем точ­ку оста­нова внут­ри вир­туаль­ной машины. Теперь мы находим­ся в самом начале рас­шифро­ван­ного фраг­мента и видим все его инс­трук­ции, кон­стан­ты, перемен­ные. Мож­но сдам­пить это все на диск и написать пар­сер, мож­но мед­ленно, но вер­но тащить­ся пошаго­во до про­вер­ки нуж­ного нам усло­вия, все зависит от тво­ей усид­чивос­ти и кон­крет­ной задачи.

Поп­робую дать совет по опти­миза­ции даль­нейше­го про­цес­са. Метод прос­той и уни­вер­саль­ный для всех вир­туаль­ных машин — по сути, нас инте­ресу­ют толь­ко узло­вые момен­ты вет­вле­ния алго­рит­ма. Cта­вим точ­ки оста­нова имен­но в этих мес­тах, то есть на хэн­дле­рах инс­трук­ций условных перехо­дов ZEND_JMPZ, ZEND_JMPNZ, ZEND_JMPZNZ, ZEND_JMPZ_EX, ZEND_JMPNZ_EX, ZEND_CASE. Реаль­но оста­нав­ливать­ся в этих точ­ках не обя­затель­но, дос­таточ­но писать про­хож­дение дан­ной точ­ки в лог. Вдо­бавок неп­лохо бы писать в лог наз­вания вызыва­емых фун­кций, бла­го инс­трук­ций с уста­нов­кой их имен нем­ного: ZEND_INIT_FCALL_BY_NAME, ZEND_INIT_FCALL, ZEND_INIT_NS_FCALL_BY_NAME, ZEND_INIT_METHOD_CALL, ZEND_INIT_STATIC_METHOD_CALL, ZEND_INIT_USER_CALL, ZEND_INIT_DYNAMIC_CALL.

По­лучен­ные тре­ки мож­но ана­лизи­ровать на пред­мет мест для пат­ча на лету. Пос­коль­ку вир­туаль­ная машина не ком­пилиру­ет код, а исполня­ет его пошаго­во, пат­чер мож­но вешать непос­редс­твен­но на вход в execute_ex. А воз­можно, эта статья натол­кнет тебя на написа­ние сво­его собс­твен­ного уни­вер­саль­ного дам­пера или даже пря­мого анпа­кера закоди­рован­ных PHP? Ведь выше­опи­сан­ный спо­соб годит­ся для рас­паков­ки и отладки скрип­тов, защищен­ных не толь­ко SourceGuardian, но и дру­гими ана­логич­ными сис­темами защиты.

По­доб­ных сис­тем великое мно­жес­тво: сре­ди них — AROHA PHPencoder, BCompiler, ByteRun Protector for PHP, ByteScrambler, CNCrypto, CodeCanyon PHP Encoder, CodeLock, CodeTangler… и это лишь малая часть спис­ка. При­чем для боль­шинс­тва из них, нап­ример для того же SourceGuardian или небезыз­вес­тно­го ionCube, не сущес­тву­ет пуб­личных декоде­ров — лишь плат­ные онлайн‑сер­висы. Пос­мотрев на прайс подоб­ных сер­висов, мож­но убе­дить­ся в том, нас­коль­ко зна­ния матери­аль­ны.

ВКонтакте
OK
Telegram
WhatsApp
Viber

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *