Pull to refresh

Comments 92

В C, очевидно, это тоже будет работать. Ничего специфичного для C++ (не считая cout, конечно) в вашем примере нет.
И да, это простой бесполезный грязный хак.
Ок, понятно. Спасибо.
а что насчет защиты секции данных от исполнения? в виндовс это не предусмотренно? :)
Отличная задача для какого-нибудь бесчеловечного собеседования.
а почему reinterpret не работает? я всегда считал что c-cast==static_cast+reinterpret_cast
Компилятор отказывается приводить указатель на объект к указателю на функцию.
Да, я хотел сказать, что если через reinterpret_cast.
UFO just landed and posted this here
UFO just landed and posted this here
Я пока не дорос до трактования смысла каждого числа. Их получил путем просмотра памяти занимаемой функцией int sum(int a, int b){return a+b;}. В примере говорю компилятору, чтобы этот массив(тех самых чисел в памяти) рассматривал как функцию.
И никакой защиты сегмента данных от выполнения?
Примерно так я писал лет 12 назад на асме в реальном режиме :)
Для проникновения в глубины вселенной и ответа на вопрос 42 вам потребуются:

1) www.idapro.ru/ или ему подобные
2) Калькулятор, который высчитывает HEX
3) wasm.ru или книжку Стива Хатчетсона, ну или любой манускрипт с толковым описанием асьма
4) Смекалка.

В действительности, вы правы, это — машинный код. Его можно перевести в АСМь
Ох, ну и раззадорили Вы меня…

FF55
FF89
FFE5
FF8B
0045
000C
0003
0045
0008
005D
FFC3
FF90
После гугления выходит следующее:
0055 — *
FF89 —
FFE5 — BIT (2 байта)
FF8B — 0045 — *
000C — CLC
0003 — *
0045 — *
0008 — INX (1 байт)
005D — TST (1 байт)
FFC3 — *
FF90 —

Опытный читатель задаст вопрос: «Ну и что всё это значит?». Вопрос будет очень к месту. Значит это, что мои, пятилетней давности кульбиты на ассемблере давно уже забыты. Я не помню, как правильно читать байткоды из памяти, сколько бит использовать на каждый, и вообще и так далее.

Да… Программирование надо учить ежедневно.

Ладно, есть варианты, как правильно дизасембленуть? 8-)
0055 — PUSH rBP
FF89 — MOV Ev,Gv
FFE5 — IN eAX,Ib
FF8B — MOV Gv,Ev
0045 — INC eBP
000C — OR AL,Ib
0003 — ADD Gv,Ev
0045 — INC eBP
0008 — OR Eb,Gb
005D — POP rBP
FFC3 — CMP AL,Ib
FF90 — NOP

После возьни и прочей финги я нашёл таки интерпритатор кодов для процессора 386 а не для моторолы 6800, стало проще, хотя всё ещё не похоже на программу.

Вывод. Надо больше спать, и не париться с асьмой.
Я не понял, что вы за странный код вы написали. Вот что получается, если просто дизассемблировать начальный код.

push ebp
mov ebp, esp
mov eax, [ebp+0Ch]
add eax, [ebp+8]
pop ebp
retn
тут 32 битные опкоды, а вы пытаетесь дизасмить их как 16 =)
Ну, собственно говоря, вот в этом то и были проблемы 8-)
Intel'овские опкоды вообще-то имеют переменную длину. Например, «retn» это C9, а «mov ebp, esp» — 89E5.
UFO just landed and posted this here
есть мнение, что за такое в продакшн коде надо отрывать руки по самые яйца
Зря человека минусуете, очень правильную мысль hellraiser09 говорит. Полностью согласен!
По самые яйца включительно.
В продакшене за многое нужно отрывать и руки и яйца, но давайте не будем забывать что это просто кусок кода, который демонстрирует просто определенную возможность. И это интересно, это любопытно, это позволяет залезть за кулисы… Если вы считаете что это бесполезно, значет этот топик не для вас. Я не говорю что при этом вы плохой программист, просто вам достаточно того уровня на котором пишут в продакшене. Для таких как автор топика и я, этого не достаточно, хочется понять все, а не «напишите вот это, а получите это». И знать как использовать это совсем не значит использовать на практике!
Возможность, простите, чего? Программировать в машинных кодах и вызывать это из C/C++?

Зачем тогда вообще нужны компиляторы, линковщики и пр.? Пишите сразу EXE файл в блокноте.
Я же сказал, знать, не значит использовать. Это в образовательных целях. Вам это не интересно? Значит это не ваше :)
Все зависит от того, насколько глубоко человек хочет вникнуть в систему. Для кого то достаточно понять фреймворк, для кого-то нужно залезть поглубже в API, еще кому то нужно расковырять все до уровня работы с памятью, регистрами, стеком, умения понимать дизасемблированный код, узнавать свои переменные/функции/классы в окне Memory View. Каждый в итоге займет свою нишу в IT сфере. Кто-то будет рубить капусту в C# выпуская бизнес приложения по пять штук в месяц. И этому человеку абсолютно пофиг что где то в С++ можно сделать грязный хак… А кто-то будет просто торчать от того что он нашел новый грязный хак, не потому что он будет теперь писать ехе файлы в блокноте, а потому что система стала ему на один шаг ближе. Он чему то научился, и что-то что ему было не знакомо еще пару минут назад, теперь стало его достоянием. Тот первый никогда не поймет радости второго. Автор топика написал что это бесполезный код. Он не будет использовать его не только в продакшене, а и вообще где нибудь… Он просто открыл для себя нечто новое, и ему это так понравилось, что он решил поделиться этим с остальными… И сдесь есть люди которым это было адресовано, а для остальных это всего лишь бесполезно прочитаный топик
Спасибо за поддержку, я бы не смог сказать лучше :)
Я считаю, что это не только интересно, но и что такие штучки помогают понять сложное.
Если человек хочет глубже понять систему, то нужно начинать не сверху — от C++ к ассемблеру и машинному коду, а наоборот — изучить ассемблер, понять, зачем и кому он нужен, после чего перейти на C, оценить преимущества, которые даёт структурированный язык с переменными и организованной кучей, после чего перейти к ООП, интерпретируемым и скриптовым языкам, фреймворкам, постепенно повторяя в своём развитии, в некотором роде, всё развитие компьютерной эры.

А подобные «открытия» «сверху вниз», возможно, интересны, но не несут ничего полезного в качестве знаний, так как не имеют под собой базиса.
Ходить вы начинали учится с 100 киломметровых марафонов? :)
Для начала как раз и сойдут скриптовые языки и фреймворки, что бы человек не вдаваясь в подробности просто проникся самой сутью программирования, алгоритмов, логики и т.д. Для начала нужно просто вникнуть что такое переменная, прочувствовать ее сущность, потом разобратся во всех тонкостях указателей и ссылок на нее, понять разницу между l и r-значениями… И лишь потом изучать по какому смещению и в каком сегменте ее искать (для начала изучив что такое смещение и сегмент)
Зачем начинающему программисту, который еще толком не может объявить эту самую переменную, знать такие тонкости? :)
З.Ы. А кто-то интересовался как компилятор физически реализует ссылку на переменную? :) Нет? рекомендую посмотреть. Мне было довольно интересно узнать как это делает MSVC++ :)
Перед тем, как учиться водить машину, человек учится ходить. А если человек сразу сел за руль, для него будет открытием, что, оказывается, можно открыть дверь и пойти пешком.
Очень бы интересно было посмотреть на человека, который бы стал использовать такое в продакшне.
Как и следовало ожидать:
Unhandled exception at 0x0012ff54 in FunctionWithoutDefinition.exe: 0xC0000005: Access violation.
0x0012ff54 — адрес массива с

Так что хак немного устарел.
Полагаю, что код очень платформозависим. У меня MinGW, виста.
Включите DEP. Я думаю у вас тоже упадёт.
Включил на 2 компах. На ноуте, с аппаратной поддержкой, упало. На старом компе нет.
Автор видимо эзотерик, вообще есть же среди программеров старая развлекуха «написать дважды два в тысячу строчек», тут другой вариант: сделать простую вещь самым неочевидным из возможных способов. Думаю как задачка она хороша, а вот как реальный боевой код за такое по рукам дают
short main[] = {
	277, 04735, -4129, 25, 0, 477, 1019, 0xbef, 0, 12800,
	-113, 21119, 0x52d7, -1006, -7151, 0, 0x4bc, 020004,
	14880, 10541, 2056, 04010, 4548, 3044, -6716, 0x9,
	4407, 6, 5568, 1, -30460, 0, 0x9, 5570, 512, -30419,
	0x7e82, 0760, 6, 0, 4, 02400, 15, 0, 4, 1280, 4, 0,
	4, 0, 0, 0, 0x8, 0, 4, 0, ',', 0, 12, 0, 4, 0, '#',
	0, 020, 0, 4, 0, 30, 0, 026, 0, 0x6176, 120, 25712,
	'p', 072163, 'r', 29303, 29801, 'e'
};


отсюда
Ха-ха, выходит на Сях вполне можно реализовать метапрограммитрование — подавать на вход правильные коды и исполнять произвольные функции
Хехе. На самом деле, то что вы сейчас сказали, звучит странно.

Представьте себе человека, который увидел впервые в жизни вафельницу и говорит: «Ух ты, она греется! Выходит на ней можно сделать блины, или вафли!»

Не в обиду.

Просто раньше это делали очень часто. Для написания эмуляторов, в основном.

У Майкрософта на смену с++ пришёл С#, в котором, слава богу, позапрещали 90% хаков и подковырок. Как-то жить стало проще, консервативнее, чтоли 8-)
Не кроссплатформенно… Это далеко не тоже самое, что eval, хотя да можно конечно в рантайме дёргать компилятор, а потом егоные объектные файлы в память грузить и кастовать в нужные вещи и юзать, но только это того не стоит.
UFO just landed and posted this here
UFO just landed and posted this here
А че это вы фразы из контекста вырываете? а? Куда дели окончание
>но только это того не стоит.
Я лишь утверждаю, что даже на C++ можно сделать аналог evalа, но из за сложностей языка он будет весьма страшным и неудобным
UFO just landed and posted this here
1. Для опенсорс программ это не проблема
2. Видимо вы не врубились в суть происходящего: дергается внешний процесс, который запускает сборку, по его завершению загружаем в память объектный файл, как массив байт, он будет выглядеть примерно в духе того, что тут было представлено, а дальше просто делаем тоже самое, что было сделано в статье.
Ну, это старый трюк…
Когда-то, в начале 80-х, мы писали для 8080 в кодах. Потом на ассемблере. Потом PL-M и C.
Тал вот иногда было проще прямо кодами, как у Вас. Только удобнее в шеснадцатиричном формате писать — потом читать легче…
Вы глупый человек :)
UFO just landed and posted this here
Аналогично — машинные коды надо писать байтами через 0x запись, поскольку ни один вменяемый человек не привык дизассемблировать в уме из десятичной записи. Ну и вообще для подобных вещей ассемблерные вставки придуманы. Ну и без очень серьезных оснований их использовать разумеется не стоит.
Это не совсем бесполезный трюк. С помощью него можно делать в определенных случаях «санки». Например, когда НЕЧТО хочет от нас callback функцию без user-аргументов, а мы хотим чтобы эта callback функция была связана с экземпляром класса в памяти. Делаем указанным выше способом функцию-«санки», которая внутри сгенерированного кода содержит указатель на класс и при вызове вызывает уже его метод.

P.S. Это я к вопросу о ТЕОРЕТИЧЕСКОЙ применимости. В коммерческих продуктах лучше так не делать :).
UFO just landed and posted this here
Можете подкинуть пример, как это выглядит?
В Delphi так реализованы оконные процедуры.
Ептыть, терь понятно, почему она настолько сильно к x86 гвоздями прибита %)
Что-то мне кажется такой подход из серии «А не запутаться ли нам вместе со всеми».
Действительно, лучше не применять, а на собеседованиях бурно негодовать.
Ну а кто же виноват в том, что c++ немного статично типизирован, с отсутствием замыканий и нормальных делегатов? :(. Вот добавят чуток в c++0x — должно стать попроще.
Забавный вариант встроить ассемблер в программу. Надо напугать одногруппников в универе)))
Да, ещё помно, когда был Borland C первых версий, там прямо в хелпе писали хак, которым можно поменять константу… Только зачем, я так и не понял, ни тогда, ни 10 лет спустя.
Дзен-программирование.
Если ты понял, как менять константы, ты на верном пути.
Если ты понял, зачем менять константы, ты достиг просветления.
Если ты начал применять это на практике то достиг нирваны?
Скорее, пиздюлей от начальника достигнешь.
Хорошо, какая мораль? С — это не игрушка и в ней можно играть с памятью на прямую? Ну так это уже не новость :)
скорее о том как работают уязвимости типа «ошибка переполнения с возможностью выполнения произвольного кода»
Не вижу никакой связи. Здесь просто человек присвоил адрес указателю на функции грязным хаком, все.
Здесь просто человек присвоил адрес указателю на функции грязным хаком, все.
Конкретно здесь — да. Но эксплоиты ошибок переполнения стека, например, именно так и работают — выполняют код из сегмента данных. К счастью, на этот случай у нас уже есть DEP.
Мда, я таким в паскале занимался еще — по адресу влезал в функцию, искал ее пределы( а потом сохранял данные для повторного использования)
Тогда на меня снизошло откровение, которое не отпускает до сих пор — а нахнера тогда скриптовые языки придумали?
пс:(и кстати да — DEP рулит )
Ну кстати красивее было бы в виде строки (и короче). Единственный минус — автонультерминатор (хотя в данном случае это не принципиально, т.к. он за ретом)

char c[] = "\xFF\x55\xFF\x89\xFF\xE5\xFF\x8B\x00\x45\x00\x0C\x00\x03\x00\x45\x00\x08\x00\x5D\xFF\xC3\xFF\x90";
Да, верно, однако мне не понятно зачем нужны "\xFF" и "\x00". С ними отказывается работать. Кстати, весь пример тоже може в одну строчку:

cout << ((int (*)(int, int))"\x55\x89\xE5\x8B\x45\x0C\x03\x45\x08\x5D\xC3\x90")(2,3);
Только когда будете показывать другим — уберите nop (\x90) в конце строки.
Хи клевый хак. Хотя в современной ОС с процом поддерживающим бит исполнения страницы не прокатит.
Не прокатит. ПОКА еще по умолчанию в большинстве OS DEP включен только для системных служб, но все идет к тому что скоро такое заигрывание с кодом накроется медной хозяйственной утварью ^_^.
UFO just landed and posted this here
Между прочим, при соблюдении нужных процедур вынесения в HAL и тп. этот прием может использоваться для частичной самокомпиляции в целях «ушоб побыстрее, ежели компилятор не сильно оптимизирующий», и лет 10 назад даже в продакшн коде так делали иногда.

А в сегодняшних реалиях — присоединяюсь к ораторам с точкой зрения «руки оторвать за такое», более быстрая машина стоит дешевле времени программистов на веселую отладку таких перлов :)
Мало того что опасный бред, так он ещё и (вполне предсказуемо) не работает:

(honeyman@honeyman-lap:pts/4, 13:55:17)
139:/tmp:$ cat > 1.cpp
#include using namespace std;
typedef int (*pf)(int, int);
char c[] = {85,-119,-27,-117,69,12,3,69,8,93,-61,-112};
pf sum = (pf)c;
int main(void) {cout << sum(2,3);}
(honeyman@honeyman-lap:pts/4, 13:55:34)
/tmp:$ g++ 1.cpp -o 1.exe; ./1.exe
zsh: segmentation fault ./1.exe

[офф]а зачем вы в никсах к бинарнику приписываете расширение .exe??[/офф]
Ну, не все же в курсе, что такое a.out.
Это, конечно, занятно, но не более. Так вообще лучше нигде и никогда не писать, если только не стоит задача максимально запутать чтение кода. Код к тому же совершенно непортируем.
UFO just landed and posted this here
Хз…

pf sum = (pf)c;

-> «error C2440: 'type cast': cannot convert from 'char [12]' to 'pf'»
Visual Studio? У меня MinGW(GCC)
Просто решается если сначала привести к указателю на void:
pf sum = (pf)(void*)c;
Тсс, только не говорите про шелдкоды! А то топик сползёт к обсуждению написания эксплоитов.
Sign up to leave a comment.

Articles