Введение в реверсинг с нуля, используя IDA PRO. Часть 65. Часть 2.

Дата публикации 11 июн 2019 | Редактировалось 4 июл 2019
1.png

Но мы можем считать, что программа возвращает значение, которое является случайным и увеличивается, и это значение делится на константу 0x7D0.

2.png

И результат сохраняется в EAX, остаток - в EDX.

3.png

Поэтому COUNTER_GUITA на этом этапе будет меньше, чем CONTADOR_GUITA₂.

4.png

И когда COUNTER_GUITA будет меньше 30000, а COUNTER_GUITA_2 будет больше, программа изменит вывод на желтый, то же самое произойдет, когда COUNTER_GUITA будет меньше 10000, а COUNTER_GUITA_2 больше 10000.

До тех пор пока COUNTER_GUITA не станет равным нулю, и программа перейдет к красному блоку, который завершает программу.

5.png


Мы видим, что все это заставляет программу просто запускаться. Это является ее потоком. Теперь мы должны увидеть главную функцию, то есть ту, которая создает этот поток, чтобы увидеть, есть ли у нас возможность повлиять на что-то здесь, чтобы остановить счетчик, потому что здесь «легально» мы видели, что не можем ни на что повлиять.

Хорошо. Давайте начнем анализировать главную функцию, где создан поток, который мы видели ранее.

6.png

Здесь мы видим, что с помощью SUB RSP, RAX, резервируются 0x1088 байтов пространства в стеке для локальных переменных, буферов и т.д. Мы также переименовываем переменную, в которой хранится COOKIE.

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

7.png

Первое, что делаете программа в первом вызове, это вызывает функцию WSASTARTUP.

8.png


IDA показывает нам аргументы. Первый из которых передается в ECX, и это версия. Здесь он устанавливается в 0x202 и сохраняется в указанной переменной WVERSIONREQUESTED, затем ECX читается как первый аргумент.

Второй аргумент, который передается через EDX, является структурой типа WSADATA.

9.png

Если мы перейдем на вкладку структуры, мы увидим это

10.png

Также в LOCAL TYPES.

11.png

Мы видим, что в начале переменная устанавливает в ноль. Мы будем называть ее FLAG_CORRECT, потому что если она равна 1, то это потому, что все в порядке.

12.png

Переменная равна 1, если поле WVERSION, которое было заполнено при вызове WSASTARTUP, равна переменной WVERSIONREQUESTED, которую мы передали ему в качестве аргумента.

Если они равны, программа устанавливает переменную FLAG_CORRECT в единицу, отмечая, что ошибки нет, и возвращает ее в EAX при выходе из этой функции в качестве возвращаемого значения.

Я переименовал функцию в нечто более близкое к тому, что она делает

13.png

Если переменная равно 1, все в порядке, и программа продолжается здесь

14.png

Первый аргумент в RCX - это адрес строки "0.0.0.0"

15.png

Это произойдет на моей машине по адресу 0X13F7ED238. Не путайте с именем A0000.

IDA всегда помещает строки в имя, которое начинается с A, а затем имеет некоторое описание строки, например, означающее, что оно указывает “A 0 0 0 0

16.png

В опциях есть префикс А, и он создаст имя. Даже если эта опция не может быть изменена и имеет немедленный эффект, это будет сделано при следующем анализе.

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

17.png

18.png

Я делаю двойной щелчок здесь.

19.png

Отлично, продолжаем.

EDX будет иметь значение 41414, которое будет портом, в котором программа будет прослушиваться.

20.png

21.png

NETSTAT -ano в консоли с правами администратора покажет нам процессы и порты. В этом случае вы видите 41414.

И третий аргумент в R8 это указатель на QWORD с именем S

22.png

Мы видим другую переменную, которая сохранит флаг, если программа может выполнить успешное прослушивание. Поэтому мы переменовываем переменную в FLAG_LISTEN

23.png

Затем вызовите HTONS, чтобы перевести значение порта в BIG_ENDIAN (darlo vuelta en criollo)

24.png

Существует также переменная имени типа SOCKADDR.

25.png

В структурах.

26.png

И в LOCAL TYPES.

27.png

Затем вызывается сокет.

28.png

29.png

Мы видим, что в поле SA_FAMILY сохраняется 2, и затем три аргумента для сокета передаются непосредственно как константы, а в AF, что является семейством адресов, программа передает 2 напрямую. (IPV4)

30.png

Тип это 1 (SOCK_STREAM) что соответствует TCP.

31.png

И протокол будет нулевым (НЕ ОПРЕДЕЛЕН)

32.png

И возвращаемое значение будет дескриптором сокета. (То, что мы обычно называем дескриптором сокета)

33.png

Затем вызывается BIND с тремя аргументами: длина имени структуры SOCKADDR, затем указатель на то же имя структуры и сокет S.

34.png

35.png

Если вы поместите BIND в этот порт, вы получите ноль. Если вы не получите ошибку, которая видна в таблице с информацией.

Если программа получит ноль, она вызовет LISTEN.

36.png

Здесь мы видим информацию, что один из аргументов - это сокет, а другой - число, называемое BACKLOG, которое в нашем случае будет равно нулю.

37.png

Здесь есть нулевое значение, что делает

38.png

Также, если все правильно, программа возвратит нуль.

39.png

Сокет, который был в локальной переменной S, сохранит его в содержимом P_S для использования в родительских функциях

40.png

Я дал имя функции.

41.png

Если возвращается 1, программа продолжает выполнение здесь.

42.png

Первый вызов - эта функция без аргументов.

43.png

Посмотрим что делает эта функция.

44.png

Мы видим, что она не имеет аргументов, непосредственно резервирует пространство для переменных с помощью Sub RSP, 0x168.

45.png

Мы видим вызов MEMSET с аргументом 0x20. Другими словами должен заполниться буфер, длина которого в аргументе SIZE равна 0x100. Также в IDA, если я перехожу к статическому представлению стека, я могу сделать это, дважды щелкнув по CHARACTER или любой переменной.

46.png

Я вижу пустое пространство в буфере. Если щелкнуть правой кнопкой мыши и выбрать ARRAY мне говорится, что длина равна 256.

47.png

Здесь существует буфер с именем CHARACTER размером 256 байт или 0x100H.

48.png

Мы видим, что цикл будет повторяться, пока счетчик не достигнет 0x50.

49.png

50.png

Ещё раз получается хэндл вывода и вызывается WRITECONSOLEOUTPUTCHARACTERA,

Если я запускаю программа, в этом я не вижу записи буфера со значением 0x2020 в каждом слове, так как буфер инициализируется пробелами 0x20. Это просто нужно для рисования в консоли, Здесь нет ничего интересного нет. Продолжаем.

51.png

Затем устанавливается позиция курсора, обнуляя поля X и Y, передавая их как DWORD, поскольку они являются последовательными, и каждое из них является словом.

52.png

Затем создаётся поток, который мы уже проанализировали с помощью CREATETHREAD, передав его в качестве начального адреса STARTADDRESS. Эта функция уже проанализирована.

53.png

После запуска потока, программа напечатает TCP SERVER ACTIVATED.

54.png

Y ya empezaría la parte interesante, normalmente uno en un server buscaría el recv y empezaría a reversear desde allí, pero acá la idea es aprender y ser detallado para practicar, así que seguimos.

55.png

Перемещается курсор, так как Y теперь равен 1.

56.png

Происходит вывод “WAITING FOR CLIENT CONNECTIONS” и вызывается функция ACCEPT.

57.png

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

58.png

Здесь находятся 3 аргумента: указатель на ADDRLEN, в данном случае 0x10, указатель на ADDR, который получает адрес того, что подключается, и последний - сокет S. В регистре RAX возвращается дескриптор этого соединения, который был установлен.

59.png

Мы видим, что если я запущу сервер и установлю BP при возврате ACCEPT, то IDA не остановится, если я не отправлю правильный пакет на правильный порт, как в этом случае. Здесь отправил пакет и отладчик остановился.

60.png

Затем вызывается другая похожая функция, которая рисует что-то в консоли.

61.png

Мы это уже проходили, я не собираюсь анализировать это снова.

62.png

Здесь печатается NEW CONECTION ACCEPTED и идет переход в функцию в RECV, где начинается то что нужно, поскольку, когда она получает данные, которые я могу отправить ей, если в программе есть какая-либо уязвимость, она может на нее повлиять.

Мы видим, что длина того, что вы можете получить, будет максимум 0x1000, флаги будут равны нулю, то, что я отправлю в буфере BUF, будет сохранено, и первым аргументом в RCX будет HANDLE_CONNECTION.

63.png

Если все прошло хорошо, возвращаемым значением будет количество полученных байтов.

64.png

Давайте посмотрим длину буфера, куда программа будет получать данные.

65.png

66.png

Так что все в порядке, буфер не переполнится.

67.png

Мы видим, что переменная ADDRLEN, которая в ACCEPT использовала ее для длины ADDR, теперь повторно использует ее для получения количества полученных байтов.

68.png

69.png

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

После печати количества полученных байтов идет функция. Посмотрим, что она делает.

70.png

Она имеет два аргумента. Первый в ECX - количество полученных байтов, а второй в RDX - указатель на буфер, в котором я храню данные, которые отправляю.

71.png

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

Если бы я хотел распространить с SET TYPE, то делаю так.

__INT64 __USERCALL FUNCION_2@<RAX>(INT CANTIDAD_BYTES_RECIBIDOS@<RCX>, CHAR * P_BUF@<EDX>);

Здесь нужно распространить переменные.

72.png

Мы видим, что программа сравнивает с помощью STRNCMP первые 6 байтов данных, которые вы отправляете, со строкой "Hello".

73.png

74.png

Если функция возвращает ноль, то они равны, и флаг установлен в 1.

Поскольку при выходе из этой функции, если она возвращает 1, происходит рукопожатие, я переименую функцию в CHECK_HANDSHAKE.

75.png

76.png

Хорошо, как мы видим, если мы передали “Hello” будет действительным рукопожатие.

Затем программе возвращает в SEND строку, которая начинается с “Hi и несколько пробелов” длиной 8, чтобы не путать с другим BUF, который находится в секции данных. Я собираюсь переименовать его, чтобы избежать проблем.

77.png

78.png

Теперь смотрится лучше. Длина данных, которые вы мне отправите, будет 8. Здесь нет проблем.

79.png

Здесь программа сохраняет отправленные байты, затем выводит “WAITING FOR REQUEST”, происходит вызов функции, а затем соединение с клиентом закрывается, и программа возвращается, чтобы принять ожидание другого, поэтому все должно быть приготовлено в этой последней функции, которая находится до CLOSESOCKET.

80.png

Мы видим, что у него есть единственный аргумент - HANDLE_CONNECTION.

После сохранения аргумента резервируется место для переменных

81.png

Тот, кто хочет распространять нужно написать так

__INT64 __USERCALL FUNCION_2@<RAX>(__INT64 HANDLE_CONEXION@<RCX>);

82.png

Затем вызывается другой RECV с тем же HANDLE_CONNECTION, но длиной 0x10. Давайте посмотрим буфер, в который поступают данные.

83.png

Поскольку размер буфера составляет 0x10 т.е. 16 байтов, он будет включать переменную BUF и три переменные 224, 220 и 21C.

84.png

Если я хочу, я могу создать структуру для этого буфера.

Здесь у меня есть 16 байтов длиной с 4 DWORDS, тогда я увижу, какое конкретное имя поставить каждому из них.

85.png

И переменную переименую в MY_BUF.

86.png

Здесь сохраняется количество полученных байтов, которых может быть не более 0x10 т.е. 16.

87.png

Сравнивается количество полученных байтов с 0x10, и оно должно быть именно этим значением, потому что, если оно не меньше, оно будет равно или больше, и выше оно не может быть, потому что у recv было максимум 0x10, поэтому он принимает только 0x10.

88.png

Мы видим, что программа собирается выполнить еще одно RECV с тем же HANDLE_CONNECTION, но в этом случае размер равен CAMPO_0 того, что я отправил в предыдущем пакете из 16 байтов, и программа сохранит результат в VAR_218, которая будет буфером для этого RECV.

89.png

Мы видим, что этот буфер составляет 512 байт, и я мог бы отправить ему больше данных, так как размер RECV, который я обрабатываю через CAMPO_0. Проблема заключается в том, что вы перезаписываете COOKIE, и это приводит к закрытию программы, и в 64-битных файлах нет исключения в стеке, так что это пока не приведет к переполнению.

90.png

91.png

Также, я переименую переменную в BUF_512_TERCER_RECV.

92.png

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

93.png

Y bueno quedo largo pero yo me entiendo jeje, con las variables reusadas pasa esto.

94.png

Затем выполняется сравнение со знаком, просто следите, если FIELD_3 меньше нуля.

95.png

Я переименую переменную.

Затем идет сравнение без знака, только если CAMPO_2 меньше или равно 0x200.

96.png

Я переименую её.

97.png

Кроме того, сообщения об ошибках дают мне представление об именах полей, CAMPO_3 будет смещением, а CAMPO_2 будет равно уровню.

98.png

Сейчас смотрится лучше.

99.png

Поле 1 является операцией сравнения с 0x11111111 или 0x22222222, и если , то программа идет на INVALID OPERATION, поэтому мы переименовываем его. Мы видим структуру второго пакета.

Мы видим структуру второго пакета.

100.png

0 2.940
yashechka

yashechka
Ростовский фанат Нарвахи

Регистрация:
2 янв 2012
Публикаций:
90