написание шеллкода: почему мой шеллкод не работает?

В настоящее время я пишу шелл-код, который использует целевую программу, использующую функцию puts. Программа выглядит так:

#include <stdio.h>
main() {
    char buf[123];
    puts(gets(buf));
}

Что я хочу сделать, так это переполнить этот буфер и вызвать execve с некоторыми аргументами. У меня есть тестовая программа, написанная на c/inline ассемблере, которая может вызывать execve с некоторыми аргументами, затем я использую gdb для получения шелл-кода из этой программы. Насколько я понимаю, макет стека выглядит так:

|-------buffer(+padding)---------|---------sfp---------|------- рет-------------|

Глядя на часть ассемблерного кода целевой программы, сгенерированной gcc:

.cfi_startproc                  
pushq   %rbp                    
.cfi_def_cfa_offset 16          
.cfi_offset 6, -16              
movq    %rsp, %rbp              
.cfi_def_cfa_register 6         
addq    $-128, %rsp             
leaq    -128(%rbp), %rax        
movq    %rax, %rdi              
call    gets                    
movq    %rax, %rdi              
call    puts                    
leave                           
.cfi_def_cfa 7, 8               
ret                             
.cfi_endproc       

Я думаю, что буфер и заполнение занимают 128 байт, а sfp и адрес возврата занимают 8 байт, так что всего 144 байт. Исходя из этого, мой nop-след, полезная нагрузка и новый адрес возврата (равный адресу буфера) вместе взятые (то есть мой шелл-код) тоже должны быть 144 байт. Например, если моя полезная нагрузка составляет 36 байт, поскольку адрес возврата занимает 8 байт, мой nop-след будет 100 байт. Но когда я сделал это таким образом, это не сработало. Поэтому я думаю, что, возможно, то, как я понял структуру стека, было неправильным. Это неправильно?

Обратите внимание, что в моем случае адрес буфера был известен, а стек был установлен как исполняемый с помощью execstack, а ASLR был отключен с помощью setarch. Таким образом, если адрес возврата был перезаписан адресом буфера, будет выполнен код, записанный в этом буфере.

И я работаю на 64-битной машине x86.

Если я правильно понимаю структуру стека, я добавлю больше информации о своем шелл-коде.


person Gnijuohz    schedule 23.10.2013    source источник
comment
Я не думаю, что вам действительно нужны nop-сани, если вы знаете точный адрес, по которому будете прыгать. Однако это не повредит, и вам понадобится какое-то заполнение, чтобы добраться до обратного адреса и перезаписать его. Может быть и нет.   -  person user2357112 supports Monica    schedule 23.10.2013
comment
@ user2357112 правда. В любом случае это должно работать.   -  person Gnijuohz    schedule 23.10.2013
comment
Это больше похоже на ошибку, связанную с пердежом мозга, чем на то, что мое понимание ошибочно, и мне нужно кое-что изучить. Я не вижу ничего явно плохого в вашем понимании структуры стека.   -  person user2357112 supports Monica    schedule 23.10.2013
comment
Подождите, этот C не должен компилироваться. main не имеет возвращаемого типа. Это реальный код? Если нет, скопируйте/вставьте фактический код в вопрос. Никогда не вводите код повторно в поле вопроса; вы скроете ошибки и введете новые ошибки.   -  person user2357112 supports Monica    schedule 23.10.2013
comment
@user2357112 user2357112 Это реальный код. Он может компилироваться с помощью gcc. Я думаю, что эта программа должна быть плохой... как пример   -  person Gnijuohz    schedule 23.10.2013
comment
О, я забыл, что пропущенные типы возвращаемых значений по умолчанию имеют значение int по историческим причинам.   -  person user2357112 supports Monica    schedule 23.10.2013
comment
Запуск вашего шеллкода приводит к segfault? Какую версию ядра Linux вы используете? В более поздние ядра (и GCC) добавлено множество довольно изящных функций безопасности, которые препятствуют самым простым попыткам использования шелл-кода.   -  person    schedule 07.12.2013


Ответы (2)


1) Вы используете уязвимый код не потому, что он имеет функцию puts(), вы используете его, потому что он использует функцию gets(), которая здесь уязвима для переполнения стека.

2) Когда у вас есть символ buf[123], если вы введете 122 символа, а затем один нулевой терминатор, стек в порядке. Но когда вы вводите больше, происходит следующее:

Давайте представим, что это buf[4], когда вы получаете()

input AAAA
EBP - 4 => will be AAAA

input AAAAAAAA (8 bytes)
EBP -4 => AAAA
EBP also => AAAA

if you enter 12x A
function return address will be 0x41414141

теперь вы также перезапишете адрес возврата функции, поэтому он тоже будет AAAA 0x41414141! Оттуда вам нужно указать адрес возврата на адрес вашего шеллкода, чтобы выполнить шеллкод.

Таким образом, при вызове функции и переполнении макет выглядит следующим образом:

Buffer for temporary storage
local variables
The saved EBP
Function return address
Function's arguments
Stack frame

так это снизу вверх. На самом деле для больших переменных лучше использовать metasploit pattern_offset.rb, он генерирует большую строку, и когда вы узнаете значение EIP, вы можете использовать вывод patter_offset.rb для определения ТОЧНЫХ отступов, необходимых для перезаписи EIP для выполнения шеллкода.

Так что на самом деле, чтобы перезаписать адрес возврата функции, в основном вам нужно [размер переменной] + 8. Но это зависит от локальных переменных, их размера, порядка и т. д. Также это зависит от компилятора, архитектуры и т. д. В основном это делается путем попытки и pattern_offset.rb и т. д.

person 72DFBF5B A0DF5BE9    schedule 31.01.2014

Когда подпрограмма начинается, она имеет адрес возврата в стеке. Я приму значение rsp = 0x428 только для того, чтобы мы могли посмотреть на реальные (но произвольно выбранные) числа:

0x428 return addr

Затем нажимаем rbp

0x428 return addr (8 bytes)
0x420 original rbp (8 bytes)

Затем мы выделяем буфер, который составляет 123 байта + padding = 0x80

0x428 return addr (8 bytes)
0x420 original rbp (8 bytes)
0x418 last eight bytes of buffer+padding 
  ...
0x3a0 buffer (128 bytes)

Таким образом, если вы ожидаете разбить адрес возврата по адресу 0x428, ваш ответ для gets() должен иметь общую длину 144 байта, причем последние 8 байтов будут изменены вашим шелл-кодом так, чтобы они указывали куда-то еще (обычно на саму область буфера).

Предполагая, что sfp на вашей диаграмме стека означает "указатель сохраненного фрейма" (иначе известный как rbp), тогда да, вы правильно поняли. ваш шеллкод, который, вероятно, неисправен. (И это, наверное, будет отдельный вопрос.)

person Edward    schedule 31.01.2014