LINUX.ORG.RU

Как написать x86 команду для вызова функции по адресу

 , , ,


0

2

Есть одна laba1, которую я типа взламываю. laba1 это программа на C, в ней есть функция write_secret, и мне нужно подавая определенный ввод этой проге, добиться, чтобы эта функция вызвалась. Эта программа читает ввод с stdin, распознает ввод как число и пытается из массива указателей на функции выбрать функцию по индексу, равному этому числу. Функции write_secret в этом массиве нет, но там нет никаких проверок на out-of-bounds, короче я могу подобрать такое число, что программа сделает call (архитектура = x86) по адресу, который указывает на середину массива, читающегося с stdin. Соответственно мне надо записать какие-то инструкции через stdin, чтобы они вызвали функцию write_secret.

В описании этой лабы на курсере говорят, что туда надо впихнуть какие-то 4 числа по 3 байта вида \xEE\xEE\xEE\xEE (вместо EE что-то подставлять), чтобы когда процессор вызовет CALL по адресу этих байтов, чтобы они вызвали нужную функцию.

А, эти \x хрени это кажется шестнадцатиричные чиселки, каждое из двух шестнадцатиричных цифр, то есть каждое число это 16*16 = 256 значений, то есть каждое \xEE это 3 байта.

Я тут гуглил, смотрел в gdb и просто получал ассемблерный код и смотрел, как там программа обычно вызывает функции, она их вызывает например вот так.

https://pp.vk.me/c627617/v627617612/22fed/OCMWTdAVCHM.jpg

Там видно, что строчка tmp(); в ассемблере это MOV EAX, локальная переменная tmp и затем CALL EAX.

А если на этом скриншотике посмотреть в низ лево, то там увидите, что я с помощью гдб посмотрел, че там в цифрах записано в этих двух ассемблерных строчках:

0x8048808 // это MOV

0x804880b // это CALL

Ну короче если это перевести в формат по 4 16-ричных числа, то это [ ff d0 e9 3a ] [ 8b 45 f4 ff ]

И действительно, я нагуглил онлайн дизассемблер https://www.onlinedisassembler.com/odaweb/ туда вбил 8b 45 f4 ff и получил то же самое, что ассемблером написано в гдб - mov eax,DWORD PTR [ebp-0xc] а под ним получил .byte 0xff - то есть я так понимаю, что в этой команде нужен номер операции, и 2 каких-то числа, означающих номер регистра и какое-то там смещение наверное, а 4-ое число не нужно, поэтому там ff, который ничего не значит.

И еще я где-то нагуглил опкоды операции CALL: https://pp.vk.me/c627617/v627617612/22ff7/IBPjdUUmNUw.jpg значит CALL который начинается с 3 байтов ff это либо Call near, absolute indirect, address given in r/m16 ( и тут же такой же вариант с r/m32 в конце), либо Call far, absolute indirect, address given in m16:16 (либо опять же 32 в самом конце).

И вот видимо мне надо состряпать инструкции, чтобы вызвать функцию по адресу, известному мне (да, я знаю адрес write_secret - беру его из gdb

(gdb) print &write_secret
$4 = (void (*)(void)) 0x8048534 <write_secret>

Стоит заметить, что в коде на скриншотике fptr это указатель на функцию, принимающую void и возвращающую void.

А собственно почему мне так именно вызвать нужно? Ну в этом курсе там задают наводящие вопросы - сначала меня спрашивают, какое число надо подать на стандартный вход, чтобы с помощью переполнения буффера указатель, указывающий обычно на какую-нибудь функцию, указал на 65-ый байт переменной buf, которая собственно читается с stdin. Это я смог сделать. Но теперь мне надо придумать, какие байтики послать на 65-ый и следующие байты buf, чтобы когда процессор выполнял их, но выполнил эту самую функцию write_secret по адресу мне известному. И вот я пытаюсь понять, как в эти 65-ый и дальше байты засунуть CALL с адресом и наверное RET.

Ты можешь написать функцию на си с вызовом по абсолютному адресу и скомпилировать с параметром -S

mittorn ★★★★★ ()
Ответ на: комментарий от Deleted

Да-да, он самый. Там ничего интересного нет, проходи дальше.

prischeyadro ★★★☆☆ ()
Ответ на: комментарий от Deleted

Пара постов выше. Это не то, про что сказал чел с котом с блинами на аватарке.

hlebushek ★★ ()
Ответ на: комментарий от hlebushek

Тогда рекомендую посмотреть HW/SF if. Он мне помог разобраться, хотя я на тот момент с трудом представлял даже как работает стек. :) Сейчас-то конкретные asm-инструкции уже подзабылись за ненадобностью.

prischeyadro ★★★☆☆ ()

16*16 = 256 значений, то есть каждое \xEE это 3 байта.

Тебе бы для начала как раз биты с байтами погонять не помешало.

prischeyadro ★★★☆☆ ()

Это в каком ВУЗ-е такое?

anonymous ()
Ответ на: комментарий от hlebushek

Спасибо! Когда-то на него энроллился, но потом забыл.

Deleted ()
Ответ на: комментарий от prischeyadro

Да, я глюканул. Не знаю почему решил, что у байта 8 значений. На деле 2^8 то есть 256, значит одно \xEE это байт

hlebushek ★★ ()
Последнее исправление: hlebushek (всего исправлений: 1)

laba1 это программа на C, в ней есть функция write_secret, и мне нужно подавая определенный ввод этой проге, добиться, чтобы эта функция вызвалась.

Что, обычный stack-overflow? Ну так забей каким-то говном переменную в функции, и подмени адресс возврата на адресс входа в нужную функцию.

invy ★★★★★ ()
Ответ на: комментарий от Deleted

Это тот курс, где предлагают подрочить на битовые операции в сишке в первой лабе.

Deleted ()

Вот ты, вроде, и в правильном направлении движешься, но такими путями, что похож на слепого котёнка. Я даже не знаю с чего начать. Возьми лучше курс по ассемблеру, а после прорешай пару десятков простеньких crackme.

mix_mix ★★★★★ ()
Ответ на: комментарий от mix_mix

Да не хочу просто курс по ассемблеру, это неинтересно. Как-нибудь изучу его по ходу курса кулхацкерства.

hlebushek ★★ ()

Если у команды CALL адрес 0x804873d, а у следующей команды - LEAVE - уже 0x8048742, то значит ли это, что на самом дел у CALL длина 5 байтов (3d, 3e, 3f, 40, 41) - то есть все до начала LEAVE, исключая само начало LEAVE?

http://i.imgur.com/rMBedoV.png

hlebushek ★★ ()
Ответ на: комментарий от hlebushek

Самый лучший способ не учить ассемблер и понять его - это писать на C.

main()
{
        void (*w)(long) = (void*)0x8048534;
        w(0x8048534);
}

Компилируем и выделяем интересное:

$ gcc -m32 -S -O call.c
$ as -alm --32 call.s
....
  14 0006 83EC10   		subl	$16, %esp
  15 0009 C7042434 		movl	$134513972, (%esp)
  15      850408
  16 0010 B8348504 		movl	$134513972, %eax
  16      08
  17 0015 FFD0     		call	*%eax
...
Параметры и т.п. можно добавить по вкусу. Вторая колонка - адрес, ну а байтики в третьей. Т.к. адрес грузиться в регистр - то позиция кода не важна.

io ★★ ()
Ответ на: комментарий от hlebushek

он ж печатает байты инструкций, не?
Ну ты можнщь в олбъектник сокмпилировать и смотреть objdump'ом, там и байты и оффсеты.

mittorn ★★★★★ ()
Ответ на: комментарий от io

В твоем выхлопе as 0010, 009, 0015 - это что? А про бинарную записаь я правильно понимаю, что первый movl это C7 04 24 34 85 04 08, и это все байтики в шестнадцатиричной записи? Или там прямо не байтики, а большие какие-то числа, и их надо разлагать на байтики и переворачивать из-за little-endian?

hlebushek ★★ ()

Хмм, оказывается в gdb можно распечатать бинарно команды вот так:

(gdb) x/20xb &call_secret
0x8048737 <call_secret>:	0x55	0x89	0xe5	0x83	0xec	0x08	0xe8	0xf2
0x804873f <call_secret+8>:	0xfd	0xff	0xff	0xc9	0xc3	0x8d	0x4c	0x24
0x8048747 <main+3>:	0x04	0x83	0xe4	0xf0

команда CALL 0xАДРЕС <write_secret> - начинается с 0xf2 и длиной 5 байт:

0xf2 0xfd 0xff 0xff 0xc9
. Но мне как-то надо выполнить write_secret за 4 байта, а не за 5 (не знаю почему, авторы курса так решили). Что же это значит? Нужен какой-то другой CALL с более близким адресом? Или вместо CALL просто как-то сменить, куда указывает регистр текущей инструкции?

hlebushek ★★ ()
Ответ на: комментарий от hlebushek

В общем я кое что перепутал, мне не надо 4-байтовую команду для прыжка написать. Я там кое где неправильно прочитал, что код делает. Мне всего лишь в эти 4 байта надо было засунуть адрес write_secret. Все оказалось куда проще, чем я думал.

hlebushek ★★ ()
Ответ на: комментарий от hlebushek

рукалицо
тебе дауну с азов начинать надо.
иди читай intel sdm

parrto ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.