InterViМне предлагали с Java начинать, я не стал, к счастью. Тогда я подумал, что это всё слишком сложно для меня - всё это программирование. Но потом мне понадобились программы (программки) и я стал изучать QBasic, потому что он был довольно простым и мощным. И вот после первых программок я понял, что мне нужно больше средств в языке, и стал изучать C++. Конечно, я круто обломился на нём, потому что не мог и шагу сделать, чтобы не увязнуть. До сих пор помню, как пытался вывести в консоль что-то через направление в cout. Купил книжку, это ничего не дало. Книжка была от Шилдта, а он, как известно, песатиль, а не программист. Поэтому три тонны мешанины из C и C++ в его исполнении убедили меня, что я не потяну C++. Вот так и было всё на уровне QBasic, пока я не прогулял геометрию и вместо неё не пошёл с другом на его занятия по Borland C++, где изучали printf() и давали задания типа нарисовать часы со стрелками и цветной флаг России на коричневой палке. И тут я понял, что я могу это делать, и стал прогуливать геометрию уже планомерно, что даже привело к диалогу типа “если нравится программирование, то переводитесь туда куда-нибудь, нельзя же прогуливать”. Когда пары закончились (их было немного, но они сделали своё дело), я поставил линукс осенью и спустя какое-то время произвольно стал заниматься по C, потому что там просто всё для этого было. Сделать программу, ничего не устанавливая, было проще простого. Потом знаний стало не хватать и я купил книжку K&R2, заказав её через Интернет по почте на Озоне. А в книжке оказалось всё совсем просто, потому что написана она и профессионалом, и программистом. Поэтому там было всего мало и только по делу. Так я её прочитал всю до системных вызовов и стал писать уже программки на C; и первой рабочей программкой стала программка для опроса модема. Модем был под столом и часто я забывал его включать, и чтобы выяснить, включен он или нет, нужно было заглядывать под стол. То есть либо ты заглядываешь под стол, либо ты открываешь браузер и у тебя нет инета. Поэтому эта программа запрашивала состояние модема и возвращала результат типа да/нет. А вот программу эту запускала кнопка на рабочем столе, которая в случае “нет” выводила сообщение, что модем выключен. Так я перестал заглядывать под стол, а просто нажимал кнопку на панели в KDE3 и смотрел.
Я с Java начинал, которую тоже ругают. Питонисты за синтаксис, сишники за потребление ресурсов.
Прошло несколько лет всяких изучений.
А вчера я писал патчер. Ну, как сказать писал, написал-то я его уже больше месяца назад. Вчера просто заполнил его новыми данными и запустил make, которая по самодельному Makefile'у собирает всё по-быстрому. (Основы make были изучены через команду info в линуксе. Она отличается от манов большей разжёванностью материала. В ней же я полностью изучил sed.) Патчер для отключения защиты в тренировочном CrackMe от FaNt0m'а. Вчера сначала написал патчер для одного CrackMe, а потом написал кейген для другого CrackMe.
Чем отличаются патчер и кейген: для патчера нужно просто найти место ненужной команды и стереть её или заменить адрес, куда программа прыгает - и всё; а для кейгена нужно проанализировать, как генерится серийник, какими вычислениями и над чем, и всё это выделить и перевести с ассемблера на C, причём перевод не прямой, так как многих прямых конструкций из ассемблера в C не существует, либо они некрасивы и считаются говнокодом.
Кейген - тоже заготовка, написал я его ещё где-то на первых CrackMe. (В программировании это называется повторное использование кода или “реюз”.) Чтобы сделать новый кейген, ты просто туда, в функцию generate() вставляешь другое содержимое и так получаешь новый алгоритм для генерации ключа. А дальше он всё спрашивает и отвечает по-старому, меняется только алгоритм генерации.
Если с патчером понятно - нужно отыскать место и вставить байты, то кейген требует перевода ассемблерного кода на C. Можно, конечно, сделать по-дурацки - типа использовать ассемблерную вставку и просто скопировать ассемблерный код в сишный исходник, но не везде тогда он будет собираться, так как в разных компиляторах используются разные диалекты ассемблера. Поэтому мы простых путей не ищем, так как знаем, что можем напороться на зависимость от системы или вообще от компилятора.
Вот это получилось
char *generate(const char *src, char *dst)
{
char *dst_start = dst;
unsigned long a, b, c, q, r, ah, al;
c = strlen(src);
c++;
while (c > 0) {
b = 0x42;
b += c;
a = *src;
src++;
a ^= b;
a <<= 5; /* must be rolled bits */
a >>= 2;
b = (b & (~0 << 8)) | 0x10;
q = a / (b & 0xff);
r = a % (b & 0xff);
a = r << 8 | q;
while (1) {
ah = (a >> 8 & 0xff);
al = a & 0xff;
a = (ah + 0x30) << 8 | al;
if (ah + 0x30 > 0x40)
a = (ah + 0x30 + 7) << 8 | al;
*dst = (a >> 8 & 0xff);
dst++;
if (al < 0x10)
a = a << 8 | 0x11;
else
break;
}
c--;
}
*dst = '\0';
return dst_start;
}
Это нужно было залезть внутрь dll-библиотеки, потому что в этом CrackMe было два усложнения: 1) экзешник и dll'шка упакованы с помощью упаковщика upx; 2) а сам серийник вычислялся не в экзешнике, а внутри dll'шки. А как в неё залезть? Вот с этим я и столкнулся. Её нужно было запустить и пройти сначала для простого ника, чтобы всё отследить. Потом это всё нужно было выделить и перевести в псевдокод. А потом уже псевдокод адаптировался под сишную запись кода. И потом уже по этому псевдокоду писался сишный код, и ещё было непонятно, правильно ли он работает. На двух длинных именах проверил - всё пашет. (Благо в своё время я изучил сишные тесты в виде cunit, а для совсем уж сложных случаев сделал питоновские юнит-тесты для любой программы на базе ввода/вывода через stdin и stdout.) Даже если бы правильно не работало, я мог бы прижать тестами функцию и исправлять её, пока не исправится.
Это была последняя из восьми CrackMe от FaNt0m'а, осталось ещё около сотни CrackMe от других ребят.
Что скажу: хоть и мало прошёл, но это уже дало возможность менять программы в винде, так как пришлось покопаться и с софтом для этого, и с переводом на нормальный язык. По крайней мере вчера я подключился к user32.dll и вызвал MessageBoxA, что дало окно из ниоткуда с текстом и кнопками.
Если ближе к питону
А вот что делать, если в Firefox'е открыто много-много вкладок, но нужно отойти от компа или припрятать их на потом и заняться другим делом в Firefox'е?
Поначалу я просто килял Firefox (через killall) и он при следующем запуске предлагал восстановить вкладки, открытые последними. Так можно было отходить от компа, но тогда в Firefox'е ничего нельзя было делать, потому что при новом запуске предлагались вкладки, которые нужно было либо снова восстановить, либо выбросить.
Чтобы решить это, можно было бы качать какой-нибудь плагин к Firefox'у, а потом ещё следить, работает ли он так, как надо. Но мы ведь знаем, что чужие плагины либо сохраняют не так, либо сохраняют не туда, либо сохраняют так, что без плагина потом в этом не разберёшься. А уж о редактировании сохранённого снаружи (типа удалить половину) и мечтать не приходится. Чаще это будет сводится к восстановлению всего, удалению ненужного и новому сохранению всего - к неповоротливой фигне.
Что делать, надо лезть в Firefox и искать, где он там понимает, что у него были открыты вкладки до кила. К счастью, в Firefox'е всё хранится в json'е и вкладки хранятся в структуре, где лежит вся информация об окнах и табах в этих окнах. Json'ить удобнее, конечно, в питоне, ибо в лине нет таких программ для обработки json'а, которые были бы везде во всех репозиториях, какая бы система ни была.
Так родился скрипт вытаскивания текущих вкладок и сохранения их в файл
[guest@localhost ~]$ cat /usr/local/bin/ffurls.py
#!/usr/bin/env python3
# Преобразует открытые в Firefox ссылки
# в названия и ссылки в текстовом файле.
import argparse
import json
def get_js_data(fname):
with open(fname, encoding='utf-8') as fin:
return json.load(fin)
def get_ff_tu(data):
for win in data['windows']:
for tab in win['tabs']:
for entry in tab['entries']:
yield entry['title'], entry['url']
def strings_to_file(fname, seq):
with open(fname, 'w', encoding='utf-8') as fout:
for i in seq:
print(i, file=fout)
def convert_ff_to_txt(ifname, ofname):
ffurls = get_ff_tu(get_js_data(ifname))
tustrs = ('{}\n{}'.format(t, u) for t, u in ffurls)
strings_to_file(ofname, tustrs)
def get_prog_args():
desc = """
Converts Firefox session json file to
a text file with titles and urls.
"""
parser = argparse.ArgumentParser(description=desc)
parser.add_argument('ifname',
help='input Firefox session file (sessionstore.js)')
parser.add_argument('ofname', help='output urls text file')
return parser.parse_args()
def main():
args = get_prog_args()
ifname = args.ifname
ofname = args.ofname
convert_ff_to_txt(ifname, ofname)
if __name__ == '__main__':
main()
[guest@localhost ~]$
(Это специально сделано на случай сюрпризов от Firefox'а в новых версиях. Бывало у него такое, что менялись файлы и директории как по названиям, так и по расположению.)
Так что управлять таким файлом неудобно.
Поэтому делаем второй скрипт, уже конкретный
[guest@localhost ~]$ cat .env/scripts/ffurls.sh
#!/bin/bash
idir="$(ls -d $HOME/.mozilla/firefox/*.default/sessionstore-backups)"
ifile="recovery.js"
odir="$HOME/Downloads"
ofile="firefox.txt"
ipath="$idir/$ifile"
opath="$odir/$ofile"
if [ -e "$opath" ]; then
n=1
while [ -e "$opath" ]; do
opath="$odir/${ofile}_$n"
((n++))
done
fi
ffurls.py "$ipath" "$opath"
exit 0
[guest@localhost ~]$
Так что только соместное использование языков даёт полную силу. В одном языке может не быть чего-то, что есть в другом языке. И если ты знаешь только один язык, это может привести к тому, что ты будешь писать что-то простое каким-то сложным образом. И всё только потому, что не знаешь правильного инструмента.