Сегодня я решил написать о том, как выглядит алгоритм 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 можно подумать что не все так плохо.
Начальная часть похожа на проверку параметров для кодирования, типа размеров массива,
или выравнивания по длине. В отличие от моей реализации, здесь нет так много циклов, так
как там преобразования выполняются сразу блоками.
Не буду говорить что я разобрался в каждой строчке. Да это и не нужно, когда надо определить какой алгоритм используется.
В заключение, скажу, что мне было интересно посмотреть, насколько сильно будут различаться реализации такого ламера как я, и профессионального разработчика. Сравнительная легкость разбора моей версии относительно чужой, показывает что "велосипедостроение" не есть хорошо. Особенно в сфере защиты информации.
Для этого я взял две реализации, мою собственную (немного корявую), и взятую из интернета.
Собственно логика этого алгоритма предельно проста: исходная строка разбивается на символы, затем они переводятся в двоичный вид, склеиваются в одну последовательность, затем берется грубо говоря по 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 можно подумать что не все так плохо.
Начальная часть похожа на проверку параметров для кодирования, типа размеров массива,
или выравнивания по длине. В отличие от моей реализации, здесь нет так много циклов, так
как там преобразования выполняются сразу блоками.
Не буду говорить что я разобрался в каждой строчке. Да это и не нужно, когда надо определить какой алгоритм используется.
В заключение, скажу, что мне было интересно посмотреть, насколько сильно будут различаться реализации такого ламера как я, и профессионального разработчика. Сравнительная легкость разбора моей версии относительно чужой, показывает что "велосипедостроение" не есть хорошо. Особенно в сфере защиты информации.
Комментариев нет:
Отправить комментарий