• Бесплатный ВЕБИНАР по OSINT с Екатериной Тьюринг: ➡️9 февраля в 19:00 (мск) пройдет урок

    Как безопасно искать информацию в открытых источниках

    🔥 Записаться 🔥

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

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

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

 
Помнится на сайте ZennoPosterа за немалую сумму продавалась подобная библиотека.
 
Проект действительно стоящий, особенно заточка под Zennoposter, это было бы очень актуально.
 
Всем привет, нужна помощь в создание dll библиотеки из этой темы.
 
Очень актуальная тема. Есть ли по ней какие-то решения для Зено?
 
Мы в соцсетях:

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