суббота, 26 декабря 2015 г.

Изучение Base64

Сегодня я решил написать о том, как выглядит алгоритм base64 в исполняемом файле.
Для этого я взял две реализации, мою собственную (немного корявую), и взятую из интернета.
Собственно логика этого алгоритма предельно проста: исходная строка разбивается на символы, затем они переводятся в двоичный вид, склеиваются в одну последовательность, затем берется грубо говоря по 6 бит и сравнивается с таблицей base64, в которой каждой такой последовательности соответствует символ из ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/.

Данный пост отражает только мысли автора.



   Итак, начнем с моей реализации данного алгоритма. Так как нас интересует вид функции шифрования, то сразу переходим в процедуру проверки пароля. Ее можно найти по ссылкам на строки Name, Password.



Видим, что перед сравнением строк вызывается функция call   sub_8048CC0. Это то что нам нужно.
Немного пугает, да? ))
Если посмотреть на самый первый блок, то мы увидим, что в нем происходит инициализация массива, по типу A[i][j]={'a-z','000000-111111'}.
Затем мы должны перевести символы в двоичный вид. Для этого я использовал такой код:
i=strlen(c);     //здесь длина нашей строки имени
for (k=0; k<i; k++){
for (int j = 7; j >= 0; --j)
    {
        b=( (c[k] & (1 << j)) ? '1' : '0' );
        o.append(1,b);       //здесь выходная строка
    }

А вот тот же код только в IDA:
loc_8049A88:
movsx   eax, byte ptr [edi+esi]
mov     ecx, ebx
mov     dword ptr [esp+4], 1
mov     [esp], ebp    
sar     eax, cl
and     eax, 1
cmp     eax, 1
sbb     eax, eax
add     eax, 31h
mov     [esp+8], eax    ; char
call    __ZNSs6appendEjc ; std::string::append(uint,char)
sub     ebx, 1
cmp     ebx, 0FFFFFFFFh
jnz     short loc_8049A88

Далее, чтобы корректно кодировались строки не кратные 6, мы дополняем нашу двоичную строку, в моем случае '0'. Вот код:
     ost=strlen(o.c_str())%6; //остаток от деления двоичной строки на 6
     ost1=strlen(o.c_str())/6; //просто деление на 6
     int razn=-strlen(o.c_str())+(ost1+1)*6;//разница от деления+1 умноженная на 6 и длиной строки
     if (ost!=0) o.append(razn,'0');

Вот как IDA распознала:
loc_8049AC0:
mov     esi, [esp+2Ch]
mov     [esp], esi      ; s
call    _strlen
mov     edx, 0AAAAAAABh
mov     ecx, eax
mul     edx
shr     edx, 2
lea     eax, [edx+edx*2]
add     eax, eax
cmp     ecx, eax
jnz     loc_8049C6B
loc_8049C6B:
lea     eax, [edx+edx*2+3]
add     eax, eax
sub     eax, ecx
mov     dword ptr [esp+8], 30h ; char
mov     [esp+4], eax    ; unsigned int
mov     [esp], ebp      ; this
call    __ZNSs6appendEjc ; std::string::append(uint,char)
mov     esi, [esp+2Ch]
mov     [esp], esi      ; s
call    _strlen
jmp     loc_8049AE7
mov     eax, ecx

Далее идет основной цикл кодирования. Рассматривать его не буду, так как он простой.
Рассмотрим теперь реализацию 2.
Глядя на то что показывает IDA можно подумать что не все так плохо.

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

Комментариев нет:

Отправить комментарий