Встраивание кода и опасность пиратского ПО

    О том, как можно встроить код без jmp в секцию кода и остаться незаметным, если не изучать досконально дизассемблированный код. Кому интересно, прошу под кат.

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

    Для начала я делал дизассемблер и мой код сейчас выглядит так, то есть считывается байт и передаётся нужной функции.

    void disasm_intel ( unsigned char *ptr, int size, int byte_order, int show ) { 
    	show_asm = show;
    	virt = global_virt_text;
    	unsigned char *start = ptr;
    	start_op = ptr;
    	for ( int index = 0; index < size; index++ ) {
    		if ( show_asm == TRUE ) printf ( "%lx: ", virt );
    		switch ( *ptr ) {
    			case 0x30: intel_opcode_1_0x30 ( &ptr, &index, byte_order ); break;
    			case 0x31: intel_opcode_1_0x31 ( &ptr, &index, byte_order ); break;
    			case 0x66: intel_opcode_1_0x66 ( &ptr, &index, byte_order ); break;
    			case 0x67: intel_opcode_1_0x67 ( &ptr, &index, byte_order ); break;
    			case 0x83: intel_opcode_1_0x83 ( &ptr, &index, byte_order ); break;
    			case 0x88: intel_opcode_1_0x88 ( &ptr, &index, byte_order ); break; // mov register to register byte
    			case 0x89: intel_opcode_1_0x89 ( &ptr, &index, byte_order ); break;
    			case 0x8a: intel_opcode_1_0x8a ( &ptr, &index, byte_order ); break;
    			case 0x8b: intel_opcode_1_0x8b ( &ptr, &index, byte_order ); break; // mov esp, %x : mov ebp, %x
    			case 0x8d: intel_opcode_1_0x8d ( &ptr, &index, byte_order ); break; // lea
    			case 0xb0: intel_opcode_1_0xb0 ( &ptr, &index ); break;    // mov al, %x
    			case 0xb1: intel_opcode_1_0xb1 ( &ptr, &index ); break;    // mov cl, %x
    			case 0xb2: intel_opcode_1_0xb2 ( &ptr, &index ); break;    // mov dl, %x
    			case 0xb3: intel_opcode_1_0xb3 ( &ptr, &index ); break;    // mov bl, %x
    			case 0xb4: intel_opcode_1_0xb4 ( &ptr, &index ); break;    // mov ah, %x
    			case 0xb5: intel_opcode_1_0xb5 ( &ptr, &index ); break;    // mov ch, %x
    			case 0xb6: intel_opcode_1_0xb6 ( &ptr, &index ); break;    // mov dh, %x
    			case 0xb7: intel_opcode_1_0xb7 ( &ptr, &index ); break;    // mov bh, %x
    			case 0xb8: intel_opcode_1_0xb8 ( &ptr, &index, byte_order ); break; // mov eax, %x
    			case 0xb9: intel_opcode_1_0xb9 ( &ptr, &index, byte_order ); break; // mov ecx, %x
    			case 0xba: intel_opcode_1_0xba ( &ptr, &index, byte_order ); break; // mov edx, %x
    			case 0xbb: intel_opcode_1_0xbb ( &ptr, &index, byte_order ); break; // mov ebx, %x
    			case 0xbe: intel_opcode_1_0xbe ( &ptr, &index, byte_order ); break; // mov esi, %x
    			case 0xbf: intel_opcode_1_0xbf ( &ptr, &index, byte_order ); break; // mov edi, %x
    			case 0xc3: intel_opcode_1_0xc3 ( ); break;   // ret
    			case 0xcd: intel_opcode_1_0xcd ( &ptr, &index ); break;   // int 0x%x
    		}
    		ptr++;
    		virt += ptr - start;
    		start = ptr;
    		start_op = ptr;
    	}
    	show_asm = FALSE;
    }
    

    И таких функций уже большая куча. В некоторых местах я сделал комментарии, чтобы уловить взаимосвязь машинных команд и может быть в потом сделать более грамотный дизасемблер. Но в этом виде, в котором у меня сейчас код, я легко могу установить для каждого оператора сколько угодно условий.

    И вот пока я делал это у меня появилась идея, возможно ли добавлять код в середину секции кода? Оказывается можно, но во всех ли случаях? Пока что чтобы добавить код я использую уже подготовленные машинные коды. Если потом смогу, то сделаю транслятор ассемблера в машинных код, чтобы добавлять код было удобней. В моём случае надо указать смещение в секции кода и байты скопируются в нужное место. Также была проблема определённая: адресация в памяти. Я добавил в команде lea код, который сохраняет в структуру нужные данные, и если ты вставляешь новые операторы в секцию кода, то все смещения выстраиваются так, что указывают на данные в новых смещениях. Ну это не очень сложно, если вставил код, то секция кода увеличилась на столько же байт и все остальные секции после секции кода будут содержать уже новые смещения. Сделал так, чтобы были различия в том, куда ты вставляешь код, все смещения работают правильно. Потом появилась проблема в том что в адресации такой

    mov eax, [eax + eax + 0x100]

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

    И ведь таким способом могут воспользоваться злоумышленники. Ведь вредоносный код могут вставить в начало какой нибудь функции в программе, например чтобы создался fork дочерний и скачался специальный файл. В линуксе это можно сделать вроде без проблем. Ведь в /usr/include есть файл со всеми системными функциями операционной системы. То есть можно использовать сетевую часть, даже если в программе нет сетевых функций. Не знаю как в windows, но я буду пробывать потом добавлять работу с pe форматом. Может получиться сделать тоже что и в линукс. Пока что у меня консольная версия. Но потом планирую делать на gtk.

    Спасибо что потратили своё время на мою статью.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 7

      +1
      Я занимаюсь разработкой дизассемблера на си для линукс.

      Вряд ли у вас получится что-то лучше, чем уже имеющиеся инструменты (тот же objdump). Но как упражнение — наверное, неплохо.

      Также была проблема определённая: адресация в памяти. Я добавил в команде lea код, который сохраняет в структуру нужные данные, и если ты вставляешь новые операторы в секцию кода, то все смещения выстраиваются так, что указывают на данные в новых смещениях.

      Вот примерно в этом месте я перестал понимать, о чем речь.
      Что за структура? Что за «нужные данные»? Что за смещения указывают на данные на новых смещениях?

      Проблема со вставкой машинного кода в середину секции .text — в том, что поплывут все смещения после вставки. А смещения могут использоваться очень много где. Например, если в первой половине секции есть код, вызывающий код из второй половины секции (call xxxxx), то придется увеличить оффсет в команде call на размер добавленного кода (call xxxxx+size(addedCode)). Аналогично — для вызовов в обратном направлении (только там придется уменьшать оффсет). А еще есть такая чудесная таблица GOT (global offsets table), где лежат адреса всякие… И еще таблицы для обработки исключений (секции .eh_frame и .eh_frame_header). И много еще чего есть в файле ELF. Да, и не забываем про 64-битный режим — там тоже много чего веселого наворочано.
      Ну и да, всяческие смещения секций придется пересчитать.

      В общем, гораздо проще найти в файле кусок неиспользуемого кода, заменить его своим и встроить куда-нибудь call на него.
        0
        Ну чтобы заменить байтами код это да, проще, но я хочу понять весь elf и весь дизассемблер. Уж очень классное приложение может получиться если реализовать всё. Ну да, я вставляю код в нужный адрес и всё пересчитывается. Потом можно проверить тем же objdump, что файл получился правильным.
          +1
          Уж очень классное приложение может получиться если реализовать всё.

          А что конкретно-то может получиться такого классного? Дизассемблер — он и в Африке дизассемблер, у него область применения довольно узкая.
          Вот если вы что-то типа IDA решите наваять — тогда да, уже интереснее. Чтобы сигнатуры стандартных функций распознавал и рантаймов различных языков, и чтоб можно было интерактивненько в процесс дизассемблинга вносить корректировки…

          Потом можно проверить тем же objdump, что файл получился правильным.

          Кхе-кхе… Я вот сейчас отдизассемблил objdump'ом бинарь, который только что собрал — 557 мегабайт получился текстовичок. Много ли удастся проверить по этому листингу? Тут к гадалке не ходи, глазами проверить все адреса не получится.
          А если билд еще и без отладочной информации, да с агрессивной оптимизацией — там вообще черт ногу сломит.
            0
            Ну моя программа например работает так. Если хочешь получить все смещения и выполнить определённую работу, то передаешь функции FALSE. Если хочешь просто вывести дизассемблированный код, то передаёшь TRUE. Но FALSE я передаю только в том случае, когда надо добавить новый ассемблерный код куда нибудь между кодом, тогда вызывается функция с FALSE, которая заполняет структуру с данными и сразу же после этой функции обрабатывает.
        +5
        У вас какая-то беда с терминологией. Пиратское ПО и вредоносное — это как бы не синонимы совсем…

        Ну а вообще в дизассемблере поведение функции разгребания кода обычно описывают с помощью таблицы а не с помощью лавины из case.
          0
          Поищи в инете исходники Mistfall PE EXE/DLL reversing&infecting engine for Win32 за авторством Z0MBiE.

          Там, правда, не ELF, а PE, но именно в том виде, как ты описываешь — берем файл, разбираем его на секции, дизассемблируем код и попутно выстраиваем огромную таблицу «смещений-которые-могут-измениться», а потом, после инжекта своего кода, пересчитываем заново все эти смещения и собираем файл обратно.
            0
            классно.

          Only users with full accounts can post comments. Log in, please.