Дмитрий DarkByte Москин

Мой блог, да.
logo

DefCon 19 - write-up rr300

Опубликовано 08.06.2011 автором Дмитрий Москин
Ещё один таск, который удалось решить :)

Retro 300
Даны 2 файла retro300 и auth.db. Первый линуксовый бинарник, второй файл базы SQLite.

При осмотре бинарника блокнотом выявляется следующая информация:
1) для подключения к сервису требуется пароль letmeinpls
2) ключ лежит на сервере в файле /home/retro300/key
3) возможно sql инъекция в запросе "select id,pin from users where user='%s'"

При осмотре файла БД находим одну таблицу с юзерами и их паролями.

Пробуем подключиться к сервису и авторизоваться с помощью данных из базы:
nc pwn512.ddtek.biz 5500
letmeinpls
_____ ______ _____ _ _
__ ____ __ / ____| ____/ ____(_) | |
____/ /___/ / /____ / /__ | (___ | |__ | | _ __| |
/ __ / __ / __/ _ \/ //_/ \___ \| __|| | | |/ _` |
/ /_/ / /_/ / /_/ __/ ,< ____) | |___| |____| | (_| |
\__,_/\__,_/\__/\___/_/|_| |_____/|______\_____|_|\__,_| (beta)


( cause everyone is looking for a new provider right?!)

Username:aaron
Passcode:7345
Bad Username or Pin


Не удачно. Попробуем разобраться в чём дело. Закидываем бинарник в IDA, находим функцию, отвечающую за авторизацию.
signed int __cdecl sub_8049D1F(int fd)
{
//local vars
v16 = sub_8048EBB(fd, (int)s1, 0x100u, 10);
if ( v16 <= 0 )
exit(0);
s1[v16] = 0;
if ( strcmp(s1, ::s2) )
exit(0);
sub_80494A3();
sub_8049418(fd);
v15 = sub_8048F25(fd, "Username:", 0);
v1 = sub_8048EBB(fd, (int)nptr, 0x63u, 10);
v14 = v1;
if ( v1 > 0 && v14 <= 99 )
{
nptr[v14] = 0;
ptr = strdup(nptr);
v15 = sub_8048F25(fd, "Passcode:", 0);
v14 = sub_8048EBB(fd, (int)nptr, 0x63u, 10);
if ( v14 == 14 )
{
v12 = 0;
strncpy(&dest, nptr, 4u);
v8 = 0;
if ( sub_8049AB3((int)ptr, (int)&s2, (int)&v4) >= 0 )
{
if ( strncmp(&dest, s2, 4u) )
{
sub_8048F25(fd, "Bad Username or Pin\n", 0);
free(ptr);
free(s2);
result = 1;
}
else
{
strncpy(&v5, &v11, 0xAu);
v6 = 0;
printf("sending %s out of buf: %s\n", &v5, nptr);
if ( sub_8049C02(v4, &v5) >= 0 )
{
do
{
sub_80497CC(fd);
v14 = sub_8048EBB(fd, (int)nptr, 0x63u, 10);
nptr[v14] = 0;
if ( v14 == 1 )
{
v17 = atoi(nptr);
if ( (unsigned int)v17 <= 9 )
JUMPOUT(__CS__, (unsigned int)off_804A918[v17]);
sub_8048F25(fd, "please select from the options presented", 0);
}
else
{
sub_8048F25(fd, "please select from the options presented", 0);
}
}
while ( v17 != 5 );
free(ptr);
free(s2);
result = 0;
}
else
{
sub_8048F25(fd, "Bad Username or Passcode\n", 0);
free(ptr);
free(s2);
result = 1;
}
}
}
else
{
sub_8048F25(fd, "Bad Username or Pin\n", 0);
free(ptr);
result = 1;
}
}
else
{
sub_8048F25(fd, "Bad Username or Pin\n", 0);
free(ptr);
result = 1;
}
}
else
{
printf("name overflow attemp: %d vs %d \n", v14, 100);
result = 1;
}
return result;
}


Понимаем, что логин может содержать от 1 до 99 символов, а пароль должен содержать ровно 14 символов, хотя в базе таких пользователей нет. Далее видим, что в функции sub_8049AB3, отвечающей за чтение данных пользователя из базы, нет экранирования кавычек, соответственно можно провести SQL инъекцию.

Передаём в качестве имени пользователя: ' union select 1,'12341234567890
В итоге запрос принимает вид: select id,pin from users where user='' union select 1,'12341234567890'

И возвращает из базы именно то, что нужно, 14 символьный пароль. Пробуем подключиться с указанным выше логином и паролем "12341234567890", на этот раз получаем ответ "Bad Username or Passcode", это означает, что авторизацию мы прошли, но есть какая то ещё проверка, реализованная в функции sub_8049C02. Смотрим, что она делает.

int __cdecl sub_8049C02(int a1, const char *s2)
{
//local vars
v15 = -1;
*(_DWORD *)s1 = 875770417;
v5 = 875770417;
v6 = 0;
v7 = 0;
v10 = 600;
v14 = dword_804BBAC;
v11 = 1976360820;
hostlong = dword_804BBAC ^ 0x75CCDF74;
v9 = time(0);
v8 = v9 - v9 % 0x258u;
for ( i = 0; i <= 149999; ++i )
{
hostlong = sub_80494E0(hostlong, v14);
if ( v14 == v8 )
{
v2 = sub_80494F8(hostlong);
sprintf(s1, "%0.10u", v2 * (a1 + 1));
v7 = 0;
}
v14 += v10;
}
if ( !strncmp(s1, s2, 0xAu) )
v15 = 0;
return v15;
}

И на этот раз точно не понимаем, что она делает. Видно лишь то, что она считает какую то контрольную сумму, зависимую от времени, а затем сравнивает её с последними 10 символами нашего пароля.

Кроме времени, хэш функция так же зависит от передаваемого параметра a1, который, как оказалось, является полем ID, выбираемым из базы. Немного подумав, приходит мысль подставить в качестве ID пользователя "-1", тогда вне зависимости от переменной v2 результат всегда будет 0, т.е. хэш будет "0000000000".

Пробуем вновь подключиться к сервису:
Username:' union select -1,'12340000000000
Passcode:12340000000000
DDTEK VPN console

Choose an option:
1: change pin
2: re-sync sec token
3: add user
4: change username
5: exit

Отлично, мы зашли, осталось придумать, что же делать дальше :)

Снова смотрим в функцию sub_8049D1F
                v17 = atoi(nptr);
if ( (unsigned int)v17 <= 9 )
JUMPOUT(__CS__, (unsigned int)off_804A918[v17]);

и видим, что от нас хотят число, от нуля до 9, а так же видим массив off_804A918, по указателям из которого будет выполнен переход, после нашего ввода.


.rodata:0804A918 off_804A918 dd offset loc_804A111
.rodata:0804A91C dd offset loc_804A0AF
.rodata:0804A920 dd offset loc_804A0C9
.rodata:0804A924 dd offset loc_804A0EA
.rodata:0804A928 dd offset loc_804A0F7
.rodata:0804A92C dd offset loc_804A104
.rodata:0804A930 dd offset loc_804A111
.rodata:0804A934 dd offset loc_804A111
.rodata:0804A938 dd offset loc_804A0D6
.rodata:0804A93C dd offset loc_804A0BC
.rodata:0804A93C _rodata ends


Пробегаемся по указателям и в 8м (loc_804A0D6) находим функцию чтения и вывода ключа.
Вводим цифру 8 и видим: lookheedsurLovesemsumAPT, что и есть ключ :)