Binance (и coinmarketcap) соло с бананом. Шучу, не соло. Geetest на запросах.
Пожалуйста, не проматывайте до середины
В этом материале я разобрал один из своих заказов, то как решал авторизацию (binance и coinmarketcap) и регистрацию (coinmarketcap) аков на запросах, их алгоритмы шифрования, а также реверс geetest slider и его решение на запросах без сервисов (онли нода, онли бас + шифрования).
Однако не все сразу, начнем чуть издалека.
Binance
Шифрования и подписи на авторизации
Понадобился мне как-то бинанс, но копировать куки из браузера было не очень хорошим решением, тк они автоматически умирали через сутки. Да, можно было повесить браузер, делать авториз и работать с ним, но мне захотелось сделать авториз на запросах, потому-что могу. А еще, через браузер, почти всегда, получается какое-то не стабильное говнище.
Для проверки авториза и работы на аке достаточно вот этого. Я ручками взял и удалял все куки, заголовки и тд. Все остальное по сути мусор.
Тоесть достаточно всего одного кукиса p20t, который серв при авторизе дает и csrftoken (который всего-то нужно как-то получить). Хорошо, получается нам нужно узнать от куда берутся всего 2 параметра)
И я сел серчить. Когда я тыкал, пост параметры выглядели следующим образом
Тыкал я, тыкал.. и случайно мне выпала другая капча. Ну раз она на обычном гитесте, который есть на рукапче, сделать авториз выйдет еще проще.
Тоесть вот так выглядят пост данные, девайс инфо - фингер, мыло, пароль, сейф пароль и капча.
Ну оки, запросим тогда капчу. Ключ для капчи получаем так
"https://accounts.binance.com/bapi/composite/v1/public/common/security/gt-code?t="+Date.now()
Апи дока по решению https://rucaptcha.com/api-rucaptcha#solving_geetest
deviceInfo - прост фингер. по итогу оно на беке будет чекать, юа ласт авториза совпадает или нет. Тоесть в случае спиздинга куков будет фрод, тк не тот юа.
password - md5(password+email)
А что если бы было сложнее?
Ну воть, зашел в функцию и опять очевидно что это md5.
safePassword - sha512(password). Да, это тоже сразу очевидно..
Если вдруг, по какой-то причине это не очевидно, тыкаем дальше брек и тут прямо в коде это написано
Потыкать можно туть: https://emn178.github.io/online-tools/md5.html
А потом я еще словил.. validateCodeType: "random". Не пон, защита це или нет, капчу решать я понимаю как, ее слать и буду.
В запросеках есть вот такое. Это обычный uuid. Но эт не обязательно, чисто украшательства для статки бинанса.
x-ui-request-trace: 74274726-6d01-48fc-b885-7eb578840b84
x-trace-id: 74274726-6d01-48fc-b885-7eb578840b84
Генерю так
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-bxxx-xxxxxxxxxxxx'.replace(/[xb]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
VAR_UUID=uuidv4();
Я: наверное бинанс использует какие-то хитрые методы для индентификации юзера, а также при авторизе супер шифруют и потом как-то разгадывают...
Бинанс: no
Тоесть они прост собирают трохи инфы о фингере, в основном тут важен юа, а стартовый csrf это md5 от нихуя.
Также, чуток посерчил вебархив на предмет рест апишек интересных. В целом, их там есть и они даже работают.
http://web.archive.org/web/20171230040337/https://www.binance.com/login.html
https://web.archive.org/web/20200417073710/https://www.binance.com/en/login
Довольно интересно копаться в сурсах через веб архив.
Старая: https://accounts.binance.com/gateway-api/v1/public/authcenter/login
Нынешняя (на момент создания софта): https://accounts.binance.com/bapi/accounts/v2/public/authcenter/login
Нынешняя (на момент написания этой заметки): https://accounts.binance.com/bapi/accounts/v3/public/authcenter/login
Я даже некий прекол нашел. При авторизе можно отправить что угодно в письме на мыло юзеру. Потенциально, зная пароль можно было бы придумать какой-то фиш..
Тоесть если проходит логин и пароль, но девайс по юа новый, в device_name можно пропихнуть свой текст. Вроде и прикольно, но просто текст, более ничего запихать не удалось, а это до уязвимости не дотягивает(
Авториз работает, банан сосатб. Вот такой скриптик по итогу вышел.
Добавляем девайс дату, как баз64 от нашего фингера
Формируем запросек на рукапчу и дергаем ответ
Ну и собственно шлем запрос авториза, а потом дергаем токен и куку, выполнил от куки хэш.
По итогу чека авториз воркал (на момент написания софта)
Капча slider (geetest)
Я был бы не я, если б не сел чекать и дефолтную капчу. Как я потом понял, оказалось, что эт был geetest. Хотя, выглядело не то чтобы сильно страшно.
Шифрование и данные
Имеем тело. Данными о решении будет data.
Напоминаю, урл банана: https://accounts.binance.com/ru/login?return_to=aHR0cHM6Ly93d3cuYmluYW5jZS5jb20vcnU%3D
И так, картинка прилетает 1 куском, получается дальше он ее режет по ширине пазла и высоту не знает, умно)
Хорошо, как обычно, открываем стек вызовов, находим хуйню. Осталось теперь понять как работает эта хуйня, а именно как формируется тело запроса.
Вариантов фона за пазлом не много и это наводит на некоторые мысли по решению самого поиска пазла. А именно, собрать фон без дырки и шукать потом отличие. Также заметим, что из исходного изображения ещё 60 пикселей слева нужно вырезать. 60 чтобы отрезать пазл и ещё 60, ТК в браузере туда ставится пазл. Но об этом трохи позже.
Тэкс. Ну, начало найдено, где бреки ставить более менее ясно.
Чуть потыкав находим ключевой поинт, от которого и будет строиться весь остальной ресерч. Это итоговый жысончег, который мы шифруем.
Выведем нашу инфу в лог до шифрования.
Тыкаем далее и попадаем сюда. Видим дохуя страшную обфускацию сглаживания потока (свичкейсы и порядок действий). Где-то тут оно шифрует наше решение.
И по итогу превращает в нечитаемую кашу, а потом перегон в base64.
Ёпта, ну.. пару строк я и ручками деобфусцирую
for (var d = 0; i[o("JF1c", 218)](d, u[s(1242, "%O$2") + "h"]); d++)
p += i[s(3410, "7crc")](on, i[n("S3iz", 2013)](u[o("UlhH", 105) + n("k*^^", 1441)](d), t[r("TKH^", 2165) + c("JF1c", 2489)](i[n("u&1s", 2227)](d, t[n("Lw!2", 3103) + "h"]))));
p = [];
for (var d = 0; d<u.length; d++){
p += String.fromCharCode(u.charCodeAt(d)^t.charCodeAt(d%t.length))
}
btoa(p)
Получается, оно просто выполняет xor с неким ключом t ("2p5facdc").
Бля, ну тогда надо понять от куда берется этот ключ и шифрование будет выебано.
Сравниваем data из запроса с той что я заенкодил. Всё как надо)
Теперь осталось понять, от куда берется этот ключ t и это, либо ek, либо salt.
Тыкаем по коду и попадаем сюды, где какая-то математика с ключом ek, но в реверс виде.
var c = ["a", "b", "c", "d", "h", "i", "j", "k", "x", "y"].join("")
, l = Math.floor(e.length/t);
var i = t.split("").reverse().join("")
Ну тож изи ручками деобфусцирую. На выходе получаю простой код. На вход ek, на выходе получим ключ. Тоесть тело нашли, шифрование, на удивление, тоже.
В итоговом коде это выглядит вот так
А потом отправляем запрос с нашим решением.
Я тестировал каждый этап работы, в том числе этот. Я брекал решенную в браузере капчу, чтобы запрос не улетел, а потом перекидывал все в бас, шифровал и слал на сервер, чтобы убедиться, что все собрал правильно.
Перейдем к анализу шифруемых данных и начнем собирать ev, be, dist
Напомню, что отталкиваемся вот от сюда.
ev - какая-то мини инфа о браузере, размеры экрана и какая-то странная pre.. выглядит довольно не сложно. А вот в be у нас есть el и th, в котором тоже el..
Остаются: be - движение мыши, dist - ответ. Ну, с dist всё понятно.
Получается, нужно всего лишь понять как формировать be и в целом, че это там происходит, и пол дела сделано будет.
Покопавшись в коде, можем найти, что оно собирает инфу по событиям. Если ориентироваться на el в th, видно как оно сейвит нажатие мыши, её движение и отпускание.
click: "cl"
mousedown: "md"
mouseenter: "me"
mousemove: "mm"
mouseout: "mo"
mouseup: "mu"
touchcancel: "tc"
touchend: "te"
touchmove: "tm"
touchstart: "ts"
Получается, но чекает координаты относительно всякой хуйни и пишет в массив.
Кажется, что в thumbnailEventList координаты только относительно квадратика.
Направил курсор на нижний левый угол, выдало координаты (0, 38), учитывая что кубик 40 на 40, а 0 в у нас от левого верхнего угла.
"me|39,20" - навел и сработал первый тригер
"mm|39,20" - появилась лапка
"md|24,20" - клик с зажатием
"mm|27,22" - тяну лапку до сюда
"me|26,22" - отпускаю лапку
В результате получаем хуйню с данными о перемещении.
"si":{"w":40,"h":40} - дефолт (це размер дырки)
"el" - другие параметры движения. вот те которых дохера
"ec":{"mm":80,"md":1,"mu":1} - количество событий в массиве а также количество тыков на капчу
Если выкинуть кучу данных это превращается в :'{"ev":{"wd":35,"im":31,"de":"","brla":35,"prde":"9,35,77,59","pl":"Win32","wiinhe":657,"wiouhe":728},"be":{"ec":{"mm":80,"md":1,"mu":1},"el":[],"th":{"el":[],"si":{"w":40,"h":40}}},"dist":133}'
А если по сути, то вообще{ev: {…}, be: {…}, dist: 133}
Получается, что be это прост пропарс результатовbe: {
ec: {}
el: []
th: {}
}
От сюдаr={
eventCount: {},
eventList: [],
lastEventTime: 0,
thumbnailEventList: [],
thumbnailSize: {w: 40, h: 40}
}
th - {thumbnailEventList, thumbnailSize}
ev = {
brla: s(),
de: "",
im: s(),
pl: pc.platform,
prde: prde(),
wd: s(),
wiinhe: pc.innerHeight,
wiouhe: pc.outerHeight
}
Что же это за хитрая функция s..function s(){
(Math.floor(50 * Math.random())*2) + 1;
}
На деле, be не такой уж и сложный
be: {
ec: {
md: 1
mm: 80
mu: 1
}
el: [ ]
th: {
el: [ ]
si: { }
}
}
ec: {
md: 1
mm: 80
mu: 1
}
Большая координат движения мыши el: [ ]
Статика si: { h: 40, w: 40 }
Малая (относительно квадратика) группа движения мыши el: [ ]
Генерация данных
Штош, разобрались с реверсом шифрования и поверхностно с данными, осталось понять, как генерировать эти самые координаты.
Начнем сначала. У нас оно чекает координаты.
var u = e.getBoundingClientRect()
, l = Math.round(t.clientX - u.left)
, d = Math.round(t.clientY - u.top)
x: Math .floor(l).toString()
y: Math .floor(d).toString()
Получается, шо это код, который дергает относительно этого маленького пазла, как было замечено ранее.
По сути просто вот эти значения - старт значения кнопки.
Для генерации времени использует (new Date).getTime()
Напомню, у нас есть несколько типов событий:click: "cl"
mousedown: "md"
mouseenter: "me"
mousemove: "mm"
mouseout: "mo"
mouseup: "mu"
touchcancel: "tc"
touchend: "te"
touchmove: "tm"
touchstart: "ts"
Фул блок капчи на самом деле содержит много областей. В зависимости от того, куда я навел мышку, кликнул и тд, выводится соответствующее событие.
0: "im|mm|355,284|1648175834425|1"
27: "im|mm|120,385|5|1"
39: "shim|mm|85,397|10|1"
40: "wi|mm|84,398|7|1"
50: "slth|mm|76,416|10489542|1"
51: "wi|mm|282,505|882163|1"
52: "wi|mm|378,413|1994077|1"
Действие над im, тип действия mm - навел на картинку
Действие над shim (пазл), mm - навел на картинку
Хорошо, раз у нас дохуя всяких областей, разберемся какие вообще мы имеем области:
shim - область полоски для пазла
sl - область слайдера. скорее всего, за исключением область квадратика и надписи
trtx - область надписи на слайдере
slth - область квадратика слайдера, исключая иконку
ar - область иконки на квадратике слайдера. В идеале, в ней и нужно двигаться при решении.
Имеем области:wi - область окна капчи
im - область картинки пазла
shim - область полоски для пазла
sl - область слайдера
trtx - область надписи на слайдере
slth - область квадратика слайдера, исключая иконку
ar - область иконки на квадратике слайдера
Имеем события:click: "cl"
mousedown: "md"
mouseenter: "me"
mousemove: "mm"
mouseout: "mo"
mouseup: "mu"
touchcancel: "tc"
touchend: "te"
touchmove: "tm"
touchstart: "ts"
Может возникнуть резонный вопрос нахуя нужно вот это? А вот в чем фокус:
0: "im|mm|355,284|1648175834425|1"
27: "im|mm|120,385|5|1"
39: "shim|mm|85,397|10|1"
40: "wi|mm|84,398|7|1"
50: "slth|mm|76,416|10489542|1"
51: "wi|mm|282,505|882163|1"
52: "wi|mm|378,413|1994077|1"
Ещё раз 50: "slth|mm|76,416|10489542|1"
Второй массив отражает движение мыши только относительно квадратика,
а основной - первый, в нем и время в миллисекундах и координаты. Получается мы можем получить второй из первого по сути.
И тут я прикинул, что закодить это все будет не очень просто. Ну точнее, мне не очень уже хочется так сильно ебаться. И кто-то наверное спросит "а почему бы просто не заюзать windmouse?", а я, который о ней узнал только после того как сделал софт, отвечу:
А потом я покумекал и решил чекнуть "что если решать имитируя телефон?". Должно же быть ну явно по проще. И таки да.
Всего лишь один простенький массив нужно генерить и всё. Еще раз, нам нужен только el для be (большой), а для "th":{"el":[]..
уже не нужен. Не то чтобы это прям на сильно проще, но точно не сложнее)
Дергает оно инфу о положении с тачскрина:
clientX: 42.66666793823242
clientY: 509.3333435058594
force: 1
identifier: 0
pageX: 42.66666793823242
pageY: 509.3333435058594
radiusX: 15.333333015441895
radiusY: 15.333333015441895
rotationAngle: 0
screenX: 366
screenY: 520
Да, нам все еще нужно чекать положение пазла относительно всей высоты страницы, но лучше генерить 1 хуйню, чем 2.
Учитывая логи выше, получается, нужно зажимать только иконку стрелки, чтобы мозги себе не ебать. 44х44
- кнопка, 24х24
- иконка.
Тоесть: x = 40+((44-24)/2)+random(0,24)
slth словил на координатах, которые означают левый край, тоесть все оки.
x = 40; y = 510
at словил тыкая тож в край иконки
x = 54; y = 524
at словил тыкая тож в край иконки
x = 52; y = 520
Получается следующее:
Минималка: x = 50; y = 520
Максималка должна быть: x = 50+24=74; y = 520+24=544
Но это в рамках начального положения.
Для теста сложил время начиная с первого значения массива:"ar|ts|61,526|1648212768794|1"
от 1648212768794
, получилось 6419
Вычел от lastEventTime: 1648212775213
, получил то же значение
значит я все правильно посчитал. А значит, время действительно правильно посчиталось. Единственное, дало пол секунды хз откуда.
Хорошо, перейдем к итоговому решению. Задаем константы.
Шизо генерация.. Вместо того, чтобы юзать windmouse, которая вероятно и в бас стоит, я наговнял свой алго ветренной мышки.
По итогу генерится вот такая красота.
С другой стороны, блять, оно ведь, действительно, по итогу работало..
Решение пазла
Вернемся немного назад, ведь нужно же еще и само положение ответа как-то найти.
Классически слайдер решают собирая фоновую картинку без дырки и сопоставляют с той что в задаче.
Но работает это, когда картинок фона не много, а хотелось бы чет более универсальное.
Также, на форуме бас были 2 старых решения.
В обоих суть сводилась к поиску кусочка пазла на картинке.
Но в этом подходе качество было крайне скверное. Поэтому, как всегда, я решил пойти своим путём.
Общая тактика решения должна свестись к тому, что первым шагом мы подрежем мини пазл и чекнем его положение, далее кропнем картинку по высоте этого пазла, а потом как-то надо будет найти куда его двигать.
Я стал разгонять, а как можно улучшить картинку, убрать всякий мусор, чтоб искало положение лучше, чем бас это делает дефолтно. Погонял разные фильтры:
Я пробовал делать перегон в чб по порогу (нативными средствами бас)
Ну и оно вроде как работает, причем довольно неплохо.
Но результат был не достаточно хорош для меня.
В процессе работы также возникла очень странная бага: на картинке нельзя было рисовать через бас.
Скриним, рисуем на картинке и.. ни ху я
Берем картинку в base64, получаем айди, применяем по айди формат в жпег,, получаем base64, получаем опять айди и рисуем прямоугольник.
Я хуй знает почему, но даже на скрине не позволял рисовать. До сих пор не разобрался.
Кароче, шукал я, шукал, и потом я наткнулся на это (Алгоритмы выделения контуров изображений):
Оказалось, что это база в обработке изображений. (Более подробная инфа про фильтр собеля с хорошим кодом есть туть). Тоесть, во всяких спец либах типо OpenCV именно так и решают мою задачу
Прогнал свою картинку на одном из сайтиков и понял, что это именно то что я искал. Онли пазл, ни каких цветов, ни какого мусора.
Чуть поискав дальше, я нашел, что у этой задачи (выделения краев) есть допил на фильтра собеля, чтоб лучше фильтровать мусор. Это был алгоритм кеннни/кэннни/канни.
Я искал то, что работает без канваса, в 23.2.2 и тут (в canny-edge-detector) всё работает. Это доп либа для библиотеки image-js, либы с кучей фич для обработки изображений.
Даже код понятный.
Очень простой и логичный подход, но на удивление эффективный. Просто переводим картинки в серое, уменьшая тем самым шум от цветов, упрощая картинку, затем применяем фильтр Гауса, чтобы добавить мыла и уменьшить количество деталей, а после восстанавливаем детали фильтром собеля (алгоритмом поиска края). Тем самым мы оставим только важные детали на картинке и получим чб. По итогу мы получим только очертания, без всякого мусора. При этом, размечать ничего тоже не надо. Иные подходы к слайдерам я разберу в одной из следующих статей.
Поэтому я сразу же решил упаковать все в модуль.
Юзать ноду конечно прикольно, но иметь 1 готовый блок на много прикольнее. Для этого я и сделаль сасный прекрасный модуль
Чуть потыкав код модуля, обнаружил, что в меню можно нарисовать практически что угодно. Гифку, видео.. Ну я и нарисовал.
Чет похерилось качество при конвертации в гифку.
В итоге остановился на более минималистичной анимации, прост чтобы что-то мелькало.
Дергаем мини пазл, отрезая X по фиксе и Y по прозрачности (сверху вниз).
Докинем контуров, кроп фона и поиск картинки в картинке.
Объединив генерацию мышки и шифрование тела из прошлых разделов с решалкой можем начать тестить.
Ну и все ебашит по красоте. Единственное, в половине случаев ему не нравились движения мышки. Фрод типо. С другой стороны, у меня и руками оно решалось через раз.
99% решения, сука. Не решилась всего одна ебучая картинка.
Помимо этого нашел еще всяких приколов. Тогда я открыл для себя huggingface, где увидел кучу нейронок. Оказалось, что детектор края делают и на нейронках (нахуя, не очень понятно, но наверное в этом есть смысл).
Ну и типо это все бесплатно. Тоесть оно решает и гугл и hcaptcha и эт готовые модельки. Как гитхаб, только для нейронок и с демками.
Оказалось, есть алгоритмы генерации всякой хуйни на фракталах.
Вот такую всякую хуйню я почекал. Времени въебал конечно много, но было довольно весело.
Ну и казалось бы фсе, но нет..
Сoinmarketcap
Спустя пару недель, как я разъебал банан, спросили "могу ли я на заказ сделать coinmarketcap (авторизация и регистрация)?". Ну я и решил чекнуть че же там, со стороны выглядит, что банан, что coinmarketcap, примерно как одна хуйня.
А потом я такой: лол они просто капчу банана поставили.. и домен в капче банана.. и подписи банана.. и кучу еще чего как у него. хм.. слишком уж все похоже.
Интересно, конечно, что линка на капчу до сих пор валид: https://public.bnbstatic.com/image/antibot/image/SLIDE/20220804/01/f4b6ab69cf7f42939d9a0c77eacfea29.png
Регистрация
Хоть капча и та же (как и всё остальное), на всякий случай проведем поверхностный ресерч.
И csrf токен в урле, как и в банане, md5 от нихуя
В случае с бананом я прост 1 раз снимал свой фингер и юзал его, но раз это заказ, много аков.. желательно и этот момент по красоте порешать.
Поэтому, нашел все данные для генерации фингера - deviceInfo и его подпись fingerprint. Для полноты картины нехватает только 3х подписей x-se на реге (x-se-bh, x-se-pd, x-se-rd) и Fvideo-Id. Фингер, кстати, подписывается через MurmurHash3 (x64hash128).
Про MurmurHash3 из либы fingerprintjs я писал уже на примере yahoo. Открытые сурсы либы нашел на каком-то гитлаб через гугел.
Штош, раз ясно что собирать, накидаем скриптик.
o = {
screen_resolution: i.screenResolution,
available_screen_resolution: i.avaScreenResolution,
system_version: Rt(),
brand_model: _t(),
system_lang: r.language,
timezone: Mt(),
timezoneOffset: r.timezoneOffset,
user_agent: r.userAgent,
list_plugin: Pt(r),
canvas_code: Bt(r.canvas),
webgl_vendor: n.vendor,
webgl_renderer: n.renderer,
audio: r.audio,
platform: r.platform,
web_timezone: r.timezone,
device_name: Et()
}
Данные тянуть можно из бас фингера, разве что аудио и обычный канвас не получится.. можно сделать нормально их, а можно чисто шума туда въебать, всеравно это хэш, а на фрод оно тут не повлияет)
В отличие от банана, тут было еще некоторое количество дополнительных приколов, например подписи в заголовке, те самые x-se. А x-request-id, как и у банана, это тупо uuid4, а именно - uuid4().replace(/-/g, "")
Ну и.. вот они, осталось деобфусцировать.
Опять тело с хуйней как у капчи, опять xor по ключу. Хотя стоп, че это блять за координаты ебучие. Заметим ключик "abcddcbabadc7hbw", он нам еще пригодится)
Оказалось, оно тречит как чел тыкает вводит форму, это и есть подпись x-se-bh.
Я взял и деобфусцировал ручками эту хуетень, получив код. Ксорим мы как раз с "abcddcbabadc7hbw". Причем, как оказалось, он статичный.
Хорошо, перейдем к следующим подписям
Возьмем подпись x-se-rd. Мы енкодим какую-то рандом строку?!
Отреверсим алгоритм и получим.. такой же код как и для прошлой подписи. И ведь с x-se-pd ровно то же самое.
Заметим, что дополнительно каждая подпись енкодится еще одним алгоритмом
Задекодим его и получим ровно то что ожидали. Однако, статичное число 1100..
Я почекал и оказалось что оно считается из длинны. И тут я подумал, ну а что если я возьму сделаю по шагам енкод, а потом наоборот декод, у нас ведь ксор. И это, действительно, сработало.
Так на много удобнее работать с этими подписями. Увидел, закинул в свой алго, декодировал и чекнул че там.
А актуален ли этот код? Да, перед выпуском данного материала я чекнул coinmarketcap и оно реально до сих пор работает.
x-se-pd и x-se-rd похожи на рандом. Потыкав чуть код (пол часика тыкал код деобфусцируя через консольку), я убедился, что это и есть рандом. Тоесть рандом + контрольная сумма от него. Назвал я этот алгоритм hueta.
Итоговый код. Скорее всего, они они чекают тайм штамп в этой псевдо рандом подписи.
Вернемся к подписи x-se-bh, тк её же надо тоже генерировать..
А шо нам тут собственно надо?ec - счетчик каждого вида в el
el - набор всех движений
mt - статик параметры полей
sg - хуета хеш от el
si - сумма всех элементов el
t - текущее время
А вот el.. бля, генерить дату под ввод каждого символа будет ооочень дрочно. Это получается, над учитывать длину вводимых данных, скорость ввода имитировать.
И тут я подумал, можно же просто взять и сделать ctrl+c ctrl+v, как я люблю. В смысле, имитировать копировать вставить и эт сильно упростит мне задачу.
0 - номер блока
ts - тип события
1659865878895 - время старта
128,184 - координаты относительно всего экрана
112,28 - координаты относительно центра объекта
Для события keydown не нужны ни какие координаты, как и для touchend, но тут добавляется всё равно черт очка -
Я проделал все действия и заготовил образцовый шаблон.
Образцовый шаблон:0: "0ts1659871850308-178,184-163,28"
1: "0te114-,"
2: "0k31"
3: "0m9-179,184"
4: "0d1-179,184-163,28"
5: "0u3-179,184"
6: "0k156"
7: "1ts6204-170,286-155,26"
8: "1te98-,"
9: "1m9-171,287"
10: "1d2-171,287-155,27"
11: "1u9-171,287"
12: "1k78"
13: "1k257"
14: "2ts1977-186,532-171,32"
15: "2te219-,"
16: "2m53-187,532"
17: "2d4-187,532-171,32"
18: "2u5-187,532"
Тыкаем на поле, потом жмем кноп очки копировать вставить, потом отпускаем и еще 1 раз но на второе поле, а потом тыкаем на кнопку отправки и тоже отпускаем.
touchstart
touchend
keydown
mousemove
mousedown
mouseup
keydown
————————-
touchstart
touchend
mousemove
mousedown
mouseup
keydown
keydown
————————-
touchstart
touchend
mousemove
mousedown
mouseup
А потом собрал всё в более полный вид. Изи)
Так, с этим разобрались. В итоге, чё по хэдерам?
Пройдемся по порядку в хэдерах запроса реги
URL запроса: https://api.coinmarketcap.com/auth/v4/user/signUp
1) x-csrf-token= о, он приходит в ответ на первый запрос
2) Дефолт хедеры:
- accept: application/json, text/plain, */*
- accept-encoding: gzip, deflate, br
- accept-language: ru-RU,ru;q=0.9
- cache-control: no-cache
- content-length: 164
- content-type: application/json
3) cookie имеют тут Fvideo-Id 33899f3fa49a9da38d94435ff364252d052666b5, но больше там ничего нового или страшного нет, кроме sensorsdata2015jssdkcross..
4) device-info ну эт фингер
5) fvideo-id та самая золупа которую я и буду дальше ковырять
6) x-request-id: 6648b1f9fc3541ddb46540b670cd2b50 - uuid4
7) Дальше подписи, как генерить их уже ясно, осталось данные научиться правильно готовить
Кстати, это тоже uuid4: "X-UI-REQUEST-TRACE", "X-TRACE-ID", "BNC-UUID"
Чекал я Fvideo-Id, но ни где его не было и я уж было начал думать, что это какая-то генерация, но нет, он есть в ответе.
Запросек https://api.commonservice.io/fvideo/tenant/sign/web?en=CXU&t=cmc
А получаем мы его отправив фингер
От фингера deviceInfo оно отличается неймингом и парой доп параметров, в остальном смысол одинаковый. Главное, канвас и аудио хэш совпадают, это наверняка и чекают
До него улетает запрос на странный урл со словом antibot: https://api.commonservice.io/gateway-api/v1/friendly/antibot/coll
Параметр c не base64, потому я попробовал декодировать как подписи до этого. И оно сработало.
Обратим внимание на 52ce8bda-04ee-4fec-bea1-2c2781d992f7.
Тоесть мы сначала шлем https://api.commonservice.io/gateway-api/v1/friendly/antibot/coll и после этого запрашиваем Fvideo-Id, при том передав один и тот же идентификатор. Так как это все на одном домене, закономерно предположить, что это отдельный антифрод.
Тогда антибот тож нужно пилить..
se_ данные эт рандом с таймингом и контрольной суммой, как и в хэдерах ранее.
А вот ev это еще один фингер. Даже парочку страшных слов есть, корс, языки.. ужос.
На самом деле, тут ничего страшного нет, чуть ручками деобфусцировать и готово.
ivw - ('$cdc_asdjflasutopfhvcZLmcfl_' in document) || !!i['webdriver'] || ![];
ismo - /Android|webOS|iPhone|iPad|iPod|BlackBerry|Mobile/i.test(navigator.userAgent) || 'ontouchstart' in window || ('orientation' in window);
Чё по кукам?
1) sajssdk_2015_cross_new_user=1;
2) sensorsdata2015jssdkcross={"distinct_id":"1827ee10e6213e-07d1158fc471378-5437971-250125-1827ee10e6a12d","first_id":"","props":{"$latest_traffic_source_type":"直接流量","$latest_search_keyword":"未取到值_直接打开","$latest_referrer":""},"identities":"eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTgyN2VlMTBlNjIxM2UtMDdkMTE1OGZjNDcxMzc4LTU0Mzc5NzEtMjUwMTI1LTE4MjdlZTEwZTZhMTJkIn0=","history_login_id":{"name":"","value":""},"$device_id":"1827ee10e6213e-07d1158fc471378-5437971-250125-1827ee10e6a12d"};
3) _ga=GA1.....; _gid=GA1.2.1...;
4) se_sd=gUKVhWRwDRVGQ4HxaCBlgZZF1AgkNESW1tWBcVEN1hdWwVlNXVIO1; se_gd=hNTVhUhgRHAUVBVlSCgggZZXQElUGBVUFoSBcVEN1hdWwDlNXVcM1; se_gsd=XjEgOzBhLCM0CQUxNAw2Ci0xEgFXAQBXV1lBW1NWVlhbAlNS1;
Вообще, это вроде какие-то чисто китайские кукисы.
А вот se_.. непонятно только от куда берется se_gsd
se_gsd берется из ответа на этот запрос + немного доп обработки.
"T1LVF1UUlBUAlNwAgwYBQMIUVpkIDM2IQMxDjE3VgMhFSdyLDA0w9223q9dj04".split('').reverse().join('')
parseInt("40jd9q3229w0ADLydSFhMgV3EjDxMQI2MDIkpVUIMQBYwgAwNlAUBlUU1FVL1T".length/0x5)
А такая функция у меня уже есть
Еще встретил в процессе реверса какие-то преколы.
Мне было лен парсить и форматировать фингеры баса, поэтому я сделал html страничку и прогнал на эмуле скрипты с установкой фингера.
Ну а далее все как было ранее, формируем фингер и докидываем хэш, формируем из него фингер для fvideo, дергаем кукисы, касуем генерацию sensorsdata2015jssdkcross, формируем sd_ подписи и проходим antibot, получив fvideo.
А далее автори. Кастуем подписи, получаем куки, решаем капчу, пытаемся войти по токену качпчи.
С капчей все как было разобрано ранее. Получаем, режем, решаем, получаем токен решения.
В итоге, все работает. Не удивительно.
Единственное, модуль на 23.2.2 бас в 12 ноде перестал работать, тк тянул новую версию image-js с какой-то багой. Хотя на новом бас в 18 ноде оно работает нормально. Для 23.2.2 я нашел ласт валидную версию - 0.21.9 и фиксанул модуль.
Авторизация
И тут меня ждал сюрприз. Я делал под слайдер, а тут не слайдер. Тут хуйня как у бинанса. Нахуя я тогда вообще ебался со слайдером?
Я начал тыкать капчу, презагружал много раз, переключал на регу. И случилось чудо. Я обнаружил "фичу" с подменой капчи)
Цимес тут вот в чем. Нельзя просто так взять и получить капчу. Капча привязана к "приложению", а именно токену который оно возвращает. Тоесть при запросе авториза оно вертает securityId (securityCheckResponseValidateId)
По этому айди вертает только капчу приложения, тоесть что бы я не написал biz mode (CMC_register например), оно вернет такую капчу, какая положена для этого ендпоинта.
Только вот есть нюанс. Я могу получить капчу слайдер для регистрации, а токен решения потом применить на авторизации)
Собственно, это я и сделал, запилив по итогу фул рабочий софт всего с 1 капчей.
А почему не соло?
Потому-что у меня есть дохуя потоков чтобы выебать бинанс)
Дополнение
Чёт я решил ещё раз почекать бинанс перед публикацией и случайно наткнулся на демо страницу, страницу с капчей и без шифрования. На её примере на много проще понять всю эту хуйню.
Чуть выше видим формирование тела решения капчи
Перейдем тогда сразу к шифрованию. Видим ультра гига мега шифровку на ксор в пару строк)
У меня по итогу ручной деобфускации вышел тот же код по сути.
В мое случае также sig была в качестве контрольной суммы, но туть она прилетает сразу.
Чекнем ev. Штош, без обфускации выглядит совсем не страшно
Ну, а c be тут все тоже на много яснее.
Я искал файл который дебажил (с обфускацией), но он не сохранился, благо на скрине осталось название, а потому я смог дернуть искомый файл)
Я думал, что в проде данная капча уже не юзается, раз сурсы открытые. Но оказалось шо нет, до сих пор висит на каких-то эндпоинтах.
А это значит, что можно подсунуть её токен в любой эндпоинт)