
Скриншот программы IrfanView
IrfanView - один из самых популярных просмотрщиков графики и в особом представлении не нуждается. IrfanView прекрасно работает без какой-либо регистрации, можно даже сказать, что он бесплатный. Регистрационный ключ высылается автором в качестве благодарности за добровольные пожертвования, в остальных случаях программа пишет, что зарегистрирована на "YOU". Отправку пожертвований я оставляю целиком на вашей совести, а сейчас мы просто разберем алгоритм регистрации программы.
Скачиваем
дистрибутив, устанавливаем, смотрим что у нас получилось. Файл i_view32.exe упакован UPX, снимаем его им же самим, upx -d i_view32.exe, тут никаких сюрпризов. Дальше в работу традиционно подключается дизассемблер, мы же пока посмотрим на то, как программа регистрируется и как реагирует на ввод неправильных регистрационных данных.

Строка сообщения в ресурсах
Строка сообщения о неправильной регистрации находится в ресурсах файла. Ее индекс равен 1238 или 4D6h в шестнадцатеричной системе счисления. Поищем это значение в листинге дизассемблера:
.text:0047FBC1 lea edx, [esp+0B1Ch+Str]
.text:0047FBC8 lea eax, [esp+0B1Ch+Text]
.text:0047FBCF push edx ; Str
.text:0047FBD0 push eax ; int
; Вызвать функцию проверки регистрационных данных
.text:0047FBD1 call sub_44B370
.text:0047FBD6 add esp, 8
; Если она вернула EAX=0, то регистрация неправильная
.text:0047FBD9 test eax, eax
.text:0047FBDB jnz short loc_47FC04
; Загрузить строчку из ресурсов и вывести сообщение о неправильной регистрации
.text:0047FBDD mov ecx, hInstance
.text:0047FBE3 push eax ; int
.text:0047FBE4 push eax ; int
.text:0047FBE5 push eax ; int
.text:0047FBE6 push eax ; int
; Индекс строки из ресурсов
.text:0047FBE7 push 4D6h ; uID
.text:0047FBEC push ecx ; hInstance
.text:0047FBED call sub_46EBC0
.text:0047FBF2 add esp, 18h
.text:0047FBF5 xor eax, eax
.text:0047FBF7 pop edi
.text:0047FBF8 pop esi
.text:0047FBF9 pop ebp
.text:0047FBFA pop ebx
.text:0047FBFB add esp, 0B0Ch
.text:0047FC01 retn 10h
.text:0047FC04 ; -----------------------------------------
.text:0047FC04 loc_47FC04:
; Сохранить регистрационные данные в ini-файле
.text:0047FC04 mov esi, ds:WritePrivateProfileStringA
.text:0047FC0A lea edx, [esp+0B1Ch+Data]
.text:0047FC11 lea eax, [esp+0B1Ch+Text]
.text:0047FC18 push edx ; lpFileName
.text:0047FC19 push eax ; lpString
.text:0047FC1A push offset aName ; "Name"
.text:0047FC1F push offset aRegistration ; "Registration"
.text:0047FC24 call esi ; WritePrivateProfileStringA
.text:0047FC26 lea ecx, [esp+0B1Ch+Data]
.text:0047FC2D lea edx, [esp+0B1Ch+Str]
.text:0047FC34 push ecx ; lpFileName
.text:0047FC35 push edx ; lpString
.text:0047FC36 push offset aCode ; "Code"
.text:0047FC3B push offset aRegistration ; "Registration"
.text:0047FC40 call esi ; WritePrivateProfileStringA
.text:0047FC42 push 0
.text:0047FC44 push 0
.text:0047FC46 push 0
.text:0047FC48 push 0
.text:0047FC4A push 4D7h
.text:0047FC4F jmp loc_481294
Код понятен. Сперва вызывается функция проверки, затем или выводится окно с сообщением о неправильной регистрации, или регистрационные данные записываются в ini-файл. Первый вариант решения, как обычно, патч функции проверки, чтобы она всегда возвращала EAX=1. Классический патч по адресу 0044B370 из двух команд MOV EAX,1 / RET и программа с радостью примет любой серийный номер.
Но так придется патчить каждую новую версию программы, что в конце концов надоест. Можно, конечно, сделать универсальный патч и даже inline-патч поверх упаковщика, но стоят ли такие усилия победы над бесплатной программой? Поэтому посмотрим, как проверяется регистрационный номер. Процедура очень простая, несмотря на множество арифметических и логических операций, весь код наглядный и сложности в анализе не представляет. Сперва высчитывается сумма кодов символов регистрационного имени, после чего значение дополнительно преобразуется несколькими операциями и после этого переводится в текстовый вид.
.text:0044B370 mov eax, [esp+Str]
.text:0044B374 sub esp, 14h
.text:0044B377 push ebx
.text:0044B378 push ebp
.text:0044B379 push esi
.text:0044B37A push edi
.text:0044B37B push eax ; Str
.text:0044B37C xor ebx, ebx
.text:0044B37E call _atol
.text:0044B383 mov esi, [esp+28h+arg_0]
.text:0044B387 mov ebp, eax
.text:0044B389 mov edi, esi
.text:0044B38B or ecx, 0FFFFFFFFh
.text:0044B38E xor eax, eax
.text:0044B390 add esp, 4
.text:0044B393 xor edx, edx
.text:0044B395 repne scasb
.text:0044B397 not ecx
.text:0044B399 dec ecx
.text:0044B39A test ecx, ecx
.text:0044B39C jle short loc_44B3B5
; Подсчитать сумму ASCII-кодов всех символов регистрационного имени
.text:0044B39E loc_44B39E:
.text:0044B39E movsx ecx, byte ptr [edx+esi]
.text:0044B3A2 add ebx, ecx
.text:0044B3A4 mov edi, esi
.text:0044B3A6 or ecx, 0FFFFFFFFh
.text:0044B3A9 xor eax, eax
.text:0044B3AB inc edx
.text:0044B3AC repne scasb
.text:0044B3AE not ecx
.text:0044B3B0 dec ecx
.text:0044B3B1 cmp edx, ecx
.text:0044B3B3 jl short loc_44B39E
.text:0044B3B5 loc_44B3B5:
; Небольшие преобразования полученного значения
.text:0044B3B5 mov eax, 104h
.text:0044B3BA push 0Ah ; Radix
.text:0044B3BC sub eax, ebx
.text:0044B3BE cdq
.text:0044B3BF xor eax, edx
.text:0044B3C1 sub eax, edx
.text:0044B3C3 add eax, 14Ch
.text:0044B3C8 lea edx, ds:0[eax*8]
.text:0044B3CF sub edx, eax
.text:0044B3D1 lea ecx, [eax+edx*4]
.text:0044B3D4 lea edx, [esp+28h+Dest]
.text:0044B3D8 push edx ; Dest
.text:0044B3D9 lea esi, [eax+ecx*2]
.text:0044B3DC shl esi, 3
.text:0044B3DF push esi ; Value
; Перевести полученное значение в строку (десятичное значение)
.text:0044B3E0 call __ltoa
...
Дальше вычисления могут пойти по двум вариантам, в зависимости от полученного в предыдущем коде значения. Если оно больше 0F423Fh (или 999999 в десятичной системе счисления), то выполняется одна ветка алгоритма, если меньше или равно - другая. Зачем так сделано, будет понятно ниже. В нашем случае для регистрационного имени "ManHunter / PCL" предварительное значение будет "623040". Смотрим дальше.
; Значение больше 999999?
.text:0044B3E8 cmp esi, 0F423Fh
; Да, переход
.text:0044B3EE ja loc_44B4E3
; Первая ветка алгоритма
.text:0044B3F4 mov cl, byte ptr [esp+24h+var_10]
.text:0044B3F8 mov al, byte ptr [esp+24h+var_10+1]
.text:0044B3FC mov dl, [esp+24h+Dest+3]
.text:0044B400 mov byte ptr [esp+24h+var_10+2], cl
.text:0044B404 mov cl, [esp+24h+Dest+1]
.text:0044B408 mov [esp+18h], al
.text:0044B40C mov al, [esp+24h+Dest+2]
.text:0044B410 mov byte ptr [esp+24h+var_10+1], dl
.text:0044B414 mov [esp+24h+Dest+2], cl
.text:0044B418 mov ecx, [esp+24h+var_10]
.text:0044B41C and ecx, 0FFh
.text:0044B422 mov [esp+24h+Dest+3], al
.text:0044B426 mov eax, ecx
.text:0044B428 shl eax, 5
.text:0044B42B sub eax, ecx
.text:0044B42D mov ecx, [esp+18h]
.text:0044B431 and ecx, 0FFh
.text:0044B437 lea edx, [eax+eax*2]
.text:0044B43A lea eax, [ecx+ecx*4]
.text:0044B43D shl eax, 3
.text:0044B440 sub eax, ecx
.text:0044B442 sub eax, edx
.text:0044B444 cdq
.text:0044B445 mov ecx, eax
.text:0044B447 xor ecx, edx
.text:0044B449 sub ecx, edx
.text:0044B44B lea eax, [ecx+ecx*4]
.text:0044B44E shl eax, 3
.text:0044B451 sub eax, ecx
.text:0044B453 mov ecx, 9
.text:0044B458 cdq
.text:0044B459 idiv ecx
.text:0044B45B mov eax, dword ptr [esp+24h+Dest+3]
.text:0044B45F and eax, 0FFh
.text:0044B464 add dl, 30h
.text:0044B467 mov byte ptr [esp+24h+var_10+3], dl
.text:0044B46B lea edx, [eax+eax*2]
.text:0044B46E shl edx, 4
.text:0044B471 sub edx, eax
.text:0044B473 mov eax, [esp+24h+var_10+1]
.text:0044B477 and eax, 0FFh
.text:0044B47C lea ecx, [eax+eax*8]
.text:0044B47F lea eax, [eax+ecx*4]
.text:0044B482 lea eax, [edx+eax*2]
.text:0044B485 cdq
.text:0044B486 xor eax, edx
.text:0044B488 sub eax, edx
.text:0044B48A lea ecx, [eax+eax*8]
.text:0044B48D lea eax, [eax+ecx*4]
.text:0044B490 mov ecx, 9
.text:0044B495 shl eax, 1
.text:0044B497 cdq
.text:0044B498 idiv ecx
.text:0044B49A mov ecx, dword ptr [esp+24h+Dest]
.text:0044B49E and ecx, 0FFh
.text:0044B4A4 lea eax, [ecx+ecx*2]
.text:0044B4A7 lea eax, [eax+eax*8]
.text:0044B4AA shl eax, 1
.text:0044B4AC sub eax, ecx
.text:0044B4AE add dl, 30h
.text:0044B4B1 mov byte ptr [esp+24h+var_10], dl
.text:0044B4B5 mov ecx, dword ptr [esp+24h+Dest+1]
.text:0044B4B9 and ecx, 0FFh
.text:0044B4BF lea edx, ds:0[ecx*8]
.text:0044B4C6 sub edx, ecx
.text:0044B4C8 lea edx, [edx+edx*4]
.text:0044B4CB sub eax, edx
.text:0044B4CD cdq
.text:0044B4CE mov ecx, eax
.text:0044B4D0 xor ecx, edx
.text:0044B4D2 sub ecx, edx
.text:0044B4D4 lea eax, [ecx+ecx*2]
.text:0044B4D7 lea eax, [eax+eax*8]
.text:0044B4DA shl eax, 1
.text:0044B4DC sub eax, ecx
.text:0044B4DE jmp loc_44B5D8
.text:0044B4E3 ; -----------------------------------------
; Вторая ветка алгоритма
.text:0044B4E3 loc_44B4E3:
.text:0044B4E3 mov al, byte ptr [esp+24h+var_10+1]
.text:0044B4E7 mov dl, byte ptr [esp+24h+var_10+2]
.text:0044B4EB mov cl, byte ptr [esp+24h+var_10]
.text:0044B4EF mov byte ptr [esp+24h+var_10+2], al
.text:0044B4F3 mov al, [esp+24h+Dest+1]
.text:0044B4F7 mov [esp+18h], dl
.text:0044B4FB mov dl, [esp+24h+Dest+2]
.text:0044B4FF mov [esp+24h+Dest+2], al
.text:0044B503 mov eax, [esp+24h+var_10+2]
.text:0044B507 mov byte ptr [esp+24h+var_10+1], cl
.text:0044B50B and eax, 0FFh
.text:0044B510 mov [esp+24h+Dest+3], dl
.text:0044B514 mov ecx, eax
.text:0044B516 shl ecx, 6
.text:0044B519 sub ecx, eax
.text:0044B51B mov eax, [esp+18h]
.text:0044B51F and eax, 0FFh
.text:0044B524 lea eax, [eax+eax*8]
.text:0044B527 shl eax, 2
.text:0044B52A sub eax, ecx
.text:0044B52C mov ecx, 9
.text:0044B531 cdq
.text:0044B532 xor eax, edx
.text:0044B534 sub eax, edx
.text:0044B536 lea eax, [eax+eax*8]
.text:0044B539 shl eax, 2
.text:0044B53C cdq
.text:0044B53D idiv ecx
.text:0044B53F add dl, 30h
.text:0044B542 mov byte ptr [esp+24h+var_10+3], dl
.text:0044B546 mov eax, [esp+24h+var_10]
.text:0044B54A and eax, 0FFh
.text:0044B54F add eax, 20h
.text:0044B552 lea edx, ds:0[eax*8]
.text:0044B559 sub edx, eax
.text:0044B55B lea eax, [eax+edx*4]
.text:0044B55E lea ecx, [eax+eax*2]
.text:0044B561 mov eax, dword ptr [esp+24h+Dest+3]
.text:0044B565 and eax, 0FFh
.text:0044B56A lea edx, [eax+eax*4]
.text:0044B56D shl edx, 3
.text:0044B570 sub edx, eax
.text:0044B572 lea eax, [ecx+edx*2]
.text:0044B575 cdq
.text:0044B576 xor eax, edx
.text:0044B578 sub eax, edx
.text:0044B57A lea ecx, ds:0[eax*8]
.text:0044B581 sub ecx, eax
.text:0044B583 lea eax, [eax+ecx*4]
.text:0044B586 mov ecx, 9
.text:0044B58B lea eax, [eax+eax*2]
.text:0044B58E cdq
.text:0044B58F idiv ecx
.text:0044B591 mov eax, dword ptr [esp+24h+Dest]
.text:0044B595 and eax, 0FFh
.text:0044B59A add dl, 30h
.text:0044B59D mov byte ptr [esp+24h+var_10], dl
.text:0044B5A1 lea edx, ds:0[eax*8]
.text:0044B5A8 sub edx, eax
.text:0044B5AA lea eax, [eax+edx*4]
.text:0044B5AD mov edx, dword ptr [esp+24h+Dest+1]
.text:0044B5B1 and edx, 0FFh
.text:0044B5B7 mov ecx, edx
.text:0044B5B9 shl ecx, 4
.text:0044B5BC add ecx, edx
.text:0044B5BE shl eax, 1
.text:0044B5C0 lea ecx, [ecx+ecx*4]
.text:0044B5C3 sub eax, ecx
.text:0044B5C5 cdq
.text:0044B5C6 xor eax, edx
.text:0044B5C8 sub eax, edx
.text:0044B5CA lea edx, ds:0[eax*8]
.text:0044B5D1 sub edx, eax
.text:0044B5D3 lea eax, [eax+edx*4]
.text:0044B5D6 shl eax, 1
.text:0044B5D8 loc_44B5D8:
.text:0044B5D8 cdq
.text:0044B5D9 mov ecx, 9
.text:0044B5DE mov [esp+24h+var_B], 0
.text:0044B5E3 idiv ecx
.text:0044B5E5 add dl, 30h
.text:0044B5E8 mov [esp+24h+Dest+1], dl
.text:0044B5EC lea edx, [esp+24h+Dest]
; Преобразовать строку в десятичное число
.text:0044B5F0 push edx ; Str
.text:0044B5F1 call _atol
На основании подсчитанного ранее значения выполняются его преобразования, а строка серийного номера дополняется несколькими символами. Проверка значения нужна для того, чтобы определить, в какие позиции записывать дополнительные символы, после 6-й или после 7-й цифры. Код обеих веток алгоритма линейный и настолько чистый, что его можно копировать в генератор ключей практически без изменений. В основном это двоичная арифметика. Небольшие нюансы есть при манипуляциях с символами, но они также становятся понятны при прохождении кода под отладчиком. Там же в отладчике по адресу 0044B5F0 регистр EDX указывает на правильный серийный номер для введенного имени. Можно на этом и остановиться, получив валидную пару, можно сделать сниффер ключей, а можно выдрать код из проверки и написать свой генератор.
Валидной парой, например, будет регистрационное имя "ManHunter / PCL" и серийный номер "642300400". Попробуем зарегистрировать с их помощью IrfanView. Все проходит на ура, программа принимает серийник и благодарит за регистрацию.

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