Как работает DefenderCheck

Недавно в чате спросили: будет ли у SignFinder поиск сигнатур как у DefenderCheck?

Утверждается, что утилита показывает байты сигнатуры, узнавая их от антивируса. Кажется, кому-то удалось вскрыть формат базы и написать удобную оболочку, но это не так. Внутри простой, но любопытный, алгоритм.

Модель поиска сигнатуры

Я переписал код, так чтобы с ним легко было играть, меняя расположение сигнатуры:

class DefenderCheck:

	def __init__(self, sign):
		self.sign = sign
		self.scan_count = 0
		self.last_good = 0
		self.show_size = 8

	def IsDetected(self, part_data):
		self.scan_count += 1
		return self.sign in part_data

	def DumpSign(self):
		offset = max(self.part_size-self.show_size, 0)
		return self.file_data[offset:self.part_size]

	def DecreaseSize(self):

		new_len = int((self.part_size - self.last_good) / 2 + self.last_good)

		if (self.part_size == new_len + 1):
			demo_part = self.DumpSign()
			raise ValueError(f"End of bad bytes at offset {self.part_size} -> {demo_part}")

		return new_len

	def IncreaseSize(self):

		new_len = int((self.file_size - self.part_size) / 2 + self.part_size)

		if new_len == self.file_size - 1:
			raise ValueError("Exhausted the search. The binary looks good to go!")

		return new_len

	def _Search(self, file_data):
	
		self.file_data = file_data

		self.file_size = len(file_data) 
		self.part_size = int(self.file_size / 2)
		
		while True:

			part_data = file_data[0:self.part_size]

			is_detected = self.IsDetected(part_data)
			detect_str = 'detect' if is_detected else 'clean'

			print(f"\n[{self.scan_count}] File[0:{self.part_size}] -> {detect_str}")

			if is_detected:
				self.part_size = self.DecreaseSize()
			else:
				self.last_good = len(part_data)
				self.part_size = self.IncreaseSize()
	
	def Search(self, file_data):	

		try:
			self._Search(file_data)		
		except ValueError as msg:
			print(f'\n[!] {msg}')


if __name__ == '__main__':
	
	defcheck = DefenderCheck('SIGN')

	defcheck.Search('___________SIGN_______')

Особенности алгоритма

Логика проста, если файл детектируется, отрезаем от него конец, если детект остался - сигнатура находится до места отрыва. Мне кажется, метод был и раньше, конец отрезался через POSIX.truncate / WinApi.SetEndOfFile в других имплементациях той же мысли.

Удивительно, но обрыв файла не смущает сканер, он честно ищет свои сигнатуры. Тестируем разные точки надреза, если при добавлении байта файл детектируется, а без него - нет, значит это конец сигнатуры.

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

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

По всей видимости, это ошибка. В логах видно, что он делает лишние проверки: на [2] шаге становится известно, о детектировании начиная с 16 байта, но на [4] идёт проверка 17

[1] File[0:11] -> clean

[2] File[0:16] -> detect

[3] File[0:13] -> clean

[4] File[0:17] -> detect

[5] File[0:15] -> detect

[!] End of bad bytes at offset 15 -> ____SIGN

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

Комментарии