Статья для участия в конкурсе программистов
WPF - приложение Cursor Path Recorder. Привет всем, пожалуй, начну!
Ни для кого не является секретом, что в современных реалиях, IT – гиганты и корпорации пытаются использовать любые данные своих пользователей для извлечения из них максимальной выгоды. В качестве последнего доказательства можно привести хотя бы постоянные обновления политики конфиденциальности при использовании продуктов Google. Для тех, кто не знаком – на текущий момент – это просто огромный перечень сложных правил по использованию сервисов Google, с которыми вам, как пользователю, придется естественно согласиться.
Приложение предназначено для автоматического управления системным курсором. Позволяет помочь в обходе защиты Google Recaptcha v3, которая предполагает анализ поведенческих факторов пользователя как одного из основных при выдаче “fraud score”.
Позволяет помочь в обходе запись Вебвизора, являющегося частью Яндекс.Метрики.
Также приложение может использоваться для любых иных задач, связанных с автоматизацией управления системным курсором, например, при тестировании веб-приложений, игр, написании ботов, в которых нужна имитация действий настоящего пользователя, для управления браузером на веб-ресурсах, которые используют антифрод-системы по отслеживанию пользовательского поведения. Из последних известных примеров можно привести: Hubstaff Tracker или Upwork Tracker, предназначенные для отслеживания работы исполнителей на биржах фриланса по довольно топорным метрикам.
Обзор интерфейса пользователя:
С первого взгляда интерфейс может показаться перегруженным элементами управления, но каждый из них используется для записи различного поведения пользователя, поэтому обо всем по порядку:
Список начальных квадратов: перечень с единичным выбором, отвечает за точку, из которой начинается записываемый путь мыши;
Список конечных квадратов: то же самое, только для конечной точки пути курсора мыши;
Квадратов по вертикали/горизонтали, кнопка “Apply”: пользователь может самостоятельно решить насколько детализированными будут записываемые пути курсора и на сколько частей нужно разделить весь экран;
Вспомогательные точки/квадраты: чекбокс определяет, будут ли отдельно подсвечиваться сиреневым цветом начальный и конечный квадраты, для которых записывается путь, также отображается номер квадратов на экране, нумерация квадратов начинается с верхнего левого угла (чтобы увидеть их, нужно предварительно выбрать цифру в «Списке начальных квадратов», «Списке конечных квадратов» и нажать на кнопку «Начать запись»);
Выбор маршрута: список с порядковыми номерами всех записанных ранее путей для комбинации начальной и конечной области экрана;
Рядом с надписью «Подстройка маршрута»: неактивные текстовые поля показывают текущую позицию курсора (изменяется при перемещении), пару X Y для начальной точки, пару X Y для конечной точки (задаются с помощью горячих клавиш).
Описание функциональных возможностей:
Общий принцип работы программы таков: изначально записываются отдельные пути движения курсора мыши самим пользователем, далее сохраненные пути воспроизводятся с учетом 2-х точек: начальной, откуда нужно передвигать мышь, и конечной, куда её нужно переместить.
Чтобы посчитать все варианты перемещения курсора из любой точки на экране, например, для ноутбука с разрешением 1366*768 нужно просто перемножить эти числа. Получим 1049088 комбинаций. Более миллиона комбинаций при далеко не самом высоком разрешении. Естественно, записывать 1 млн. комбинаций вручную было бы очень затруднительно, поэтому в приложении используются специальные интерполирующие формулы, которые сглаживают и обобщают записанные пути для более обширных областей экрана, чем 1 пиксель. В случае с разрешением 1366*768 экран разделен на 6*7 = 42 частей. Чтобы записать все возможные комбинации по перемещению курсора между этими областями, нужно 42*42 = 1764 записей. Согласитесь, это гораздо меньше, чем 1 млн. Конечно, и кол-во в 1,7к записей может показаться раздутым, но этого вполне достаточно, чтобы передвижения курсора, записанные заранее, идеально смотрелись при кастомном указании любых других точек.
Структура, в которой сохраняются пути движения курсора позволяет пользователю записывать несколько однотипных траекторий, например, чтобы не повторять один и тот же маршрут из точки в точку, пользователь может записать 2 или более путей, по которым курсор может быть перемещен в конечную точку. В прикрепленном предварительно сохраненном наборе записанных путей курсора, в основном, записано по 2 вариации перемещения курсора из точки в точку.
Теперь после небольших объяснений к самим возможностям:
- возможность записи/воспроизведения/очистки «прямых» путей движения курсора (это обычное перемещение курсора от точки до точки, к ним применяется интерполяция точек маршрута)
- возможность записи/воспроизведения/очистки «блуждающих» по экрану путей (это пути, к которым в дальнейшем не применяются формулы для интерполяции пути, т.е. эти пути позже будут воспроизведены в точности также как они были записаны ранее)
- запись маршрутов скроллинга
- имитация промахивания при перемещении курсора до точки
Фрагменты кода:
Функция ExecuteWay
Код поверхностного окна для Canvas
Текущее применение приложения:
Данное приложение было изначально разработано для тестирования наличия и степени сложности обхода антифрод систем, которые могут быть установлены администраторами веб-ресурсов и веб-приложений. Приложений было успешно протестировано на биткоин – кранах для автоматического и полуавтоматического (не решает капчу) сбора криптовалюты с кранов.
В списке протестированных ресурсов есть приложения, которые используют Вебвизор от Яндекса для треккинга и сбора информации от пользователей (определено с помощью Wappalyzer):
Вариант дальнейшего развития приложения:
Архив Cursor Path Recorder:
В приложении к посту прилагается исполняемый файл WPF - приложения CursorPathRecorder.exe, список заготовленных путей для разрешения экрана 1366*768 в файле serialized_ways.xml.
Выполнять проверку работоспособности приложения сильно рекомендуется на экранах с разрешением 1366*768 (на этом разрешении изначально разрабатывалось и для него были записаны все пути).
WPF - приложение Cursor Path Recorder. Привет всем, пожалуй, начну!
Ни для кого не является секретом, что в современных реалиях, IT – гиганты и корпорации пытаются использовать любые данные своих пользователей для извлечения из них максимальной выгоды. В качестве последнего доказательства можно привести хотя бы постоянные обновления политики конфиденциальности при использовании продуктов Google. Для тех, кто не знаком – на текущий момент – это просто огромный перечень сложных правил по использованию сервисов Google, с которыми вам, как пользователю, придется естественно согласиться.
Приложение предназначено для автоматического управления системным курсором. Позволяет помочь в обходе защиты Google Recaptcha v3, которая предполагает анализ поведенческих факторов пользователя как одного из основных при выдаче “fraud score”.
Позволяет помочь в обходе запись Вебвизора, являющегося частью Яндекс.Метрики.
Также приложение может использоваться для любых иных задач, связанных с автоматизацией управления системным курсором, например, при тестировании веб-приложений, игр, написании ботов, в которых нужна имитация действий настоящего пользователя, для управления браузером на веб-ресурсах, которые используют антифрод-системы по отслеживанию пользовательского поведения. Из последних известных примеров можно привести: Hubstaff Tracker или Upwork Tracker, предназначенные для отслеживания работы исполнителей на биржах фриланса по довольно топорным метрикам.
Обзор интерфейса пользователя:
С первого взгляда интерфейс может показаться перегруженным элементами управления, но каждый из них используется для записи различного поведения пользователя, поэтому обо всем по порядку:
Список начальных квадратов: перечень с единичным выбором, отвечает за точку, из которой начинается записываемый путь мыши;
Список конечных квадратов: то же самое, только для конечной точки пути курсора мыши;
Квадратов по вертикали/горизонтали, кнопка “Apply”: пользователь может самостоятельно решить насколько детализированными будут записываемые пути курсора и на сколько частей нужно разделить весь экран;
Вспомогательные точки/квадраты: чекбокс определяет, будут ли отдельно подсвечиваться сиреневым цветом начальный и конечный квадраты, для которых записывается путь, также отображается номер квадратов на экране, нумерация квадратов начинается с верхнего левого угла (чтобы увидеть их, нужно предварительно выбрать цифру в «Списке начальных квадратов», «Списке конечных квадратов» и нажать на кнопку «Начать запись»);
Выбор маршрута: список с порядковыми номерами всех записанных ранее путей для комбинации начальной и конечной области экрана;
Рядом с надписью «Подстройка маршрута»: неактивные текстовые поля показывают текущую позицию курсора (изменяется при перемещении), пару X Y для начальной точки, пару X Y для конечной точки (задаются с помощью горячих клавиш).
Описание функциональных возможностей:
Общий принцип работы программы таков: изначально записываются отдельные пути движения курсора мыши самим пользователем, далее сохраненные пути воспроизводятся с учетом 2-х точек: начальной, откуда нужно передвигать мышь, и конечной, куда её нужно переместить.
Чтобы посчитать все варианты перемещения курсора из любой точки на экране, например, для ноутбука с разрешением 1366*768 нужно просто перемножить эти числа. Получим 1049088 комбинаций. Более миллиона комбинаций при далеко не самом высоком разрешении. Естественно, записывать 1 млн. комбинаций вручную было бы очень затруднительно, поэтому в приложении используются специальные интерполирующие формулы, которые сглаживают и обобщают записанные пути для более обширных областей экрана, чем 1 пиксель. В случае с разрешением 1366*768 экран разделен на 6*7 = 42 частей. Чтобы записать все возможные комбинации по перемещению курсора между этими областями, нужно 42*42 = 1764 записей. Согласитесь, это гораздо меньше, чем 1 млн. Конечно, и кол-во в 1,7к записей может показаться раздутым, но этого вполне достаточно, чтобы передвижения курсора, записанные заранее, идеально смотрелись при кастомном указании любых других точек.
Структура, в которой сохраняются пути движения курсора позволяет пользователю записывать несколько однотипных траекторий, например, чтобы не повторять один и тот же маршрут из точки в точку, пользователь может записать 2 или более путей, по которым курсор может быть перемещен в конечную точку. В прикрепленном предварительно сохраненном наборе записанных путей курсора, в основном, записано по 2 вариации перемещения курсора из точки в точку.
Теперь после небольших объяснений к самим возможностям:
- возможность записи/воспроизведения/очистки «прямых» путей движения курсора (это обычное перемещение курсора от точки до точки, к ним применяется интерполяция точек маршрута)
- возможность записи/воспроизведения/очистки «блуждающих» по экрану путей (это пути, к которым в дальнейшем не применяются формулы для интерполяции пути, т.е. эти пути позже будут воспроизведены в точности также как они были записаны ранее)
- запись маршрутов скроллинга
- имитация промахивания при перемещении курсора до точки
Фрагменты кода:
Функция ExecuteWay
C#:
private void ExecuteWay(WayTypes wt, int way_number, int miss, int walk_way_numb)
{
List<List<int>> walkingCoordinates, straightCoordinates, shortCoordinates, wheelCoordinates;
Random rnd = new Random();
switch (wt)
{
case WayTypes.WalkingWay:
int waysCount;
if (walk_way_numb == -1)
{
//выбрать случайный путь из блуждающих по всему экрану
waysCount = WalkingWaysStructure.Count;
if (way_number == 0)
waysCount = rnd.Next(0, waysCount);
else waysCount = way_number;
}
else
{
waysCount = walk_way_numb;
}
walkingCoordinates = DeepCopy(WalkingWaysStructure[waysCount]);
//если из текущей точки, в которой находится курсор, можно выполнить действие, то применить нужную дельту координатам пути и выполнить путь
//определеяем текущие координаты, находим разницу с первой точкой маршрута, учитывая знак
//применяем такое же изменение к другим точкам
GetCursorPos(out beginPoint);
int deltaX = beginPoint.X - walkingCoordinates[0][0];
int deltaY = beginPoint.Y - walkingCoordinates[0][1];
for (int jj = 0; jj < walkingCoordinates.Count; jj++)
{
walkingCoordinates[jj][0] += deltaX;
walkingCoordinates[jj][1] += deltaY;
}
if (CanPerformWay(walkingCoordinates) == false)
{
//если маршрут не умещается на экране, для избежания некорректного поведения, передвинуть курсор с помощью перемещения из точки в точку туда, откуда должен начинаться данный маршрут
//начальная точка уже записана несколькими строками ранее
endPoint.X = WalkingWaysStructure[waysCount][0][0];
endPoint.Y = WalkingWaysStructure[waysCount][0][1];
GetCursorPos(out beginPoint);
ExecuteWay(WayTypes.StraightWay, -1, 0, -1); //промах в конце прямого маршрута не выполняется, если этот маршрут используется для перемещения курсора на нужное место, а не для наведения на объект на странице в браузере
//ожидание завершения работы потока, отвечающего за доведения указателя до нужной точки через прямой маршрут
play_thread.Join();
PlayCoordinates(DeepCopy(WalkingWaysStructure[waysCount]));
}
else
PlayCoordinates(walkingCoordinates);
play_thread.Join();
break;
case WayTypes.StraightWay:
//координаты начала и конца маршрута уже заданы извне либо использующим библиотеку приложением, либо пользователем
//прежде всего нужно определить, не является ли путь очень коротким, т.к. при коротких траекториях другое поведение и общая трансформация, которая будет применяться дальше
//может очень сильно исказить маршрут, так что он станет выглядеть неправдоподобно
//тут возникает вопрос, когда траектория считается короткой - когда прямая длина пути меньше, чем расстояние между серединами двух соседних квадратов (по горизонтали или вертикали)
//подразумевается, что точка назначения endPoint установлена извне
int Xx = Math.Max(beginPoint.X, endPoint.X) - Math.Min(beginPoint.X, endPoint.X);
int Yy = Math.Max(beginPoint.Y, endPoint.Y) - Math.Min(beginPoint.Y, endPoint.Y);
double pathLength = Math.Sqrt(Xx * Xx + Yy * Yy);
//при прямом пути нужно опеределить, в какой квадрат попадает начальная точка пути, в какой квадрат попадает конечная точка
//определяем номер через ceiling
int idxBeginStraight = (Convert.ToInt32(Math.Ceiling((double)(beginPoint.X) / (double)(rectangleWidth)))) + (Convert.ToInt32(Math.Floor(((double)(beginPoint.Y) / (double)(rectangleHeight)))) * (widthSquares +1));
int idxEndStraight = (Convert.ToInt32(Math.Ceiling(((double)(endPoint.X) / (double)(rectangleWidth))))) + (Convert.ToInt32(Math.Floor(((double)(endPoint.Y) / (double)(rectangleHeight)))) * (widthSquares + 1));
//логика на случай реализации коротких маршрутов до конца
if (pathLength < minPath)
{
int totalSquares = (heightSquaresReal * widthSquaresReal);
//выбрать в пределах трех квадратов место назначения для перемещения
List<int> numberList = new List<int>();
//добавление верхней линии
int beginSquare = (idxBeginStraight - ((widthSquaresReal) * 3) - 3);
int beginFloor = (Convert.ToInt32(Math.Ceiling((double)idxBeginStraight / (double)(widthSquaresReal)))-1);
for (int oo = beginSquare; oo < beginSquare + 7; oo++)
{
if (filterBorderSquare(beginFloor-3, oo, totalSquares))
numberList.Add(oo);
}
//добавление 5 следующих промежуточных линий
addMiddleLines(numberList, idxBeginStraight, 2, true, beginFloor-2, totalSquares);
addMiddleLines(numberList, idxBeginStraight, 1, true, beginFloor-1, totalSquares);
addMiddleLines(numberList, idxBeginStraight, 0, true, beginFloor+0, totalSquares);
addMiddleLines(numberList, idxBeginStraight, 1, false, beginFloor+1, totalSquares);
addMiddleLines(numberList, idxBeginStraight, 2, false, beginFloor+2, totalSquares);
//добавление нижней линии из квадратов
beginSquare = (idxBeginStraight + ((widthSquaresReal) * 3) - 3); //-1 в конце, мы работаем с индексами
for (int oo = beginSquare; oo < beginSquare + 7; oo++)
{
if (filterBorderSquare(beginFloor+3, oo, totalSquares))
numberList.Add(oo);
}
//выбор случайного квадрата из выбранных
int rndS = rnd.Next(0, numberList.Count);
rndS = numberList[rndS];
//сохранение точки назначения в промежуточной переменной, выполнение промежуточного маршрута, выбор промежуточной точки для перемещения в промежуточный квадрат
POINT endPointSave = endPoint;
double wholePart = Math.Ceiling((double)rndS/(double)(widthSquaresReal))-1;
double minX = Math.Floor((((rndS-1-wholePart* (widthSquaresReal)) * deskWidth / widthSquaresReal) +1) + xBorder);
double maxX = Math.Floor(((rndS - wholePart * (widthSquaresReal)) * deskWidth / widthSquaresReal) - xBorder);
endPoint.X = rnd.Next(Convert.ToInt32(minX), Convert.ToInt32(maxX));
double minY = Math.Floor(((wholePart* deskHeight / heightSquaresReal) + 1) + yBorder);
double maxY = Math.Floor(((wholePart+1) * deskHeight / heightSquaresReal) - yBorder);
endPoint.Y = rnd.Next(Convert.ToInt32(minY), Convert.ToInt32(maxY));
ExecuteWay(WayTypes.StraightWay, -1, 0, -1);
play_thread.Join();
//выполнение второй части маршрута с доведением до нужной точки с предварительным обновлением начальной точки и восстановлением конечной точки
GetCursorPos(out beginPoint);
endPoint = endPointSave;
ExecuteWay(WayTypes.StraightWay, -1, 0, -1); //промах в конце короткого маршрута не выполняется
play_thread.Join();
}
else
{
//подстройка индексов квадратов для обращения в массивах
idxBeginStraight--; idxEndStraight--;
int waysCountrnd;
if (way_number == -1)
{
//выбрать случайный доступный маршрут по такой траектории (из предварительно записанных)
waysCountrnd = BeginStraightWaysStructure[idxBeginStraight][idxEndStraight].Count;
waysCountrnd = rnd.Next(0, waysCountrnd);
}
else waysCountrnd = way_number;
//straightCoordinates = new List<List<int>>(WaysStructure[waysCountrnd]);
straightCoordinates = DeepCopy(BeginStraightWaysStructure[idxBeginStraight][idxEndStraight][waysCountrnd]);
//провести необходимые преобразования маршрута, чтобы выполнить не записанное движение, а то, которое нужно нам
ScalePathTransform(straightCoordinates);
//проверка на необходимость добавить в конце маршрута промах по объекту на странице
if (miss == 1)
{
//добавить к массиву координат новые, которые отвечают за промах
//при этом брать нужно только те координаты, которые наиболее близки по направлению с предыдущей частью маршрута
//как работать и определять эти направления? Мы при записи маршрутов из прямоугольных областей можем записать любой маршрут
//т.е. он будет не совсем прямой на всем пути свеого следования, но мы обязательно должны дойти до конкретной точки в начале и начальная точка тоже строго фиксирована
//поэтому берем первую точку маршрута (именно ту точку, которая относится к самому маршруту, а не к исходной точки в прямоугольнике, предназначенном для записи)
//берем последнюю точку, и вычсляем общее направление маршрута только по ним, не забываем о том, что специфика координат на экране такая, что значения по оси Y изменяются в противоположную сторону, т.е. вверху находятся меньшие значения
double path = Math.Sqrt(Math.Pow(Math.Abs(endPoint.X - beginPoint.X), 2) + Math.Pow(Math.Abs(endPoint.Y - beginPoint.Y), 2));
double cosOriginal = (endPoint.X - beginPoint.X) / path;
//для учета знака просто изменяем порядок слагаемых в первом выражении
double sinOriginal = (beginPoint.Y - endPoint.Y) / path;
//после того как направление определено (оно задается парой cos, sin), берем ближаюшую пару значений cos - sin из значений для маршрута поиска
//например, у нас получились значения cos = 0.5, sin = -0.4, ближайшее значений по cos = 0.7, ближайшее значений sin = -0.3.
//другими словами, ближайшее значение определяется как минимальная сумма по ближайшим cos+sin. Далее берется направление для заданных значений
//например, это направление 2
//как сравнить значения по-отдельностии? неужели нужно сравнивать значения для всех 8-ми направлений?
//находим первое ближайшее значение, которое меньше сравниваемой величины либо равно ей, берем следующее значение, котороые больше либо равно сравниаемой величине (условие больше либо равно)
int[] firstX = { 1, 2 };
int[] firstY = { 3, 4 };
int[] secondX = { 1, 8 };
int[] secondY = { 3, 2 };
double firstCos = directionsValues[0][0];
double firstSin = directionsValues[2][1];
double secondCos = directionsValues[1][0];
double secondSin = directionsValues[3][1];
for (int jk = 1; jk < 5; jk++)
{
if (cosOriginal <= directionsValues[jk][0] && cosOriginal >= directionsValues[jk + 1][0])
{
//если значение меньше, чем предыдущее, то устанавливаем новую пару, firstCos, firstSin, где теперь firstCos = secondCos
firstX[0] = jk+1; firstX[1] = jk + 2;
secondX[0] = 10 - firstX[0];
secondX[1] = 10 - firstX[1];
if (firstX[0] == 1)
{
secondX[0] = 1;
}
if (firstX[1] == 5)
{
secondX[1] = 5;
}
firstCos = secondCos;
secondCos = directionsValues[jk + 1][0];
}
else
{
//нашли два значения по cos, теперь ищем по sin
for (int jl = 3; jl < 7; jl++)
{
if (sinOriginal <= directionsValues[jl][1] && sinOriginal >= directionsValues[jl + 1][1])
{
firstY[0] = jl+1; firstY[1] = jl + 2;
if (firstY[0] <= 5)
{
secondY[0] = 10 - firstY[0] - 4;
}
else
{
secondY[0] = 10 - firstY[0] + 4;
}
if (firstY[1] <= 5)
{
secondY[1] = 10 - firstY[1] - 4;
}
else
{
secondY[1] = 10 - firstY[1] + 4;
}
firstSin = secondSin;
secondSin = directionsValues[jk + 1][1];
}
}
}
}
//теперь нам остается только сравнить две суммы и взять наименьшую, точнее номер направления, который соответствует наименьшей сумме
int[] allX = { firstX[0], firstX[1], secondX[0], secondX[1] };
int[] allY = { firstY[0], firstY[1], secondY[0], secondY[1] };
int unique1 = -1, unique2 = -1;
for (int jj = 0; jj < allX.Length; jj++)
{
for (int jl = 0; jl < allY.Length; jl++)
{
if (allX[jj] == allY[jl])
if (unique1 == -1)
unique1 = allX[jj];
else if (unique2 == -1)
{
if (allX[jj] == unique1)
continue;
else
unique2 = allX[jj];
}
else break;
}
}
double sum1 = Math.Abs(Math.Abs(directionsValues[unique1-1][0]) - Math.Abs(cosOriginal)) + Math.Abs(Math.Abs(directionsValues[unique1-1][1]) - Math.Abs(sinOriginal));
double sum2 = Math.Abs(Math.Abs(directionsValues[unique2-1][0]) - Math.Abs(cosOriginal)) + Math.Abs(Math.Abs(directionsValues[unique2-1][1]) - Math.Abs(sinOriginal));
int direction;
if (sum1 <= sum2)
direction = unique1;
else direction = unique2;
//-> далее берется любой из предварительно записанных маршрутов для этого направления
//если маршрутов по данному направлению не найдено, то приплюсавка значений не производится
//try
//{
int missCountrnd = rnd.Next(0, MissWaysStructure[direction-1].Count);
List<List<int>> additionCoordin = MissWaysStructure[direction-1][missCountrnd];
beginPoint.X = endPoint.X; beginPoint.Y = endPoint.Y;
deltaX = beginPoint.X - additionCoordin[0][0];
deltaY = beginPoint.Y - additionCoordin[0][1];
for (int jj = 0; jj < additionCoordin.Count; jj++)
{
additionCoordin[jj][0] += deltaX;
additionCoordin[jj][1] += deltaY;
}
int lengthOrigin = straightCoordinates.Count;
for (int yy = 0; yy < additionCoordin.Count; yy++)
{
straightCoordinates.Add(new List<int>(2) { 0,0});
straightCoordinates[lengthOrigin + yy][0] = additionCoordin[yy][0];
straightCoordinates[lengthOrigin + yy][1] = additionCoordin[yy][1];
}
}
//catch(Exception ex) { }
//}
PlayCoordinates(straightCoordinates);
//относится к условной развилке shortWay
}
break;
case WayTypes.ShortWay:
//вызов простого перемещения на небольшую дистанцию
//для упрощения структур с маршрутами будет только 4 коротких путей
//как будет работать короткий путь, во многом он похож на прямой, также будет выполняться его подстройка под точки,
//преднамерено мы запишем этом путь так, что в нем будет немного больше точек, чем определяет minPath
//это нужно, чтобы мы гарантированно могли добраться до конечной точки
//далее, допустим у нас минимальный маршрут - это 228 точек, тогда любые перемещения, которые по длине меньше 228 точек
//будут выполняться этим методом. Но что если у нас очень короткий маршрут, например 70 точек или 50, мало ли какой
//нам потребуется маршрут
//если мы будем ужимать 228 точек в 70, то получится неадекватный маршурт, поэтому мы просто выберем из эталонного маршрута
//первые 70 точек пути +20% от пути (у нас же все-таки не прямой путь) и будем с ними работать
//все, что выше бред - первый вариант - черновик - ниже функционал
//если точка находится в той же позиции, то выполнять маршрут не требуется
if (beginPoint.X != endPoint.X || beginPoint.Y != endPoint.Y)
{
double path = Math.Sqrt(Math.Pow(Math.Abs(endPoint.X - beginPoint.X), 2) + Math.Pow(Math.Abs(endPoint.Y - beginPoint.Y), 2));
double cosOriginal = (endPoint.X - beginPoint.X) / path;
double sinOriginal = (beginPoint.Y - endPoint.Y) / path;
//применить трансформацию ко всему короткому маршруту, который выбран случайно
waysCount = rnd.Next(0, ShortWaysStructure.Count);
shortCoordinates = DeepCopy(ShortWaysStructure[waysCount]);
//применение дельты к маршруту
int deltaSX = beginPoint.X - shortCoordinates[0][0];
int deltaSY = beginPoint.Y - shortCoordinates[0][1];
for (int jj = 0; jj < shortCoordinates.Count; jj++)
{
shortCoordinates[jj][0] += deltaSX;
shortCoordinates[jj][1] += deltaSY;
}
int wayLenght = ShortWaysStructure[waysCount].Count;
//точка вокруг которой нужно вращать - начальная точка, в которой сейчас находимся
for (int rr = 0; rr < wayLenght; rr++)
{
POINT savedShort; savedShort.X = shortCoordinates[rr][0]; savedShort.Y = shortCoordinates[rr][1];
shortCoordinates[rr][0] = Convert.ToInt32(beginPoint.X + (shortCoordinates[rr][0] - beginPoint.X) * cosOriginal - (shortCoordinates[rr][1] - beginPoint.Y) * -sinOriginal);
shortCoordinates[rr][1] = Convert.ToInt32(beginPoint.Y + (shortCoordinates[rr][1] - beginPoint.Y) * cosOriginal + (savedShort.X - beginPoint.X) * -sinOriginal);
}
//оборвать маршрут на той точке, которая максимально близка к той, в которую нам нужно переместиться
List<double> paths = new List<double>();
for (int rr = 0; rr < wayLenght; rr++)
{
path = Math.Sqrt(Math.Pow(Math.Abs(endPoint.X - shortCoordinates[rr][0]), 2) + Math.Pow(Math.Abs(endPoint.Y - shortCoordinates[rr][1]), 2));
paths.Add(path);
}
double minPath = paths.Min();
int idxMin = paths.IndexOf(minPath);
List<List<int>> shortedWay = new List<List<int>>(idxMin+1);
for (int ww=0; ww< idxMin; ww++)
shortedWay.Add(shortCoordinates[ww]);
//передать аргумент пути (список List<List<int>>)
PlayCoordinates(shortedWay);
play_thread.Join();
}
else { }
break;
case WayTypes.WheelWay:
//опциональная строка, которая нужна только в тестовом приложении
Thread.Sleep(Convert.ToInt32(scroll_delay_text.Text)*1000);
//
//чаще всего пользователь выполняет по 3 скролла
int remainingScrolls = scrollWheels;
int currentScrolls = 1;
//запустить в отдельном потоке путь для скроллинга
//запускаемый поток обрывается извне, далее по листингу
PlayWheelCoordinates();
while (remainingScrolls > 0)
{
currentScrolls = rnd.Next(1, 5);
if (currentScrolls > remainingScrolls)
currentScrolls = remainingScrolls;
//выполнить скроллы с одинаковым интервалом между ними
for (int df = 0; df < currentScrolls; df++)
performScroll(scrollDirection, rnd.Next(120,180));
//большая задержка между скроллами
//каждые 5 скроллов человек останавливает руку для того, чтобы передохнуть
if (remainingScrolls - currentScrolls == 0)
wheel_thread.Abort();
if (remainingScrolls % 5 == 0)
Thread.Sleep(rnd.Next(400, 500));
else
{
if (scrollSpeed == 1)
Thread.Sleep(rnd.Next(100, 400));
else
Thread.Sleep(rnd.Next(300, 500));
}
remainingScrolls -= currentScrolls;
}
wheel_thread.Abort();
break;
}
}
Код поверхностного окна для Canvas
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace CursorPathRecorder
{
/// <summary>
/// Interaction logic for Canvas_fullscreen.xaml
/// </summary>
public partial class Canvas_fullscreen : Window
{
MainWindow parent;
Rect beginArea, endArea, beginArea1;
Rectangle beginRect, endRect, beginRect1;
MainWindow.POINT currentPoint, beginPoint, endPoint;
double deskHeight;
double deskWidth;
int rectangleWidth;
int rectangleHeight;
public Canvas_fullscreen(MainWindow ParentWindow,MainWindow.POINT beginPoint, MainWindow.POINT endPoint, int idxBegin, int idxEnd)
{
InitializeComponent();
parent = ParentWindow;
this.KeyDown += new KeyEventHandler(OnButtonKeyDown);
this.endPoint = endPoint;
this.beginPoint = beginPoint;
beginRect = new Rectangle();
beginRect1 = new Rectangle();
endRect = new Rectangle();
front_canvas.Children.Add(beginRect);
front_canvas.Children.Add(beginRect1);
front_canvas.Children.Add(endRect);
initializePointsRectStaff();
Color adjustmentColor = Color.FromArgb(120, 123, 45, 67);
endRect.Width = endArea.Width;
endRect.Height = endArea.Height;
endRect.Stroke = new SolidColorBrush(adjustmentColor);
endRect.Fill = new SolidColorBrush(adjustmentColor);
Canvas.SetLeft(endRect, endArea.X);
Canvas.SetTop(endRect, endArea.Y);
//установка текста для endRect
TextBlock textBlock = new TextBlock();
textBlock.FontSize = 30;
textBlock.Text = (idxEnd+1).ToString();
textBlock.Foreground = new SolidColorBrush(Color.FromArgb(255, 255, 255, 255));
Canvas.SetLeft(textBlock, endArea.X + rectangleWidth/2 - 20);
Canvas.SetTop(textBlock, endArea.Y + rectangleHeight / 2 -20);
front_canvas.Children.Add(textBlock);
beginRect1.Width = beginArea1.Width;
beginRect1.Height = beginArea1.Height;
beginRect1.Stroke = new SolidColorBrush(adjustmentColor);
beginRect1.Fill = new SolidColorBrush(adjustmentColor);
Canvas.SetLeft(beginRect1, beginArea1.X);
Canvas.SetTop(beginRect1, beginArea1.Y);
//установка текста для beginRect1
textBlock = new TextBlock();
textBlock.FontSize = 30;
textBlock.Text = (idxBegin+1).ToString();
textBlock.Foreground = new SolidColorBrush(Color.FromArgb(255, 255, 255, 255));
Canvas.SetLeft(textBlock, beginArea1.X + rectangleWidth / 2 - 20);
Canvas.SetTop(textBlock, beginArea1.Y + rectangleHeight / 2 - 20);
front_canvas.Children.Add(textBlock);
}
private void OnButtonKeyDown(object sender, KeyEventArgs e)
{
if (Keyboard.IsKeyDown(Key.Escape) == true)
this.Close();
parent.RaiseEvent(e);
}
private void initializePointsRectStaff()
{
//когда мы записываем прямой маршрут, пользователю нужно немного помочь. Помощь заключается в том, что пользователь не должен делить
//экран на части и считать местоположение квадратов и их координаты
//поэтому в данном участке программы создается canvas, который обрисовывает прямоугольную область начального и конечного квадратов
//(нам номера обеих этих областей известны)
//отрисовка общего полотна, передача прямоугольных областей
//определение прямоугольных областей, по умолчанию экран делится на 6 областей по - горизонтали, 5 областей по - вертикали
deskHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
deskWidth = System.Windows.SystemParameters.PrimaryScreenWidth;
rectangleWidth = Convert.ToInt32(Math.Truncate(deskWidth / MainWindow.widthSquares));
rectangleHeight = Convert.ToInt32(Math.Truncate(deskHeight / MainWindow.heightSquares));
int xRectBegin, yRectBegin;
int xRectEnd, yRectEnd;
int xRectBegin1, yRectBegin1;
if (beginPoint.X <= rectangleWidth * (MainWindow.widthSquares+1))
{
yRectBegin1 = rectangleHeight * Convert.ToInt32(Math.Ceiling((double)(beginPoint.Y / rectangleHeight)));
}
else
{
yRectBegin1 = rectangleHeight * (MainWindow.widthSquares+1);
}
xRectBegin1 = rectangleWidth * Convert.ToInt32(Math.Ceiling((double)(beginPoint.X / rectangleWidth)));
//то же самое для y - координаты
if (endPoint.X <= rectangleWidth * (MainWindow.widthSquares+1))
{
yRectEnd = rectangleHeight * Convert.ToInt32(Math.Ceiling((double)(endPoint.Y / rectangleHeight)));
}
else
{
yRectEnd = rectangleHeight * (MainWindow.widthSquares+1);
}
xRectEnd = rectangleWidth * Convert.ToInt32(Math.Ceiling((double)(endPoint.X / rectangleWidth)));
MainWindow.GetCursorPos(out currentPoint);
//точка попала в один из 6 прямоугольников
if (currentPoint.X <= rectangleWidth * (MainWindow.widthSquares+1))
{
xRectBegin = rectangleWidth * Convert.ToInt32(Math.Ceiling((double)(currentPoint.X / rectangleWidth)));
}
else
//разрешение экрана не может всегда быть кратно 6, поэтому предусматриваем вариант, когда точка попала в 7 квадрат
{
xRectBegin = rectangleWidth * (MainWindow.widthSquares+1);
}
yRectBegin = rectangleHeight * Convert.ToInt32(Math.Ceiling((double)(currentPoint.Y / rectangleHeight)));
beginArea = new Rect(xRectBegin, yRectBegin, rectangleWidth, rectangleHeight);
endArea = new Rect(xRectEnd, yRectEnd, rectangleWidth, rectangleHeight);
beginArea1 = new Rect(xRectBegin1, yRectBegin1, rectangleWidth, rectangleHeight);
}
private void reCountBeginRect()
{
int xRectBegin, yRectBegin;
MainWindow.GetCursorPos(out currentPoint);
if (currentPoint.X <= rectangleWidth * (rectangleWidth+1))
{
xRectBegin = rectangleWidth * Convert.ToInt32(Math.Ceiling((double)(currentPoint.X / rectangleWidth)));
}
else
//разрешение экрана не может всегда быть кратно 6, поэтому предусматриваем вариант, когда точка попала в 7 квадрат
{
xRectBegin = rectangleWidth * (rectangleWidth+1);
}
yRectBegin = rectangleHeight * Convert.ToInt32(Math.Ceiling((double)(currentPoint.Y / rectangleHeight)));
beginArea = new Rect(xRectBegin, yRectBegin, rectangleWidth, rectangleHeight);
}
public void Canvas_mouse_move(object sender, RoutedEventArgs e)
{
//часть, отвечающая за остлеживание расположения на экране находится здесь, вызывается при генерации события mouse_move над элементом canvas
//нужно для динамического отображения границ прямоугольника, с которого начинается движение маршрута
//отслеживание нужно производить только тогда, когда запись еще не начата
//пересчет значений для отображения начального прямоугольника
reCountBeginRect();
if (parent.IsRecording == false)
{
beginRect.Width = beginArea.Width;
beginRect.Height = beginArea.Height;
beginRect.Stroke = new SolidColorBrush(Colors.Gray);
beginRect.Fill = new SolidColorBrush(Colors.Gray);
Canvas.SetLeft(beginRect, beginArea.X);
Canvas.SetTop(beginRect, beginArea.Y);
}
}
public void Canvas_click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}
Текущее применение приложения:
Данное приложение было изначально разработано для тестирования наличия и степени сложности обхода антифрод систем, которые могут быть установлены администраторами веб-ресурсов и веб-приложений. Приложений было успешно протестировано на биткоин – кранах для автоматического и полуавтоматического (не решает капчу) сбора криптовалюты с кранов.
В списке протестированных ресурсов есть приложения, которые используют Вебвизор от Яндекса для треккинга и сбора информации от пользователей (определено с помощью Wappalyzer):
Ссылка скрыта от гостей
Ссылка скрыта от гостей
Ссылка скрыта от гостей
Ссылка скрыта от гостей
Ссылка скрыта от гостей
Вариант дальнейшего развития приложения:
- трансформировать приложений с UI – компонентами в .dll – библиотеку C#, из которой возможно напрямую вызывать методы по управлению курсором в простом виде (на входе координаты начальной, конечной точки и способ перемещения);
- использовать .dll – библиотеку для создания плагина для студии по автоматизации задач SEO – ZennoPoster (имеется поддержка исполнения и компиляции C# - кода).
Архив Cursor Path Recorder:
В приложении к посту прилагается исполняемый файл WPF - приложения CursorPathRecorder.exe, список заготовленных путей для разрешения экрана 1366*768 в файле serialized_ways.xml.
Выполнять проверку работоспособности приложения сильно рекомендуется на экранах с разрешением 1366*768 (на этом разрешении изначально разрабатывалось и для него были записаны все пути).
Ссылка скрыта от гостей