~ neuro disassembler ~

     Как-то  раз  понадобился  нам  дизассемблер длин опкодов под x86. Но чужие
  статические  таблицы  длин  опкодов  юзать  не хотелось, поэтому в результате
  мытарств  родился  самообучающийся динамический дебагер. В нём юзается что-то
  вроде нейро-сети, хотя конечно знатоки скорее всего это дело назовут как-нить
  иначе. Ну да пох, собственно какова идея:

  Есть файл disasm.txt, в нём хранятся записи в таком виде:

  {первый опкод, второй опкод, третий опкод, длина инструкции}

  Например:
    {0x5d, 0, 0, 1},
    {0x90, 0, 0, 1},
    {0xe8, 0x16, 0, 5},
     Это  собственно  мозг  нашего  дизассемблера.  По  мере его обучения, файл
  увеличивается. Это своего рода нейросеть и есть =)

     Далее  есть  скрипт,  который  перегоняет  disasm.txt  в  удобоваримый код
  массива  структур  и  сохраняет  его  в  disasm.h,  который  можно  юзать  из
  какой-нибудь сишной проги.

     Теперь  есть  прога,  которая юзает disasm.h и пытается дизасмить на блоки
  какой-то бинарник. Пашет это так: читаем байт из бинарника и прогоняем его по
  массиву  структур из disasm.h, если первый байт совпадает смотрим на второй и
  третий  байты. Если же других структур, кроме той где указан лишь первый байт
  опкода,  нету  -  то  берём длину из этой структуры и скипаем на её величину.
  Если  записи  с  первым  опкодом  равным искомому байту нету, то выходим. Всё
  просто до безобразия. Прогу назовём ./disasm.

  Код в бинарнике:
  0x55 0x89 0xe5 0x83 0xec 0x0c 0x90 ...

  Структуры:
  ...
  {0x55, 0, 0, 1},
  {0x89, 0, 0, 2},
  {0x83, 0xec, 0, 3},
  {0x90, 0, 0, 1},
  ...

  Вот как будет идти дизасм:
  [ 55 ]       (длина блока 1)
  [ 89 e5 ]    (длина блока 2)
  [ 83 ec 0c ] (длина блока 3)
  [ 90 ]       (длина блока 1)

     Таким  образом мы можем представить бинарный код в виде блоков-инструкций,
  каждую  из  которых можно поменять или проинфектить. Ну об этом далее, а пока
  сосредоточимся на том как сделать самообучающийся дизассемблер.

     На данный момент всё тупо и статично - если есть записи в файле disasm.txt
  то  всё  норм,  если нет - то дизассемблер начинает спотыкаться. Поэтому наша
  задача  -  написать  софтину  (в  данном случае это перловый скрипт), которая
  будет    изучать    причины   спотыкания   дизассемблера   и   модифицировать
  disasm.txt. Наш вариант основан на перле и юзает objdump - смотрит как
  разбирает инструкции на блоки disasm и сверяет с выводом objdump-а, и в
  зависимости от этого модифицирует свою память (disasm.txt). Вот лог работы:


learning current neuro-net
try 8049c87
 8049c87:   a0 3a be 04 08          mov    0x804be3a,%al

i=13  addr = 0x8049c87
len = 5
new = {0xa0, 0x3a, 0xbe, 5},
new array len = 4 (0x, 0x, 0x)
new array len = 4 (0x, 0x, 0)
try 8049e2e

minus 2
try 8049e2c
 8049e2c:   ff 70 1c                pushl  0x1c(%eax)

i=13  addr = 0x8049e2c
len = 3
new = {0xff, 0x70, 0x1c, 3},
new array len = 30 (0x, 0x, 0x)
new array len = 30 (0x, 0x, 0)
new array len = 1 (0x, 0, 0)
nlen=2 lena=3
try 8049fca

minus 2
try 8049fc8
 8049fc8:   ff 72 0c                pushl  0xc(%edx)

i=13  addr = 0x8049fc8
len = 3
new = {0xff, 0x72, 0x0c, 3},
new array len = 31 (0x, 0x, 0x)
new array len = 31 (0x, 0x, 0)
new array len = 1 (0x, 0, 0)
nlen=2 lena=3

try 8049fe3

minus 2
try 8049fe1
 8049fe1:   ff 71 10                pushl  0x10(%ecx)

i=13  addr = 0x8049fe1
len = 3
new = {0xff, 0x71, 0x10, 3},
new array len = 32 (0x, 0x, 0x)
new array len = 32 (0x, 0x, 0)
new array len = 1 (0x, 0, 0)
nlen=2 lena=3
try 804a171

minus 3
try 804a16e
 804a16e:   66 3d 00 20             cmp    $0x2000,%ax

i=13  addr = 0x804a16e
len = 4
new = {0x66, 0x3d, 0, 4},
new array len = 15 (0x, 0x, 0x)
new array len = 5 (0x, 0x, 0)
new array len = 1 (0x, 0, 0)
nlen=3 lena=4
try 804a50f

minus 1
try 804a50e

minus 2
try 804a50c
 804a50c:   f7 35 48 d6 04 08       divl   0x804d648

i=13  addr = 0x804a50c
len = 6
new = {0xf7, 0x35, 0x48, 6},
new array len = 13 (0x, 0x, 0x)
new array len = 13 (0x, 0x, 0)
new array len = 1 (0x, 0, 0)
nlen=2 lena=6
try 804a53b

minus 2
try 804a539

minus 2
try 804a537

minus 3
try 804a534
 804a534:   83 7f 0c 01             cmpl   $0x1,0xc(%edi)

i=13  addr = 0x804a534
len = 4
new = {0x83, 0x7f, 0x0c, 4},
new array len = 30 (0x, 0x, 0x)
new array len = 30 (0x, 0x, 0)
new array len = 1 (0x, 0, 0)
nlen=3 lena=4


   Ну а теперь собственнно детали и код. Disasm, когда спотыкается, дампает
 инфу о том, на каком опкоде он споткнулся и какие успел до этого прошагать.
 Примерно такой вот вывод:

    804a57d    - адрес, где произошло спотыкание
    cb         - опкод, вызвавший сбой
    0x8d:3     - последние 6 пройденых опкодов
    0x99:1
    0x3:2
    0x38:2
    0x56:1
    0x3c:2

     Разбирать  будем  это  так:  смотрим напрямую objdump-овый дамп по данному
  адресу,  смотрим  где  мы находимся. Если опкод, вызвавший сбой - первый байт
  инструкции,  то это просто неизвестная нам запись. Добавляем её в disasm.txt.
  Дальше,  если  адрес  где  произошло  спотыкание  ни  показывает ничего через
  objdump  (мы попали в середину блока какой-то инструкции), то шагаем назад по
  списку  и проверяем записи в нейро-сети. Либо одна из предидущих длин записей
  указана  неверно,  либо  существует случай при котором нужно учитывать второй
  опкод, или даже третий. Модифицируем "мозг".

  Вот исходник скрипта для обучения нейро-сети:

#!/usr/bin/perl

@phile = `cat file`;
chomp @phile;

if ($#phile < 1)
{
    print "BLYA\n";
    exit;
}

$IS_LIB = $phile[1];
$PROG = $phile[0];

@argz = `cat status`;
chomp @argz;

($ch[1], $len[1]) = split(/:/, $argz[7]);
($ch[2], $len[2]) = split(/:/, $argz[6]);
($ch[3], $len[3]) = split(/:/, $argz[5]);
($ch[4], $len[4]) = split(/:/, $argz[4]);
($ch[5], $len[5]) = split(/:/, $argz[3]);
($ch[6], $len[6]) = split(/:/, $argz[2]);

if ($IS_LIB)
{
    $addr = hex($argz[0]);
} else {
    $addr = hex($argz[0]);
}

if ($argz[7] eq "0xc3:1" && $argz[1] eq "0")
{
    print "Done\n";
    `echo DONE > complete`;
    exit;
}

$i = 0;
while ($i < 7)
{
    $adrs = sprintf("%x", $addr);
    print "try $adrs\n";
    $tst = `objdump -d $PROG | grep $adrs:`;
    print "$tst\n";
    chomp $tst;

    if (length($tst) > 2)
    { $i = 12; }

    $i++;
    if ($i < 7)
    {
    $addr -= $len[$i];
    print "minus $len[$i]\n";
    }
}

printf("i=%i  addr = 0x%x\n", $i, $addr);

$ls=`objdump -d $PROG | grep $adrs:`;
chomp $ls;

@test = split(/\t/, $ls);
$ls = $test[1];

$ops = "";
$ls =~ s/([a-f0-9][a-f0-9])\s/ $ops .= "$1 " /ge;
@op_r = split(/ /, $ops);

$op_r[0] = "0x$op_r[0]";

$lena = $#op_r + 1;

if ($lena > 1)
{
    if ($lena > 2)
    {
    if ($op_r[2] eq "00")
    {
        $op_r[2] = 0;
        } else {
            $op_r[2] = "0x$op_r[2]";
    }
    }

    if ($op_r[1] eq "00")
    {
        $op_r[1] = 0;
    } else {
    $op_r[1] = "0x$op_r[1]";
    }
}

$lena = $#op_r+1;

if ($lena == 7)
{
    print "EXTRA LEN CHECK\n";

    $xtr = `objdump -d $PROG | grep -1 $adrs: | tail -1`;
    chomp $xtr;
    ($a1, $a2) = split(/:/, $xtr);
    $xtr = $a2;

    ($b1, $b2, $b3) = split(/\t/, $xtr);
    $xtr = $b2;

#    $xtr =~ s/\s+/ /g;
    @xtra = split(/ /, $xtr);

#    print "len xtra = $#xtra; len xtra2 = $#xtra2\n";

#    if ($#xtra == $#xtra2 + 2)
    if ($b3 eq "")
    {
    print "found long isnt $xtr\n";
    $lena += $#xtra+1;
    }
}

print "len = $lena\n";

if ($i == 13)  # true unknown op
{
    add_op();
    exit;
}

#

@data = `cat disasm.txt`;
chomp @data;

if ($lena > $len[$i])
{
    open(FD,">disasm.h");

    foreach $rec (@data)
    {
    if ($rec =~ m/{0x$op_r[0], 0/)
    {
        print FD "{0x$op_r[0], 0x$op_r[1], 0, $lena},\n";
    } elsif ($rec =~ m/0, $len[$i]}/)
    {
        print FD "{0x$op_r[0], 0x$op_r[1], 0, $lena},\n";
    } else {
        print FD "{0x$op_r[0], 0x$op_r[1], 0x$op_r[2], $lena},\n";
    }

    print FD "$rec\n";
    }

    close(FD);
}

if ($lena < $len[$i])
{
    print "change len for op $op_r[0] from $len[$i] to $lena\n";
    open(FD,">disasm.h");

    foreach $rec (@data)
    {
    if ($rec !~ m/^{0x$op_r[0]/ || $rec !~ m/$len[$i]}/)
        { print FD "$rec\n"; next; }

    if ($lena < 6)
    {
        print FD "{0x$op_r[0], 0, 0, $lena},\n";
    } else {
        print FD "{0x$op_r[0], 0x$op_r[1], 0, $lena},\n";
    }
    }
    close(FD);
}

if ($lena == $len[$i])
{ exec("echo DONE > complete"); }

exit;

sub add_op()
{
    @data = `cat disasm.txt`;
    chomp @data;

    print "new = {$op_r[0], $op_r[1], $op_r[2], $lena},\n";

    open(FD, ">disasm.txt");

    # write all other lines
    $i = 0;
    foreach $line (@data)
    {
    if ($line !~ m/{$op_r[0]/)
    {
        print FD "$line\n";
    } else {
        $dat2[$i] = $line;
        $i++;
    }
    }

    if ($i == 0)
    {
    new_add();
    close(FD);
    exit;
    }

    print "new array len = $i (0x, 0x, 0x)\n";

    $i = 0;
    # {0xa, 0xb, 0xc}
    foreach $line (@dat2)
    {
    if ($lena < 3 || $line !~ m/{$op_r[0], $op_r[1], $op_r[2], /)
    {
        if ($line !~ m/, 0,/)
        {
        print FD "$line\n";
        } else {
        $dat3[$i] = $line;
        $i++;
        }

        next;
    }

    if ($line =~ m/, $lena}/)
    {
        print FD "$line\n";
        last;
    } else {
        print "ERROR - two eq rec with diff lens $line vs ($lena)\n";
        $c = `echo ASS > complete`;
        exit;
    }
    }

    if ($i == 0)
    {
    new_add();
    close(FD);
    exit;
    }

    print "new array len = $i (0x, 0x, 0)\n";
    $i = 0;

    # {0xa, 0xb, 0}
    foreach $line (@dat3)
    {
    if ($lena < 2 || $line !~ m/{$op_r[0], $op_r[1], 0, /)
    {
        if ($line !~ m/, 0, 0/)
        {
        print FD "$line\n";
        } else {
        $dat4[$i] = $line;
        $i++;
        }

        next;
    }

    $nlen = get_len($line);

    if ($nlen == $lena)
    {
        print "line $line\n";
        print "EQUAL\n";
        print FD "$line\n";
        close(FD);
        exit;
    }

    if ($lena > $nlen)
    {
        print FD "{$op_r[0], $op_r[1], $op_r[2], $lena},\n";
        print FD "$line\n";
    } else {
        print FD "{$op_r[0], $op_r[1], 0, $lena},\n";
    }
    last;
    }

    if ($i == 0)
    {
    new_add();
    close(FD);
    exit;
    }

    print "new array len = $i (0x, 0, 0)\n";

    if ($i > 1)
    {
    print @dat4;
    close(FD);
    $c = `echo $i > complete`;
    exit;
    }

    # $i should be == 1

    $nlen = get_len($dat4[0]);
    print "nlen=$nlen lena=$lena\n";

    if ($lena > $nlen)
    {
    print FD "{$op_r[0], $op_r[1], 0, $lena},\n";
    print FD "$dat4[0]\n";
    } else {
    print FD "{$op_r[0], 0, 0, $lena},\n";
    }

    close(FD);
    return 0;
}


sub get_len()
{
    $t = shift @_;
    ($a,$b,$c,$d) = split(/,/, $t);

    $d =~ s/ //;
    $d =~ s/[{,}]//;

    return $d;
}


sub new_add()
{
    if ($lena > 6)
    {
    print FD "{$op_r[0], $op_r[1], $op_r[2], $lena},\n";
    }

    if ($lena > 4 && $lena < 7)
    {
    print FD "{$op_r[0], $op_r[1], 0, $lena},\n";
    }

    if ($lena < 5)
    {
    if ($op_r[0] ne "0x00")
    {
        print FD "{$op_r[0], 0, 0, $lena},\n";
    } else {
        print FD "{$op_r[0], $op_r[1], 0, $lena},\n";
    }
    }

    return 0;
}

  Всё это проще понять, если погонять софтину на своём боксе. В инклудах в дире
  neiro лежат все необходимые исходнки. Юзается это всё так:

  1. в auto.sh правим вот эти строки:

    DPROG="./test"       # файл, который собираемся дизасмить
    OFFS="0x8048330"     # смещение к началу секции с кодом, виртуальный адрес

    IS_LIB=0             # исследуемый файл: 1 - библиотека, 0 - обычный бираник

  2. если есть желание обучать нейросеть с нуля, то достаточно удалить старые
     базы.

     # rm -f disasm.h disasm.txt

  3. производительность зависит от многих параметров, но к примеру на роутере
    p133 и 32mb RAM с FreeBSD система пашет сравнительно медленно =) Поэтому
    желательно машину побыстрее.

 з.ы. замечаение, могут возникнуть мысли типа - а не проще ли, если система
  эта юзает objdump сделать просто сразу полный дизасм-дамп всего кода и
  его уже просто раскидать на записи disasm.txt. Это конечно неплохо, но тогда
  получится скорее не самообучающийся дизассемблер, а продвинутый скрипт-парсер.
  Кому надо - тот напишет )