Конкурс Имитация поведенческих факторов курсора мыши C#

Статья для участия в конкурсе программистов

WPF - приложение Cursor Path Recorder. Привет всем, пожалуй, начну!

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

Приложение предназначено для автоматического управления системным курсором. Позволяет помочь в обходе защиты Google Recaptcha v3, которая предполагает анализ поведенческих факторов пользователя как одного из основных при выдаче “fraud score”.
Позволяет помочь в обходе запись Вебвизора, являющегося частью Яндекс.Метрики.
Также приложение может использоваться для любых иных задач, связанных с автоматизацией управления системным курсором, например, при тестировании веб-приложений, игр, написании ботов, в которых нужна имитация действий настоящего пользователя, для управления браузером на веб-ресурсах, которые используют антифрод-системы по отслеживанию пользовательского поведения. Из последних известных примеров можно привести: Hubstaff Tracker или Upwork Tracker, предназначенные для отслеживания работы исполнителей на биржах фриланса по довольно топорным метрикам.

Обзор интерфейса пользователя:
С первого взгляда интерфейс может показаться перегруженным элементами управления, но каждый из них используется для записи различного поведения пользователя, поэтому обо всем по порядку:

path_recorder_screen.png


Список начальных квадратов: перечень с единичным выбором, отвечает за точку, из которой начинается записываемый путь мыши;

Список конечных квадратов: то же самое, только для конечной точки пути курсора мыши;

Квадратов по вертикали/горизонтали, кнопка “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 (на этом разрешении изначально разрабатывалось и для него были записаны все пути).

 

alexej

Green Team
15.10.2018
55
18
BIT
0
Помнится на сайте ZennoPosterа за немалую сумму продавалась подобная библиотека.
 
П

Петр Чере

Проект действительно стоящий, особенно заточка под Zennoposter, это было бы очень актуально.
 

Infosimple

New member
12.12.2019
1
0
BIT
0
Всем привет, нужна помощь в создание dll библиотеки из этой темы.
 

sps75

New member
20.03.2021
1
0
BIT
0
Очень актуальная тема. Есть ли по ней какие-то решения для Зено?
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!