Русский
Русский
English
Статистика
Реклама

Flair

IDA Pro работа с библиотечным кодом (не WinAPI)

01.07.2020 20:13:32 | Автор: admin

Всем привет,



При работе в IDA мне, да и, наверняка, вам тоже, часто приходится иметь дело с приложениями, которые имеют достаточно большой объём кода, не имеют символьной информации и, к тому же, содержат много библиотечного кода. Зачастую, такой код нужно уметь отличать от написанного пользователем. И, если на вход библиотечного кода подаются только int, void *, да const char *, можно отделаться одними лишь сигнатурами (созданные с помощью FLAIR-утилит sig-файлы). Но, если нужны структуры, аргументы, их количество, тут без дополнительной магии не обойдёшься В качестве примера я буду работать с игрой для Sony Playstation 1, написанной с использованием PSYQ v4.7.


Дополнительная магия


Представим ситуацию: вам попалась прошивка от какой-нибудь железки. Обычный Bare-metal ROM (можно даже с RTOS). Или же ROM игры. В подобных случаях, скорее всего, при компиляции использовался какой-то SDK/DDK, у которого имеется набор LIB/H/OBJ файлов, которые вклеиваются линкером в итоговый файл.


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


  1. Взять все lib/obj-файлы, и создать из них сигнатуры (или набор сигнатур). Это поможет нам отделить статически влинкованный библиотечный код.
  2. Взять все h-файлы и создать из них библиотеку типов. Эта библиотека хранит не только типы данных, но и информацию об именах и типах аргументов функций, в которых объявленные типы используются.
  3. Применить сигнатуры, чтобы у нас определились библиотечные функции и их имена.
  4. Применить библиотеки типов, чтобы применить прототипы функций и используемые типы данных.

Создаём sig-файлы


Для создания файла сигнатур необходимо воспользоваться набором FLAIR-утилит, доступных лицензионным пользователям IDA. Список необходимых утилит следующий:


  • pcf LIB/OBJ-parser, создаёт PAT-файл из COFF-объектных файлов
  • pelf LIB/OBJ-парсер, создаёт PAT-файл из ELF-файлов (Unix)
  • plb LIB/OBJ-parser, создаёт PAT-файл из OMF-объектных файлов
  • pmacho MACH-O-парсер, создаёт PAT-файл из MACH-O-файлов (MacOS)
  • ppsx OBJ-парсер, создаёт PAT-файл из библиотечных файлов PSYQ
  • ptmobj OBJ-парсер, создаёт PAT-файл из библиотечных файлов Trimedia
  • sigmake конвертирует ранее созданный PAT-файл в SIG-файл, перевариваемый IDA

В моём случае это ppsx. Собираю bat-файл, в котором перечисляю все lib- и obj-файлы, и добавляю к каждой строке вызов ppsx, чтобы получилось формирование итогового PAT-файла. Получилось следующее содержимое:


run_47.bat
@echo offppsx -a 2MBYTE.OBJ psyq47.patppsx -a 8MBYTE.OBJ psyq47.patppsx -a LIBAPI.LIB psyq47.patppsx -a LIBC.LIB psyq47.patppsx -a LIBC2.LIB psyq47.patppsx -a LIBCARD.LIB psyq47.patppsx -a LIBCD.LIB psyq47.patppsx -a LIBCOMB.LIB psyq47.patppsx -a LIBDS.LIB psyq47.patppsx -a LIBETC.LIB psyq47.patppsx -a LIBGPU.LIB psyq47.patppsx -a LIBGS.LIB psyq47.patppsx -a LIBGTE.LIB psyq47.patppsx -a LIBGUN.LIB psyq47.patppsx -a LIBHMD.LIB psyq47.patppsx -a LIBMATH.LIB psyq47.patppsx -a LIBMCRD.LIB psyq47.patppsx -a LIBMCX.LIB psyq47.patppsx -a LIBPAD.LIB psyq47.patppsx -a LIBPRESS.LIB psyq47.patppsx -a LIBSIO.LIB psyq47.patppsx -a ashldi3.obj psyq47.patppsx -a ashrdi3.obj psyq47.patppsx -a CACHE.OBJ psyq47.patppsx -a clear_cache.obj psyq47.patppsx -a CLOSE.OBJ psyq47.patppsx -a cmpdi2.obj psyq47.patppsx -a CREAT.OBJ psyq47.patppsx -a ctors.obj psyq47.patppsx -a divdi3.obj psyq47.patppsx -a dummy.obj psyq47.patppsx -a eh.obj psyq47.patppsx -a eh_compat.obj psyq47.patppsx -a exit.obj psyq47.patppsx -a ffsdi2.obj psyq47.patppsx -a fixdfdi.obj psyq47.patppsx -a fixsfdi.obj psyq47.patppsx -a fixtfdi.obj psyq47.patppsx -a fixunsdfdi.obj psyq47.patppsx -a fixunsdfsi.obj psyq47.patppsx -a fixunssfdi.obj psyq47.patppsx -a fixunssfsi.obj psyq47.patppsx -a fixunstfdi.obj psyq47.patppsx -a fixunsxfdi.obj psyq47.patppsx -a fixunsxfsi.obj psyq47.patppsx -a fixxfdi.obj psyq47.patppsx -a floatdidf.obj psyq47.patppsx -a floatdisf.obj psyq47.patppsx -a floatditf.obj psyq47.patppsx -a floatdixf.obj psyq47.patppsx -a FSINIT.OBJ psyq47.patppsx -a gcc_bcmp.obj psyq47.patppsx -a LSEEK.OBJ psyq47.patppsx -a lshrdi3.obj psyq47.patppsx -a moddi3.obj psyq47.patppsx -a muldi3.obj psyq47.patppsx -a negdi2.obj psyq47.patppsx -a new_handler.obj psyq47.patppsx -a op_delete.obj psyq47.patppsx -a op_new.obj psyq47.patppsx -a op_vdel.obj psyq47.patppsx -a op_vnew.obj psyq47.patppsx -a OPEN.OBJ psyq47.patppsx -a PROFILE.OBJ psyq47.patppsx -a pure.obj psyq47.patppsx -a read.obj psyq47.patppsx -a shtab.obj psyq47.patppsx -a snctors.obj psyq47.patppsx -a SNDEF.OBJ psyq47.patppsx -a SNMAIN.OBJ psyq47.patppsx -a SNREAD.OBJ psyq47.patppsx -a SNWRITE.OBJ psyq47.patppsx -a trampoline.obj psyq47.patppsx -a ucmpdi2.obj psyq47.patppsx -a udiv_w_sdiv.obj psyq47.patppsx -a udivdi3.obj psyq47.patppsx -a udivmoddi4.obj psyq47.patppsx -a umoddi3.obj psyq47.patppsx -a varargs.obj psyq47.patppsx -a write.obj psyq47.patppsx -a LIBSND.LIB psyq47.patppsx -a LIBSPU.LIB psyq47.patppsx -a LIBTAP.LIB psyq47.patppsx -a LOW.OBJ psyq47.patppsx -a MCGUI.OBJ psyq47.patppsx -a MCGUI_E.OBJ psyq47.patppsx -a NOHEAP.OBJ psyq47.patppsx -a NONE3.OBJ psyq47.patppsx -a NOPRINT.OBJ psyq47.patppsx -a POWERON.OBJ psyq47.pat

LIBSN.LIB файл имеет формат, отличный от остальных библиотек, поэтому пришлось разложить его на OBJ-файлы утилитой PSYLIB2.EXE, которая входит в комплект PSYQ. Запускаем run_47.bat. Получаем следующий выхлоп:


run_47.bat output
2MBYTE.OBJ: skipped 0, total 18MBYTE.OBJ: skipped 0, total 1LIBAPI.LIB: skipped 0, total 89LIBC.LIB: skipped 0, total 55LIBC2.LIB: skipped 0, total 50LIBCARD.LIB: skipped 0, total 18LIBCD.LIB: skipped 0, total 51LIBCOMB.LIB: skipped 0, total 3LIBDS.LIB: skipped 0, total 36LIBETC.LIB: skipped 0, total 8LIBGPU.LIB: skipped 0, total 60LIBGS.LIB: skipped 0, total 167LIBGTE.LIB: skipped 0, total 535LIBGUN.LIB: skipped 0, total 2LIBHMD.LIB: skipped 0, total 585LIBMATH.LIB: skipped 0, total 59LIBMCRD.LIB: skipped 0, total 7LIBMCX.LIB: skipped 0, total 31LIBPAD.LIB: skipped 0, total 21LIBPRESS.LIB: skipped 0, total 7LIBSIO.LIB: skipped 0, total 4ashldi3.obj: skipped 0, total 1ashrdi3.obj: skipped 0, total 1CACHE.OBJ: skipped 0, total 1clear_cache.obj: skipped 0, total 1CLOSE.OBJ: skipped 0, total 1cmpdi2.obj: skipped 0, total 1CREAT.OBJ: skipped 0, total 1ctors.obj: skipped 0, total 0divdi3.obj: skipped 0, total 1dummy.obj: skipped 0, total 1Fatal: Illegal relocation information at file pos 0000022Deh_compat.obj: skipped 0, total 1exit.obj: skipped 0, total 1ffsdi2.obj: skipped 0, total 1fixdfdi.obj: skipped 0, total 1fixsfdi.obj: skipped 0, total 1fixtfdi.obj: skipped 0, total 0fixunsdfdi.obj: skipped 0, total 1fixunsdfsi.obj: skipped 0, total 1fixunssfdi.obj: skipped 0, total 1fixunssfsi.obj: skipped 0, total 1fixunstfdi.obj: skipped 0, total 0fixunsxfdi.obj: skipped 0, total 0fixunsxfsi.obj: skipped 0, total 0fixxfdi.obj: skipped 0, total 0floatdidf.obj: skipped 0, total 1floatdisf.obj: skipped 0, total 1floatditf.obj: skipped 0, total 0floatdixf.obj: skipped 0, total 0FSINIT.OBJ: skipped 0, total 1gcc_bcmp.obj: skipped 0, total 1LSEEK.OBJ: skipped 0, total 1lshrdi3.obj: skipped 0, total 1moddi3.obj: skipped 0, total 1muldi3.obj: skipped 0, total 1negdi2.obj: skipped 0, total 1Fatal: Illegal relocation information at file pos 0000013Dop_delete.obj: skipped 0, total 1op_new.obj: skipped 0, total 1op_vdel.obj: skipped 0, total 1op_vnew.obj: skipped 0, total 1OPEN.OBJ: skipped 0, total 1PROFILE.OBJ: skipped 0, total 1pure.obj: skipped 0, total 1Fatal: Unknown record type 60 at 0000015Fshtab.obj: skipped 0, total 0Fatal: Unknown record type 60 at 000000EESNDEF.OBJ: skipped 0, total 0SNMAIN.OBJ: skipped 0, total 1SNREAD.OBJ: skipped 0, total 1SNWRITE.OBJ: skipped 0, total 1trampoline.obj: skipped 0, total 0ucmpdi2.obj: skipped 0, total 1udiv_w_sdiv.obj: skipped 0, total 1udivdi3.obj: skipped 0, total 1udivmoddi4.obj: skipped 0, total 1umoddi3.obj: skipped 0, total 1varargs.obj: skipped 0, total 1Fatal: Unknown record type 60 at 00000160LIBSND.LIB: skipped 0, total 223LIBSPU.LIB: skipped 0, total 126LIBTAP.LIB: skipped 0, total 1LOW.OBJ: skipped 0, total 1Fatal: can't find symbol F003MCGUI_E.OBJ: skipped 0, total 1NOHEAP.OBJ: skipped 0, total 1NONE3.OBJ: skipped 0, total 1NOPRINT.OBJ: skipped 0, total 1POWERON.OBJ: skipped 0, total 1

Видим некоторое количество ошибок парсинга, но, в тех файлах всего 1 сигнатура (total 1), поэтому, думаю, что это не критично. Далее преобразовываем PAT-файл в SIG-файл:


sigmake -n"PsyQ v4.7" psyq47.pat psyq47.sigpsyq47.sig: modules/leaves: 1345/2177, COLLISIONS: 21See the documentation to learn how to resolve collisions.

В итоге получаем следующий список файлов:


  • psyq47.err его не трогаем
  • psyq47.exc его нужно будет отредактировать
  • psyq47.pat его тоже не трогаем

Открываем на редактирование .exc-файл. Видим:


;--------- (delete these lines to allow sigmake to read this file); add '+' at the start of a line to select a module; add '-' if you are not sure about the selection; do nothing if you want to exclude all modules

Если удалить ---------, всё, что содержится в файле ниже, будет учитываться. Давайте взглянем на то, что там есть. Вот пример:


CdPosToInt                                          60 A21C 0000839001008690022903008010050021104500401002000F00633021104300DsPosToInt                                          60 A21C 0000839001008690022903008010050021104500401002000F00633021104300

Видим, что две функции имеют одну и ту же сигнатуру, и нам нужно выбрать, какую из них использовать. Для этого слева, рядом с именем нужной функции ставим +. Я выбираю первую. То же самое повторяем с остальными строками.


В итоге, если всё сделано правильно, получаем SIG-файл. Его нужно положить в соответствующую папку в каталоге установка IDA.


Создаём til-файлы


Эти файлы нужны для хранения информации о типах, об аргументах функций и т.п. По-умолчанию. Создаются они с помощью утилиты tilib, которую необходимо положить в каталог с Идой (ей, почему-то, нужен ida.hlp файл).


Данной утилите нужно скормить include-файлы вашего SDK/DDK. При том парсинг этой утилитой отличается от такового средством "Parse C header file..." в самой IDA. Вот описание из readme:


Its functionality overlaps with "Parse C header file..." from IDA Pro.
However, this utility is easier to use and provides more control
over the output. Also, it can handle the preprocessor symbols, while
the built-in command ignores them.

У этой утилиты есть один нюанс: она по умолчанию использует режим, когда символы заманглены, либо имеют стоящее в начале имени нижнее подчёркивание. В случае работы со статически влинкованным кодом этот режим нужно выключить флагом -Gn.


По-умолчанию, данная утилита принимает на вход только один include-файл. Если же файлов много, нужно соорудить include-файл следующего содержания:


#include "header1.h"#include "header2.h"#include "header3.h"// ...

Этот файл передаётся с помощью флага -hFileName.h. Далее, передаём путь поиска остальных header-файлов и получаем следующую командую строку:


tilib -c -Gn -I. -hpsyq47.h psyq47.til

На выходе получаем til-файл, пригодный для использования. Кладём его в соответствующий каталог IDA: sig\mips.


Проверяем результат


Закидываем ROM-файл в IDA, дожидаемся окончания анализа. Далее, необходимо указать компилятор. Для этого заходим в Options->Compiler:



Теперь просто меняем Unknown на GNU C++ (в случае PSX). Остальное оставляем как есть:



Теперь жмём Shift+F5 (либо меню View->Open subviews->Signatures), жмём Insert и выбираем нужный файл сигнатур:



Жмём OK и ждём, пока применяются сигнатуры (у меня получилось 482 распознанных функции).



Далее необходимо применить библиотеку типов (til-файл). Для этого жмём Shift+F11 (либо View->Open subviews->Type libraries) и понимаем, что IDA не может определить компилятор (не смотря на то, что мы его уже указали):



Но это нам всё равно не помешает выбрать til-файл (всё так же, через Insert):



Получаем то, что так хотели:



Теперь декомпилятор успешно подхватывает информацию о типах, и выхлоп становится куда лучше:



P.S.


Надеюсь, эта информация окажется для вас полезной. Удачного реверс-инжиниринга!

Подробнее..

Могут ли нейросети читать чеки?

14.09.2020 14:13:59 | Автор: admin

Вот уже почти три года я скрупулезно записываю все свои доходы и расходы в hledger. Почему именно он? Так сложилось исторически. С наступлением 2018 года я начал все записывать в гугл табличку, а в апреле поехал в Японию. Я сидел в отеле и пытался понять, как мне правильно считать цены в разных валютах, и решил написать что-нибудь на лиспе. И написал. И показал это людям в емаксовом чатике. На что получил ответ "а ведь уже есть готовое" и ссылку на hledger. После чего перетащил в hledger все свои записи из гугловой таблички.

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

И вот изучая в какой-то момент чек от Утконоса, я задумался, а сколько же я трачу на шоколад? Спойлер много. Полез в историю заказов, нашел там старые чеки, переписал записи по категориям. Были просто "expenses:продукты", а стало "expenses:продукты:фрукты" и прочие. Заодно там же обнаружились и кое-какие бытовые товары.

Первое время я занимался этим переписыванием совсем вручную. То есть приходил домой из магазина, смотрел в чек и записывал много строчек. Потом немного автоматизировал сделал в emacs шаблон таблички, где в строках написаны товары с их категориями и ценами, а в последней колонке фильтр по категориям выдает сразу суммы.

Но ведь нейросети и прочий датасатанизм.

В голове возникла идея, что нейросеть вполне в состоянии понять, что "GL.VIL.Апельсины ОТБОР.фас.1кг" это фрукт такой, в отличие от "НИЖ.ХЛ.атон ПОДМОСКОВНЙ 400г" (хлеб, но для этого мне пришлось скопировать это название в гугл).

Задача, которую требуется решить, это очевидно классификация. На вход подаются строчки-названия, а на выходе ожидается категория. Изначально эти категории я буду проставлять вручную, а потом уже только править предсказания. Соответственно программа должна сначала проанализировать текст чека, выделить оттуда названия продуктов и их стоимости, для каждого названия предсказать категорию. Показать мне предсказанное, чтобы я мог поправить. Когда все в порядке, сформировать набор строчек для hledger.

Анализ текста я сделал просто с помощью разбиения на строки, а строк на части. Обычные правила, сформулированные на питоне.

Примерно так
def parse_utk(lines):    while lines:        if lines[0].startswith('Адрес проверки чека'):            break        lines.pop(0)    else:        return    lines.pop(0)    result = []    while lines:        data = lines[0:6]        name, price, lines = lines[0], lines[3], lines[6:]        if name.startswith('ИТОГ'):            break        assert price.startswith('= ')        result.append((name, Decimal(price[2:]))    return result

Не самый изящный код, но свое дело делает. Аналогичным образом у меня сразу были написаны правила для чеков Пятерочки и Магнита.

Дальше начинается более интересная часть, а именно нейросети. Я уже давно считаю, что будущее за character-level моделями, в частности мне нравится flair. Чтобы сделать нейросеть нужны два компонента языковая модель и собственно классификатор. Языковую модель я делаю в двух экземплярах по предсказанию последующих символов и по предсказанию предыдущих. Скрипт для обучения сети сразу написан так, чтобы можно было как обучать сеть с нуля, так и дообучать существующую.

Результат оказался даже лучше, чем я ожидал. Как только я справился со всеми тонкостями "правильно запустить", сеть сразу же стала показывать неплохие результаты. Первые три чека пришлось разметить вручную. В четвертом я исправил примерно половину предсказаний. В девятом из 23 позиций корректно были определены 21. При появлении новых категорий я переобучал сеть заново это занимает минуты.

После этого я уже смог пересмотреть часть записей в hledger.

Я же говорил про шоколад
$ hledger bal продукты -b thisyear -% -S             100.0 %  expenses:продукты              20.6 %    <unsorted>              15.3 %    шоколад               7.9 %    овощи               7.1 %    сыр               6.9 %    заморозка               6.3 %    кофе               5.2 %    колбаса               4.8 %    чай               4.0 %    снеки               3.8 %    яйца               2.7 %    мясо               2.7 %    вода               2.0 %    макароны               2.0 %    фрукты               1.7 %    консервы               1.6 %    алкоголь               1.3 %    бакалея               1.2 %    морепродукты               0.9 %    молоко               0.9 %    крупы               0.5 %    соусы               0.4 %    хлеб               0.2 %    специи

И если уже все работает, наверно можно было бы и успокоиться. Что я вобщем и сделал и пару месяцев просто пользовался. Каждый раз радуюсь, что нейросеть способна отличить соль для ванн (бытовые товары) от обычной поваренной соли.

А потом сходил в Мяснов и у меня появился еще один формат чеков. Тоже довольно легко анализируется, но сомнение в голове возникло. Если уж я использую нейросети, почему бы не использовать их по полной программе?

Это еще одна хотелка я считаю, что предобработка данных для нейросетей должна быть минимальной. Мой код для парсинга чеков это некоторая rule-based предобработка. А вот сможет ли нейросеть просто из полного текста чека (или точнее копи-пасты из личного кабинета) выцепить все те данные, которые нужны классификатору? И если да, то как ей это объяснить.

Это уже другая задача NER. Опять же я беру flair, но языковая модель будет обучена на другом наборе данных (насколько это обязательно? Может быть и классификатор смог бы корректно работать с языковой моделью полного чека). Первый пункт это разметить данные для обучения. Я потратил полдня на код, который позволяет мне в интерактивном режиме проставить IOB-теги для каждого токена в тексте чека А потом понял, что занимался этим зря.

Если можно проанализировать чек с помощью rule-based парсеров, то ими же можно сгенерировать обучающие данные. И еще полдня я убил на это. Впрочем, "убил" это не самое подходящее слово. Все получилось, но результат был так себе. Теггер не мог адекватно понять, почему вот это число это количество товара и потому не нужно, а вот это число это рубли, а значит требуется. Во-вторых, были трудности с адекватным сопоставлением результатов rule-based парсинга и предсказанного теггером.

Еще мне в глаза бросилось некоторое различие между строчками, которые я отдавал в теггер и которые он выдавал в качестве предложений. Как оказалось, дело в токенизаторе. Мне нужен был токенизатор, которые просто разбивает по пробельным символам. В flair есть готовый SpaceTokenizer, который разбивает по пробелам, но у меня бывают также и табы и переносы строк. Пришлось реализовать свой методом "скопировать и поменять одно условие". Результат стал лучше, но все равно далек от желаемого.

Где-то когда-то мне попалась фраза: "Чтобы понять, сможет ли нейросеть решить ту или иную задачу, надо ответить на вопрос, сможет ли ее на основе этих же данных решить специалист". Действительно, из строки "1 107 99" вовсе неочевидно, что это одна бутылка Швепса стоимостью 107 рублей 99 копеек. Да и вообще, сопоставить сам товар с его ценой это тоже не очень тривиально (не во всех форматах чеков цена идет после имени товара). Но как бы читал чек живой человек? Когда я читаю чек, я сначала пытаюсь понять, какие строки относятся к одному товару.

Итак, новая модель. Точнее две. Первая модель должна сделать ровно одну вещь все строчки разбить на "ненужную фигню", "начало блока по товару" и "продолжение блока по товару". То есть NER с ровно одним видом сущностей. Вторая модель внутри блока будет искать сущности "название товара" и "стоимость".

Для первой модели опять же нужен особый токенизатор. Токенами для нее являются строки чека, то есть токенизатор должен делить по символам новой строки (да, еще один свой токенизатор, NewlineTokenizer), а файл с данными для обучения должен допускать пробельные символы в строке с текстом. Ничего страшного, в ColumnCorpus можно задать произвольный разделитель столбцов. Сначала я выбрал \r, но нарвался на то, что питон считает его нормальным окончанием строки. Затем я взял vertical tab (\x0b), но как раз в этот момент мне подсказали о существовании символов RS и US.

Вроде бы что-то стало получаться, но не совсем. Смущало, что в выхлопе присутствует категория O, которая вроде бы и не категория вовсе. Я долго и внимательно изучал логи, пытаясь понять, почему оно анализируется как отдельная категория. А потом задался вопросом, почему при выводе меток, которые проставил теггер, степень достоверности пишется с новой строки.

И вдруг оказалось, что название категории 'entry' не равно строке 'entry'. Это могло означать только одно имена категорий попали в теггер с символами новой строки на конце. То есть вместо тега 'O', который должен обрабатываться особым образом, был тег 'O\n', на который эти привилегии не распространяются.

Еще некоторое время ушло на изучение того, почему вообще это происходит. Как оказалось это баг в flair.

И вот после этого уже стало все очень неплохо. Сначала первый теггер выделяет из чека сегменты по конкретным товарам обучается с нуля быстро, но изначально делает ошибки и включает "Итого" как один из товаров. Потом второй теггер совершенно корректно выделяет продукты.

При наличии работающих rule-based парсеров не очень понятно, зачем такой "умный" парсер мог бы пригодиться. Конечно же, для таких чеков, для которых rule-based парсера еще нет. Чтобы было с чем экспериментировать, пришлось идти и оформлять для себя учетку во Вкусвилле, чтобы получить новый формат чеков, для которого rule-based парсера еще нет.

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

Потом причесать код и опубликовать.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru