Как работает 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 байт слева от среза, выдавая их за найденную сигнатуру.