第79章 “QR9”:魔方态加密模型

非专业的加密系统偶尔会让人感到匪夷所思。

本章将与您共同逆向分析一款已经找不到源代码的数据加密软件。

首先利用IDA的导出功能,导出这款程序的指令清单:

.text:00541000 set_bit          proc near                 ; CODE XREF: rotate1+42
.text:00541000                                            ; rotate2+42 ...
.text:00541000
.text:00541000 arg_0            = dword ptr  4
.text:00541000 arg_4            = dword ptr  8
.text:00541000 arg_8            = dword ptr  0Ch
.text:00541000 arg_C            = byte  ptr  10h
.text:00541000
.text:00541000                  mov     al, [esp+arg_C]
.text:00541004                  mov     ecx, [esp+arg_8]
.text:00541008                  push    esi
.text:00541009                  mov     esi, [esp+4+arg_0]
.text:0054100D                  test    al, al
.text:0054100F                  mov     eax, [esp+4+arg_4]
.text:00541013                  mov     dl, 1
.text:00541015                  jz      short loc_54102B
.text:00541017                  shl     dl, cl
.text:00541019                  mov     cl, cube64[eax+esi*8]
.text:00541020                  or      cl, dl
.text:00541022                  mov     cube64[eax+esi*8], cl
.text:00541029                  pop     esi
.text:0054102A                  retn
.text:0054102B
.text:0054102B loc_54102B:                                ; CODE XREF: set_bit+15
.text:0054102B                  shl     dl, cl
.text:0054102D                  mov     cl, cube64[eax+esi*8]
.text:00541034                  not     dl
.text:00541036                  and     cl, dl
.text:00541038                  mov     cube64[eax+esi*8], cl
.text:0054103F                  pop     esi
.text:00541040                  retn
.text:00541040 set_bit          endp
.text:00541040
.text:00541041                  align 10h
.text:00541050
.text:00541050 ; =============== S U B R O U T I N E =============
.text:00541050
.text:00541050
.text:00541050 get_bit          proc near                 ; CODE XREF: rotate1+16
.text:00541050                                            ; rotate2+16 ...
.text:00541050
.text:00541050 arg_0            = dword ptr  4
.text:00541050 arg_4            = dword ptr  8
.text:00541050 arg_8            = byte  ptr  0Ch
.text:00541050
.text:00541050                  mov     eax, [esp+arg_4]
.text:00541054                  mov     ecx, [esp+arg_0]
.text:00541058                  mov     al, cube64[eax+ecx*8]
.text:0054105F                  mov     cl, [esp+arg_8]
.text:00541063                  shr     al, cl
.text:00541065                  and     al, 1
.text:00541067                  retn
.text:00541067 get_bit          endp
.text:00541067
.text:00541068                  align 10h
.text:00541070
.text:00541070 ; =========== S U B R O U T I N E ============
.text:00541070
.text:00541070
.text:00541070 rotate1          proc near              ; CODE XREF: rotate_all_with_password+8E
.text:00541070
.text:00541070 internal_array_64= byte ptr -40h
.text:00541070 arg_0            = dword ptr  4
.text:00541070
.text:00541070                  sub     esp, 40h
.text:00541073                  push    ebx
.text:00541074                  push    ebp
.text:00541075                  mov     ebp, [esp+48h+arg_0]
.text:00541079                  push    esi
.text:0054107A                  push    edi
.text:0054107B                  xor     edi, edi               ; EDI is loop1 counter
.text:0054107D                  lea     ebx, [esp+50h+internal_array_64]
.text:00541081
.text:00541081 first_loop1_begin:                              ; CODE XREF: rotate1+2E
.text:00541081                  xor     esi, esi               ; ESI is loop2 counter
.text:00541083
.text:00541083 first_loop2_begin:                              ; CODE XREF: rotate1+25
.text:00541083                  push    ebp                    ; arg_0
.text:00541084                  push    esi
.text:00541085                  push    edi
.text:00541086                  call    get_bit
.text:0054108B                  add     esp, 0Ch
.text:0054108E                  mov     [ebx+esi], al    ; store to internal array
.text:00541091                  inc     esi
.text:00541092                  cmp     esi, 8
.text:00541095                  jl      short first_loop2_begin
.text:00541097                  inc     edi
.text:00541098                  add     ebx, 8
.text:0054109B                  cmp     edi, 8
.text:0054109E                  jl      short first_loop1_begin
.text:005410A0                  lea     ebx, [esp+50h+internal_array_64]
.text:005410A4                  mov     edi,7; EDI is loop1 counter, initial state is 7
.text:005410A9
.text:005410A9 second_loop1_begin:                     ; CODE XREF: rotate1+57
.text:005410A9                  xor     esi, esi       ; ESI is loop2 counter
.text:005410AB
.text:005410AB second_loop2_begin:                     ; CODE XREF: rotate1+4E
.text:005410AB                  mov     al, [ebx+esi]  ; value from internal array
.text:005410AE                  push    eax
.text:005410AF                  push    ebp            ; arg_0
.text:005410B0                  push    edi
.text:005410B1                  push    esi
.text:005410B2                  call    set_bit
.text:005410B7                  add     esp, 10h
.text:005410BA                  inc     esi            ; increment loop2 counter
.text:005410BB                  cmp     esi, 8
.text:005410BE                  jl      short second_loop2_begin
.text:005410C0                  dec     edi            ; decrement loop2 counter
.text:005410C1                  add     ebx, 8
.text:005410C4                  cmp     edi, 0FFFFFFFFh
.text:005410C7                  jg      short second_loop1_begin
.text:005410C9                  pop     edi
.text:005410CA                  pop     esi
.text:005410CB                  pop     ebp
.text:005410CC                  pop     ebx
.text:005410CD                  add     esp, 40h
.text:005410D0                  retn
.text:005410D0 rotate1          endp
.text:005410D0
.text:005410D1                  align 10h
.text:005410E0
.text:005410E0 ; ============ S U B R O U T I N E =========
.text:005410E0
.text:005410E0
.text:005410E0 rotate2          proc near                ; CODE XREF: rotate_all_with_password+7A
.text:005410E0
.text:005410E0 internal_array_64= byte ptr -40h
.text:005410E0 arg_0            = dword ptr  4
.text:005410E0
.text:005410E0                  sub     esp, 40h
.text:005410E3                  push    ebx
.text:005410E4                  push    ebp
.text:005410E5                  mov     ebp, [esp+48h+arg_0]
.text:005410E9                  push    esi
.text:005410EA                  push    edi
.text:005410EB                  xor     edi, edi         ; loop1 counter
.text:005410ED                  lea     ebx, [esp+50h+internal_array_64]
.text:005410F1
.text:005410F1 loc_5410F1:                               ; CODE XREF: rotate2+2E
.text:005410F1                  xor     esi, esi         ; loop2 counter
.text:005410F3
.text:005410F3 loc_5410F3:                               ; CODE XREF: rotate2+25
.text:005410F3                  push    esi              ; loop2
.text:005410F4                  push    edi              ; loop1
.text:005410F5                  push    ebp              ; arg_0
.text:005410F6                  call    get_bit
.text:005410FB                  add     esp, 0Ch
.text:005410FE                  mov     [ebx+esi], al    ; store to internal array
.text:00541101                  inc     esi              ; increment loop1 counter
.text:00541102                  cmp     esi, 8
.text:00541105                  jl      short loc_5410F3
.text:00541107                  inc     edi              ; increment loop2 counter
.text:00541108                  add     ebx, 8
.text:0054110B                  cmp     edi, 8
.text:0054110E                  jl      short loc_5410F1
.text:00541110                  lea     ebx, [esp+50h+internal_array_64]
.text:00541114                  mov     edi, 7  ; loop1 counter is initial state 7
.text:00541119
.text:00541119 loc_541119:                             ; CODE XREF: rotate2+57
.text:00541119                  xor     esi, esi       ; loop2 counter
.text:0054111B
.text:0054111B loc_54111B:                             ; CODE XREF: rotate2+4E
.text:0054111B                  mov     al, [ebx+esi]  ; get byte from internal array
.text:0054111E                  push    eax
.text:0054111F                  push    edi            ; loop1 counter
.text:00541120                  push    esi            ; loop2 counter
.text:00541121                  push    ebp            ; arg_0
.text:00541122                  call    set_bit
.text:00541127                  add     esp, 10h
.text:0054112A                  inc     esi            ; increment loop2 counter
.text:0054112B                  cmp     esi, 8
.text:0054112E                  jl      short loc_54111B
.text:00541130                  dec     edi            ; decrement loop2 counter
.text:00541131                  add     ebx, 8
.text:00541134                  cmp     edi, 0FFFFFFFFh
.text:00541137                  jg      short loc_541119
.text:00541139                  pop     edi
.text:0054113A                  pop     esi
.text:0054113B                  pop     ebp
.text:0054113C                  pop     ebx
.text:0054113D                  add     esp, 40h
.text:00541140                  retn
.text:00541140 rotate2          endp
.text:00541140
.text:00541141                  align 10h
.text:00541150
.text:00541150 ; ============ S U B R O U T I N E ===========
.text:00541150
.text:00541150
.text:00541150 rotate3          proc near      ; CODE XREF: rotate_all_with_password+66
.text:00541150
.text:00541150 var_40           = byte ptr -40h
.text:00541150 arg_0            = dword ptr  4
.text:00541150
.text:00541150                  sub     esp, 40h
.text:00541153                  push    ebx
.text:00541154                  push    ebp
.text:00541155                  mov     ebp, [esp+48h+arg_0]
.text:00541159                  push    esi
.text:0054115A                  push    edi
.text:0054115B                  xor     edi, edi
.text:0054115D                  lea     ebx, [esp+50h+var_40]
.text:00541161
.text:00541161 loc_541161:                                ; CODE XREF: rotate3+2E
.text:00541161                  xor     esi, esi
.text:00541163
.text:00541163 loc_541163:                                ; CODE XREF: rotate3+25
.text:00541163                  push    esi
.text:00541164                  push    ebp
.text:00541165                  push    edi
.text:00541166                  call    get_bit
.text:0054116B                  add     esp, 0Ch
.text:0054116E                  mov     [ebx+esi], al
.text:00541171                  inc     esi
.text:00541172                  cmp     esi, 8
.text:00541175                  jl      short loc_541163
.text:00541177                  inc     edi
.text:00541178                  add     ebx, 8
.text:0054117B                  cmp     edi, 8
.text:0054117E                  jl      short loc_541161
.text:00541180                  xor     ebx, ebx
.text:00541182                  lea     edi, [esp+50h+var_40]
.text:00541186
.text:00541186 loc_541186:                                ; CODE XREF: rotate3+54
.text:00541186                  mov     esi, 7
.text:0054118B
.text:0054118B loc_54118B:                                ; CODE XREF: rotate3+4E
.text:0054118B                  mov     al, [edi]
.text:0054118D                  push    eax
.text:0054118E                  push    ebx
.text:0054118F                  push    ebp
.text:00541190                  push    esi
.text:00541191                  call    set_bit
.text:00541196                  add     esp, 10h
.text:00541199                  inc     edi
.text:0054119A                  dec     esi
.text:0054119B                  cmp     esi, 0FFFFFFFFh
.text:0054119E                  jg      short loc_54118B
.text:005411A0                  inc     ebx
.text:005411A1                  cmp     ebx, 8
.text:005411A4                  jl      short loc_541186
.text:005411A6                  pop     edi
.text:005411A7                  pop     esi
.text:005411A8                  pop     ebp
.text:005411A9                  pop     ebx
.text:005411AA                  add     esp, 40h
.text:005411AD                  retn
.text:005411AD rotate3          endp
.text:005411AD
.text:005411AE                  align 10h
.text:005411B0
.text:005411B0 ; ============ S U B R O U T I N E ===========
.text:005411B0
.text:005411B0
.text:005411B0 rotate_all_with_password proc near       ; CODE XREF: crypt+1F
.text:005411B0                                          ; decrypt+36
.text:005411B0
.text:005411B0 arg_0            = dword ptr  4
.text:005411B0 arg_4            = dword ptr  8
.text:005411B0
.text:005411B0                  mov     eax, [esp+arg_0]
.text:005411B4                  push    ebp
.text:005411B5                  mov     ebp, eax
.text:005411B7                  cmp     byte ptr [eax], 0
.text:005411BA                  jz      exit
.text:005411C0                  push    ebx
.text:005411C1                  mov     ebx, [esp+8+arg_4]
.text:005411C5                  push    esi
.text:005411C6                  push    edi
.text:005411C7
.text:005411C7 loop_begin:                   ; CODE XREF: rotate_all_with_password+9F
.text:005411C7                  movsx   eax, byte ptr [ebp+0]
.text:005411CB                  push    eax    ; C
.text:005411CC                  call    _tolower
.text:005411D1                  add     esp, 4
.text:005411D4                  cmp     al, 'a'
.text:005411D6                  jl      short next_character_in_password
.text:005411D8                  cmp     al, 'z'
.text:005411DA                  jg      short next_character_in_password
.text:005411DC                  movsx   ecx, al
.text:005411DF                  sub     ecx, 'a'
.text:005411E2                  cmp     ecx, 24
.text:005411E5                  jle     short skip_subtracting
.text:005411E7                  sub     ecx, 24
.text:005411EA
.text:005411EA skip_subtracting:              ; CODE XREF: rotate_all_with_password+35
.text:005411EA                  mov     eax, 55555556h
.text:005411EF                  imul    ecx
.text:005411F1                  mov     eax, edx
.text:005411F3                  shr     eax, 1Fh
.text:005411F6                  add     edx, eax
.text:005411F8                  mov     eax, ecx
.text:005411FA                  mov     esi, edx
.text:005411FC                  mov     ecx, 3
.text:00541201                  cdq
.text:00541202                  idiv    ecx
.text:00541204                  sub     edx, 0
.text:00541207                  jz      short call_rotate1
.text:00541209                  dec     edx
.text:0054120A                  jz      short call_rotate2
.text:0054120C                  dec     edx
.text:0054120D                  jnz     short next_character_in_password
.text:0054120F                  test    ebx, ebx
.text:00541211                  jle     short next_character_in_password
.text:00541213                  mov     edi, ebx
.text:00541215
.text:00541215 call_rotate3:                 ; CODE XREF: rotate_all_with_password+6F
.text:00541215                  push    esi
.text:00541216                  call    rotate3
.text:0054121B                  add     esp, 4
.text:0054121E                  dec     edi
.text:0054121F                  jnz     short call_rotate3
.text:00541221                  jmp     short next_character_in_password
.text:00541223
.text:00541223 call_rotate2:                  ; CODE XREF: rotate_all_with_password+5A
.text:00541223                  test    ebx, ebx
.text:00541225                  jle     short next_character_in_password
.text:00541227                  mov     edi, ebx
.text:00541229
.text:00541229 loc_541229:                    ; CODE XREF: rotate_all_with_password+83
.text:00541229                  push    esi
.text:0054122A                  call    rotate2
.text:0054122F                  add     esp, 4
.text:00541232                  dec     edi
.text:00541233                  jnz     short loc_541229
.text:00541235                  jmp     short next_character_in_password
.text:00541237
.text:00541237 call_rotate1:                  ; CODE XREF: rotate_all_with_password+57
.text:00541237                  test    ebx, ebx
.text:00541239                  jle     short next_character_in_password
.text:0054123B                  mov     edi, ebx
.text:0054123D
.text:0054123D loc_54123D:                    ; CODE XREF: rotate_all_with_password+97
.text:0054123D                  push    esi
.text:0054123E                  call    rotate1
.text:00541243                  add     esp, 4
.text:00541246                  dec     edi
.text:00541247                  jnz     short loc_54123D
.text:00541249
.text:00541249 next_character_in_password:    ; CODE XREF: rotate_all_with_password+26
.text:00541249                                ; rotate_all_with_password+2A ...
.text:00541249                  mov     al, [ebp+1]
.text:0054124C                  inc     ebp
.text:0054124D                  test    al, al
.text:0054124F                  jnz     loop_begin
.text:00541255                  pop     edi
.text:00541256                  pop     esi
.text:00541257                  pop     ebx
.text:00541258
.text:00541258 exit:                         ; CODE XREF: rotate_all_with_password+A
.text:00541258                  pop     ebp
.text:00541259                  retn
.text:00541259 rotate_all_with_password endp
.text:00541259
.text:0054125A                  align 10h
.text:00541260
.text:00541260 ; ============= S U B R O U T I N E ===============
.text:00541260
.text:00541260
.text:00541260 crypt            proc near            ; CODE XREF: crypt_file+8A
.text:00541260
.text:00541260 arg_0            = dword ptr  4
.text:00541260 arg_4            = dword ptr  8
.text:00541260 arg_8            = dword ptr  0Ch
.text:00541260
.text:00541260                  push    ebx
.text:00541261                  mov     ebx, [esp+4+arg_0]
.text:00541265                  push    ebp
.text:00541266                  push    esi
.text:00541267                  push    edi
.text:00541268                  xor     ebp, ebp
.text:0054126A
.text:0054126A loc_54126A:                           ; CODE XREF: crypt+41
.text:0054126A                  mov     eax, [esp+10h+arg_8]
.text:0054126E                  mov     ecx, 10h
.text:00541273                  mov     esi, ebx
.text:00541275                  mov     edi, offset cube64
.text:0054127A                  push    1
.text:0054127C                  push    eax
.text:0054127D                  rep movsd
.text:0054127F                  call    rotate_all_with_password
.text:00541284                  mov     eax, [esp+18h+arg_4]
.text:00541288                  mov     edi, ebx
.text:0054128A                  add     ebp, 40h
.text:0054128D                  add     esp, 8
.text:00541290                  mov     ecx, 10h
.text:00541295                  mov     esi, offset cube64
.text:0054129A                  add     ebx, 40h
.text:0054129D                  cmp     ebp, eax
.text:0054129F                  rep movsd
.text:005412A1                  jl      short loc_54126A
.text:005412A3                  pop     edi
.text:005412A4                  pop     esi
.text:005412A5                  pop     ebp
.text:005412A6                  pop     ebx
.text:005412A7                  retn
.text:005412A7 crypt            endp
.text:005412A7
.text:005412A8                  align 10h
.text:005412B0
.text:005412B0 ; =========== S U B R O U T I N E ============
.text:005412B0
.text:005412B0
.text:005412B0 ; int __cdecl decrypt(int, int, void *Src)
.text:005412B0 decrypt          proc near            ; CODE XREF: decrypt_file+99
.text:005412B0
.text:005412B0 arg_0            = dword ptr  4
.text:005412B0 arg_4            = dword ptr  8
.text:005412B0 Src              = dword ptr  0Ch
.text:005412B0
.text:005412B0                  mov     eax, [esp+Src]
.text:005412B4                  push    ebx
.text:005412B5                  push    ebp
.text:005412B6                  push    esi
.text:005412B7                  push    edi
.text:005412B8                  push    eax            ; Src
.text:005412B9                  call    __strdup
.text:005412BE                  push    eax            ; Str
.text:005412BF                  mov     [esp+18h+Src], eax
.text:005412C3                  call    __strrev
.text:005412C8                  mov     ebx, [esp+18h+arg_0]
.text:005412CC                  add     esp, 8
.text:005412CF                  xor     ebp, ebp
.text:005412D1
.text:005412D1 loc_5412D1:                           ; CODE XREF: decrypt+58
.text:005412D1                  mov     ecx, 10h
.text:005412D6                  mov     esi, ebx
.text:005412D8                  mov     edi, offset cube64
.text:005412DD                  push    3
.text:005412DF                  rep movsd
.text:005412E1                  mov     ecx, [esp+14h+Src]
.text:005412E5                  push    ecx
.text:005412E6                  call    rotate_all_with_password
.text:005412EB                  mov     eax, [esp+18h+arg_4]
.text:005412EF                  mov     edi, ebx
.text:005412F1                  add     ebp, 40h
.text:005412F4                  add     esp, 8
.text:005412F7                  mov     ecx, 10h
.text:005412FC                  mov     esi, offset cube64
.text:00541301                  add     ebx, 40h
.text:00541304                  cmp     ebp, eax
.text:00541306                  rep movsd
.text:00541308                  jl      short loc_5412D1
.text:0054130A                  mov     edx, [esp+10h+Src]
.text:0054130E                  push    edx                ; Memory
.text:0054130F                  call    _free
.text:00541314                  add     esp, 4
.text:00541317                  pop     edi
.text:00541318                  pop     esi
.text:00541319                  pop     ebp
.text:0054131A                  pop     ebx
.text:0054131B                  retn
.text:0054131B decrypt          endp
.text:0054131B
.text:0054131C                  align 10h
.text:00541320
.text:00541320 ; ============= S U B R O U T I N E ============
.text:00541320
.text:00541320
.text:00541320 ; int __cdecl crypt_file(int Str, char *Filename, int password)
.text:00541320 crypt_file       proc near               ; CODE XREF: _main+42
.text:00541320
.text:00541320 Str              = dword ptr  4
.text:00541320 Filename         = dword ptr  8
.text:00541320 password         = dword ptr  0Ch
.text:00541320
.text:00541320                  mov     eax, [esp+Str]
.text:00541324                  push    ebp
.text:00541325                  push    offset Mode      ; "rb"
.text:0054132A                  push    eax              ; Filename
.text:0054132B                  call    _fopen           ; open file
.text:00541330                  mov     ebp, eax
.text:00541332                  add     esp, 8
.text:00541335                  test    ebp, ebp
.text:00541337                  jnz     short loc_541348
.text:00541339                  push    offset Format    ; "Cannot open input file!\n"
.text:0054133E                  call    _printf
.text:00541343                  add     esp, 4
.text:00541346                  pop     ebp
.text:00541347                  retn
.text:00541348
.text:00541348 loc_541348:                               ; CODE XREF: crypt_file+17
.text:00541348                  push    ebx
.text:00541349                  push    esi
.text:0054134A                  push    edi
.text:0054134B                  push    2                ; Origin
.text:0054134D                  push    0                ; Offset
.text:0054134F                  push    ebp              ; File
.text:00541350                  call    _fseek
.text:00541355                  push    ebp              ; File
.text:00541356                  call    _ftell           ; get file size
.text:0054135B                  push    0                ; Origin
.text:0054135D                  push    0                ; Offset
.text:0054135F                  push    ebp              ; File
.text:00541360                  mov     [esp+2Ch+Str], eax
.text:00541364                  call    _fseek           ; rewind to start
.text:00541369                  mov     esi, [esp+2Ch+Str]
.text:0054136D                  and     esi, 0FFFFFFC0h  ; reset all lowest 6 bits
.text:00541370                  add     esi, 40h         ; align size to 64-byte border
.text:00541373                  push    esi              ; Size
.text:00541374                  call    _malloc
.text:00541379                  mov     ecx, esi
.text:0054137B                  mov     ebx, eax   ; allocated buffer pointer -> to EBX
.text:0054137D                  mov     edx, ecx
.text:0054137F                  xor     eax, eax
.text:00541381                  mov     edi, ebx
.text:00541383                  push    ebp         ; File
.text:00541384                  shr     ecx, 2
.text:00541387                  rep stosd
.text:00541389                  mov     ecx, edx
.text:0054138B                  push    1           ; Count
.text:0054138D                  and     ecx, 3
.text:00541390                  rep stosb            ; memset (buffer, 0, aligned_size)
.text:00541392                  mov     eax, [esp+38h+Str]
.text:00541396                  push    eax               ; ElementSize
.text:00541397                  push    ebx               ; DstBuf
.text:00541398                  call    _fread            ; read file
.text:0054139D                  push    ebp               ; File
.text:0054139E                  call    _fclose
.text:005413A3                  mov     ecx, [esp+44h+password]
.text:005413A7                  push    ecx               ; password
.text:005413A8                  push    esi               ; aligned size
.text:005413A9                  push    ebx               ; buffer
.text:005413AA                  call    crypt             ; do crypt
.text:005413AF                  mov     edx, [esp+50h+Filename]
.text:005413B3                  add     esp, 40h
.text:005413B6                  push    offset aWb        ; "wb"
.text:005413BB                  push    edx               ; Filename
.text:005413BC                  call    _fopen
.text:005413C1                  mov     edi, eax
.text:005413C3                  push    edi               ; File
.text:005413C4                  push    1                 ; Count
.text:005413C6                  push    3                 ; Size
.text:005413C8                  push    offset aQr9       ; "QR9"
.text:005413CD                  call    _fwrite           ; write file signature
.text:005413D2                  push    edi               ; File
.text:005413D3                  push    1                 ; Count
.text:005413D5                  lea     eax, [esp+30h+Str]
.text:005413D9                  push    4                 ; Size
.text:005413DB                  push    eax               ; Str
.text:005413DC                  call    _fwrite           ; write original file size
.text:005413E1                  push    edi               ; File
.text:005413E2                  push    1                 ; Count
.text:005413E4                  push    esi               ; Size
.text:005413E5                  push    ebx               ; Str
.text:005413E6                  call    _fwrite           ; write encrypted file
.text:005413EB                  push    edi               ; File
.text:005413EC                  call    _fclose
.text:005413F1                  push    ebx               ; Memory
.text:005413F2                  call    _free
.text:005413F7                  add     esp, 40h
.text:005413FA                  pop     edi
.text:005413FB                  pop     esi
.text:005413FC                  pop     ebx
.text:005413FD                  pop     ebp
.text:005413FE                  retn
.text:005413FE crypt_file       endp
.text:005413FE
.text:005413FF                  align 10h
.text:00541400
.text:00541400 ; =========== S U B R O U T I N E ==============
.text:00541400
.text:00541400
.text:00541400 ; int __cdecl decrypt_file(char *Filename, int, void *Src)
.text:00541400 decrypt_file     proc near                   ; CODE XREF: _main+6E
.text:00541400
.text:00541400 Filename         = dword ptr  4
.text:00541400 arg_4            = dword ptr  8
.text:00541400 Src              = dword ptr  0Ch
.text:00541400
.text:00541400                  mov     eax, [esp+Filename]
.text:00541404                  push    ebx
.text:00541405                  push    ebp
.text:00541406                  push    esi
.text:00541407                  push    edi
.text:00541408                  push    offset aRb        ; "rb"
.text:0054140D                  push    eax               ; Filename
.text:0054140E                  call    _fopen
.text:00541413                  mov     esi, eax
.text:00541415                  add     esp, 8
.text:00541418                  test    esi, esi
.text:0054141A                  jnz     short loc_54142E
.text:0054141C                  push    offset aCannotOpenIn_0 ; "Cannot open input file!\n"
.text:00541421                  call    _printf
.text:00541426                  add     esp, 4
.text:00541429                  pop     edi
.text:0054142A                  pop     esi
.text:0054142B                  pop     ebp
.text:0054142C                  pop     ebx
.text:0054142D                  retn
.text:0054142E
.text:0054142E loc_54142E:                                ; CODE XREF: decrypt_file+1A
.text:0054142E                  push    2                 ; Origin
.text:00541430                  push    0                 ; Offset
.text:00541432                  push    esi               ; File
.text:00541433                  call    _fseek
.text:00541438                  push    esi               ; File
.text:00541439                  call    _ftell
.text:0054143E                  push    0                 ; Origin
.text:00541440                  push    0                 ; Offset
.text:00541442                  push    esi               ; File
.text:00541443                  mov     ebp, eax
.text:00541445                  call    _fseek
.text:0054144A                  push    ebp               ; Size
.text:0054144B                  call    _malloc
.text:00541450                  push    esi               ; File
.text:00541451                  mov     ebx, eax
.text:00541453                  push    1                 ; Count
.text:00541455                  push    ebp               ; ElementSize
.text:00541456                  push    ebx               ; DstBuf
.text:00541457                  call    _fread
.text:0054145C                  push    esi               ; File
.text:0054145D                  call    _fclose
.text:00541462                  add     esp, 34h
.text:00541465                  mov     ecx, 3
.text:0054146A                  mov     edi, offset aQr9_0 ; "QR9"
.text:0054146F                  mov     esi, ebx
.text:00541471                  xor     edx, edx
.text:00541473                  repe cmpsb
.text:00541475                  jz      short loc_541489
.text:00541477                  push    offset aFileIsNotCrypt ; "File is not encrypted!\n"
.text:0054147C                  call    _printf
.text:00541481                  add     esp, 4
.text:00541484                  pop     edi
.text:00541485                  pop     esi
.text:00541486                  pop     ebp
.text:00541487                  pop     ebx
.text:00541488                  retn
.text:00541489
.text:00541489 loc_541489:                               ; CODE XREF: decrypt_file+75
.text:00541489                  mov     eax, [esp+10h+Src]
.text:0054148D                  mov     edi, [ebx+3]
.text:00541490                  add     ebp, 0FFFFFFF9h
.text:00541493                  lea     esi, [ebx+7]
.text:00541496                  push    eax              ; Src
.text:00541497                  push    ebp              ; int
.text:00541498                  push    esi              ; int
.text:00541499                  call    decrypt
.text:0054149E                  mov     ecx, [esp+1Ch+arg_4]
.text:005414A2                  push    offset aWb_0     ; "wb"
.text:005414A7                  push    ecx              ; Filename
.text:005414A8                  call    _fopen
.text:005414AD                  mov     ebp, eax
.text:005414AF                  push    ebp              ; File
.text:005414B0                  push    1                ; Count
.text:005414B2                  push    edi              ; Size
.text:005414B3                  push    esi              ; Str
.text:005414B4                  call    _fwrite
.text:005414B9                  push    ebp              ; File
.text:005414BA                  call    _fclose
.text:005414BF                  push    ebx              ; Memory
.text:005414C0                  call    _free
.text:005414C5                  add     esp, 2Ch
.text:005414C8                  pop     edi
.text:005414C9                  pop     esi
.text:005414CA                  pop     ebp
.text:005414CB                  pop     ebx
.text:005414CC                  retn
.text:005414CC decrypt_file     endp

笔者在分析过程中逐步添加了各标签的名称。

我们从文件头开始分析。第一个函数读取两个文件名和一个密码:

.text:00541320 ; int __cdecl crypt_file(int Str, char *Filename, int password)
.text:00541320 crypt_file       proc near
.text:00541320
.text:00541320 Str              = dword ptr  4
.text:00541320 Filename         = dword ptr  8
.text:00541320 password         = dword ptr  0Ch
.text:00541320

如果不能成功打开明文文件,程序就会进行异常处理:

.text:00541320                  mov      eax, [esp+Str]
.text:00541324                  push     ebp
.text:00541325                  push     offset Mode    ; "rb"
.text:0054132A                  push     eax            ; Filename
.text:0054132B                  call     _fopen         ; open file
.text:00541330                  mov      ebp, eax
.text:00541332                  add      esp, 8
.text:00541335                  test     ebp, ebp
.text:00541337                  jnz      short loc_541348
.text:00541339                  push     offset Format  ; "Cannot open input file!\n"
.text:0054133E                  call     _printf
.text:00541343                  add      esp, 4
.text:00541346                  pop      ebp
.text:00541347                  pop      ebp
.text:00541348
.text:00541348 loc_541348:

通过fseek()/ftell()函数获取文件大小:

.text:00541348 push    ebx
.text:00541349 push    esi
.text:0054134A push    edi
.text:0054134B push    2             ; Origin
.text:0054134D push    0             ; Offset
.text:0054134F push    ebp           ; File

; move current file position to the end
.text:00541350 call    _fseek
.text:00541355 push    ebp           ; File
.text:00541356 call    _ftell        ; get current file position
.text:0054135B push    0             ; Origin
.text:0054135D push    0             ; Offset
.text:0054135F push    ebp           ; File
.text:00541360 mov     [esp+2Ch+Str], eax

; move current file position to the start
.text:00541364 call    _fseek

上述指令把文件大小向64字节边界对齐。程序所采用的加密算法只能处理64字节消息块。它的算法相当直白:把文件尺寸除以64,舍弃余数,然后把整除结果乘以64。下述指令的“与”运算起到整除64并清除余数的作用,然后加法运算指令把上述整除的商再加上64。这组指令将使文件大小向64字节边界对齐。

.text:00541369 mov     esi, [esp+2Ch+Str]
; reset all lowest 6 bits
.text:0054136D and     esi, 0FFFFFFC0h
; align size to 64-byte border
.text:00541370 add     esi, 40h

接下来按照上述结果分配缓冲区:

.text:00541373               push    esi                ; Size
.text:00541374               call    _malloc

调用memset(),即清除缓冲区数据:[1]

.text:00541379 mov     ecx, esi
.text:0054137B mov     ebx, eax          ; allocated buffer pointer -> to EBX
.text:0054137D mov     edx, ecx
.text:0054137F xor     eax, eax
.text:00541381 mov     edi, ebx
.text:00541383 push    ebp               ; File
.text:00541384 shr     ecx, 2
.text:00541387 rep stosd
.text:00541389 mov     ecx, edx
.text:0054138B push    1                 ; Count
.text:0054138D and     ecx, 3
.text:00541390 rep stosb                 ; memset (buffer, 0, aligned_size)

调用标准C函数fread()读取文件:

.text:00541392                  mov     eax, [esp+38h+Str]
.text:00541396                  push    eax          ; ElementSize
.text:00541397                  push    ebx          ; DstBuf
.text:00541398                  call    _fread       ; read file
.text:0054139D                  push    ebp          ; File
.text:0054139E                  call    _fclose

调用crypt()函数,并且向这个函数传递缓冲区、缓冲区尺寸及密码字符串:

.text:005413A3                  mov     ecx, [esp+44h+password]
.text:005413A7                  push    ecx          ; password
.text:005413A8                  push    esi          ; aligned size
.text:005413A9                  push    ebx          ; buffer
.text:005413AA                  call    crypt        ; do crypt

创建输出文件。虽然研发人员确实检测了能否成功打开文件,但是他们忘记了检测能否正确创建文件:

.text:005413AF                  mov     edx, [esp+50h+Filename]
.text:005413B3                  add     esp, 40h
.text:005413B6                  push    offset aWb      ; "wb"
.text:005413BB                  push    edx             ; Filename
.text:005413BC                  call    _fopen
.text:005413C1                  mov     edi, eax

新建文件的句柄(handle)存储在EDI寄存器里。然后写上签名“QR9”:

.text:005413C3                  push    edi          ; File
.text:005413C4                  push    1            ; Count
.text:005413C6                  push    3            ; Size
.text:005413C8                  push    offset aQr9  ; "QR9"
.text:005413CD                  call    _fwrite      ; write file signature

标注原始文件的实际大小(未经数据对齐处理的原始值):

.text:005413D2                  push    edi          ; File
.text:005413D3                  push    1            ; Count
.text:005413D5                  lea     eax, [esp+30h+Str]
.text:005413D9                  push    4            ;Size
.text:005413DB                  push    eax          ; Str
.text:005413DC                  call    _fwrite      ; write original file size

写入密文的缓冲区:

.text:005413E1                  push    edi          ; File
.text:005413E2                  push    1            ; Count
.text:005413E4                  push    esi          ; Size
.text:005413E5                  push    ebx          ; Str
.text:005413E6                  call    _fwrite      ; write encrypted file

关闭文件、释放缓冲区:

.text:005413EB                  push    edi          ; File
.text:005413EC                  call    _fclose
.text:005413F1                  push    ebx          ; Memory
.text:005413F2                  call    _free
.text:005413F7                  add     esp, 40h
.text:005413FA                  pop     edi
.text:005413FB                  pop     esi
.text:005413FC                  pop     ebx
.text:005413FD                  pop     ebp
.text:005413FE                  retn
.text:005413FE crypt_file       endp

通过上面的分析,我们可整理出源代码如下:

void crypt_file(char *fin, char* fout, char *pw)
{
        FILE *f;
        int flen, flen_aligned;
        BYTE *buf;

        f=fopen(fin, "rb");

        if (f==NULL)
        {
                printf ("Cannot open input file!\n");
                return;
        };

        fseek (f, 0, SEEK_END);
        flen=ftell (f);
        fseek (f, 0, SEEK_SET);

        flen_aligned=(flen&0xFFFFFFC0)+0x40;

        buf=(BYTE*)malloc (flen_aligned);
        memset (buf, 0, flen_aligned);

        fread (buf, flen, 1, f);

        fclose (f);

        crypt (buf, flen_aligned, pw);

        f=fopen(fout, "wb");

        fwrite ("QR9", 3, 1, f);
        fwrite (&flen, 4, 1, f);
        fwrite (buf, flen_aligned, 1, f);

        fclose (f);

        free (buf);
};

解密过程几乎如出一辙:

.text:00541400 ; int __cdecl decrypt_file(char *Filename, int, void *Src)
.text:00541400 decrypt_file     proc near
.text:00541400
.text:00541400 Filename         = dword ptr  4
.text:00541400 arg_4            = dword ptr  8
.text:00541400 Src              = dword ptr  0Ch
.text:00541400
.text:00541400                  mov     eax, [esp+Filename]
.text:00541404                  push    ebx
.text:00541405                  push    ebp
.text:00541406                  push    esi
.text:00541407                  push    edi
.text:00541408                  push    offset aRb        ; "rb"
.text:0054140D                  push    eax               ; Filename
.text:0054140E                  call    _fopen
.text:00541413                  mov     esi, eax
.text:00541415                  add     esp, 8
.text:00541418                  test    esi, esi
.text:0054141A                  jnz     short loc_54142E
.text:0054141C                  push    offset aCannotOpenIn_0 ; "Cannot open input file!\n"
.text:00541421                  call    _printf
.text:00541426                  add     esp, 4
.text:00541429                  pop     edi
.text:0054142A                  pop     esi
.text:0054142B                  pop     ebp
.text:0054142C                  pop     ebx
.text:0054142D                  retn
.text:0054142E
.text:0054142E loc_54142E: 
.text:0054142E                  push    2               ; Origin
.text:00541430                  push    0               ; Offset
.text:00541432                  push    esi             ; File
.text:00541433                  call    _fseek
.text:00541438                  push    esi             ; File
.text:00541439                  call    _ftell
.text:0054143E                  push    0               ; Origin
.text:00541440                  push    0               ; Offset
.text:00541442                  push    esi             ; File
.text:00541443                  mov     ebp, eax
.text:00541445                  call    _fseek
.text:0054144A                  push    ebp             ; Size
.text:0054144B                  call    _malloc
.text:00541450                  push    esi             ; File
.text:00541451                  mov     ebx, eax
.text:00541453                  push   1                ; Count
.text:00541455                  push    ebp             ; ElementSize
.text:00541456                  push    ebx             ; DstBuf
.text:00541457                  call    _fread
.text:0054145C                  push    esi             ; File
.text:0054145D                  call    _fclose

检测前三个字节的程序签名:

.text:00541462                  add     esp, 34h
.text:00541465                  mov     ecx, 3
.text:0054146A                  mov     edi, offset aQr9_0 ; "QR9"
.text:0054146F                  mov     esi, ebx
.text:00541471                  xor     edx, edx
.text:00541473                  repe cmpsb
.text:00541475                  jz      short loc_541489

如果签名有误,则进行错误提示:

.text:00541477                  push    offset aFileIsNotCrypt ; "File is not encrypted!\n"
.text:0054147C                  call    _printf
.text:00541481                  add     esp, 4
.text:00541484                  pop     edi
.text:00541485                  pop     esi
.text:00541486                  pop     ebp
.text:00541487                  pop     ebx
.text:00541488                  retn
.text:00541489
.text:00541489 loc_541489:

调用decrypt()函数:

.text:00541489                  mov     eax, [esp+10h+Src]
.text:0054148D                  mov     edi, [ebx+3]
.text:00541490                  add     ebp, 0FFFFFFF9h
.text:00541493                  lea     esi, [ebx+7]
.text:00541496                  push    eax                  ; Src
.text:00541497                  push    ebp                  ; int
.text:00541498                  push    esi                  ; int
.text:00541499                  call    decrypt
.text:0054149E                  mov     ecx, [esp+1Ch+arg_4]
.text:005414A2                  push    offset aWb_0         ; "wb"
.text:005414A7                  push    ecx                  ; Filename
.text:005414A8                  call    _fopen
.text:005414AD                  mov     ebp, eax
.text:005414AF                  push    ebp                  ; File
.text:005414B0                  push    1                    ; Count
.text:005414B2                  push    edi                  ; Size
.text:005414B3                  push    esi                  ; Str
.text:005414B4                  call    _fwrite
.text:005414B9                  push    ebp                  ; File
.text:005414BA                  call    _fclose
.text:005414BF                  push    ebx                  ; Memory
.text:005414C0                  call    _free
.text:005414C5                  add     esp, 2Ch
.text:005414C8                  pop     edi
.text:005414C9                  pop     esi
.text:005414CA                  pop     ebp
.text:005414CB                  pop     ebx
.text:005414CC                  retn
.text:005414CC decrypt_file     endp

通过上面的分析,我们可整理出decrypt_file()的源代码如下:

void decrypt_file(char *fin, char* fout, char *pw)
{
        FILE *f;
        int real_flen, flen;
        BYTE *buf;

        f=fopen(fin, "rb");

        if (f==NULL)
        {
                printf ("Cannot open input file!\n");
                return;
        };

        fseek (f, 0, SEEK_END);
        flen=ftell (f);
        fseek (f, 0, SEEK_SET);

        buf=(BYTE*)malloc (flen);

        fread (buf, flen, 1, f);

        fclose (f);

        if (memcmp (buf, "QR9", 3)!=0)
        {
                printf ("File is not encrypted!\n");
                return;
        };

        memcpy (&real_flen, buf+3, 4);

        decrypt (buf+(3+4), flen-(3+4), pw);

        f=fopen(fout, "wb");

        fwrite (buf+(3+4), real_flen, 1, f);

        fclose (f);

        free (buf);
};

接下来,我们深入研究crypt()函数:

.text:00541260 crypt            proc near      
.text:00541260 
.text:00541260 arg_0            = dword ptr  4 
.text:00541260 arg_4            = dword ptr  8 
.text:00541260 arg_8            = dword ptr  0Ch 
.text:00541260 
.text:00541260                  push    ebx 
.text:00541261                  mov     ebx, [esp+4+arg_0] 
.text:00541265                  push    ebp 
.text:00541266                  push    esi 
.text:00541267                  push    edi 
.text:00541268                  xor     ebp, ebp 
.text:0054126A 
.text:0054126A loc_54126A:

这段指令从输入缓冲区中读取部分数据(即消息块),把它传递到内部数组。为了方便讨论,笔者把内部数组叫作“cube64”。信息的尺寸存储在ECX寄存器里。MOVSD代表move 32-bit dword。而16个32位dwords数据就是64个字节。

.text:0054126A                  mov     eax, [esp+10h+arg_8]
.text:0054126E                  mov     ecx, 10h
.text:00541273                  mov     esi, ebx ; EBX is pointer within input buffer
.text:00541275                  mov     edi, offset cube64
.text:0054127A                  push    1
.text:0054127C                  push    eax
.text:0054127D                  rep movsd

调用 rotate_all_with_password():

.text:0054127F                  call    rotate_all_with_password

把加密内容从cube64复制到缓冲区:

.text:00541284                  mov     eax, [esp+18h+arg_4]
.text:00541288                  mov     edi, ebx
.text:0054128A                  add     ebp, 40h
.text:0054128D                  add     esp, 8
.text:00541290                  mov     ecx, 10h
.text:00541295                  mov     esi, offset cube64
.text:0054129A                  add     ebx, 40h; add 64 to input buffer pointer
.text:0054129D                  cmp     ebp, eax; EBP contain amount of encrypted data.
.text:0054129F                  rep movsd

如果EBP不大于传入函数的文件大小,那么继续处理下一个消息块:

.text:005412A1                  jl      short loc_54126A
.text:005412A3                  pop     edi
.text:005412A4                  pop     esi
.text:005412A5                  pop     ebp
.text:005412A6                  pop     ebx
.text:005412A7                  retn
.text:005412A7 crypt            endp

通过上面的分析,我们可整理出crypt()的源代码如下:

void crypt (BYTE *buf, int sz, char *pw)
{
        int i=0;

        do
        {
                memcpy (cube, buf+i, 8*8);
                rotate_all (pw, 1);
                memcpy (buf+i, cube, 8*8);
                i+=64;
        }
        while (i<sz);
};

然后我们再分析rotate_all_with_password()函数。它有两个参数:密码字符串和数字参数。在加密crypt()函数里数字参数是1,而在同样调用rotate_all_with_password()函数的解密decrypt()函数里,这个数字参数的值为3。

.text:005411B0 rotate_all_with_password proc near
.text:005411B0
.text:005411B0 arg_0            = dword ptr 4
.text:005411B0 arg_4            = dword ptr 8
.text:005411B0
.text:005411B0                  mov     eax, [esp+arg_0]
.text:005411B4                  push    ebp
.text:005411B5                  mov     ebp, eax

检测密码中的当前字符。如果是零字节,就退出:

.text:005411B7                  cmp     byte ptr [eax], 0
.text:005411BA                  jz      exit
.text:005411C0                  push    ebx
.text:005411C1                  mov     ebx, [esp+8+arg_4]
.text:005411C5                  push    esi
.text:005411C6                  push    edi
.text:005411C7
.text:005411C7 loop_begin:

调用标准C函数tolower():

.text:005411C7                  movsx   eax, byte ptr [ebp+0]
.text:005411CB                  push    eax                ; C
.text:005411CC                  call    _tolower
.text:005411D1                  add     esp, 4

这个函数会忽略(跳过)密码中的非拉丁字符。在进行程序测试的时候,加密工具确实忽略了非拉丁字符。

.text:005411D4                  cmp     al, 'a'
.text:005411D6                  jl      short next_character_in_password
.text:005411D8                  cmp     al, 'z'
.text:005411DA                  jg      short next_character_in_password
.text:005411DC                  movsx   ecx, al

从当前字符的ASCII值中减去97(即字母“a”):

.text:005411DF                  sub     ecx, 'a' ; 97

经过刚才的处理,字母a的值就变成了0,b的值为1,z的值为 25:

.text:005411E2                  cmp     ecx, 24
.text:005411E5                  jle     short skip_subtracting
.text:005411E7                  sub     ecx, 24

似乎“y”和“z”是特例。在执行上述指令之后,“y”变成了0,“z”变成了1。这也就是说,26个拉丁字母最终被转换为了0~23之间(共计24个)的数字。

.text:005411EA
.text:005411EA skip_subtracting:              ;CODE XREF: rotate_all_ with_password+35

接着,它以乘法指令实现除法运算。本书第41章介绍过“除以9”的详细步骤。下面这段指令把密码字符的值除以3:

.text:005411EA                  mov     eax, 55555556h
.text:005411EF                  imul    ecx
.text:005411F1                  mov     eax, edx
.text:005411F3                  shr     eax, 1Fh
.text:005411F6                  add     edx, eax
.text:005411F8                  mov     eax, ecx
.text:005411FA                  mov     esi, edx
.text:005411FC                  mov     ecx, 3
.text:00541201                  cdq
.text:00541202                  idiv    ecx

除法运算的余数存储在EDX寄存器里:

.text:00541204 sub      edx, 0
.text:00541207 jz       short call_rotate1 ; if remainder is zero, go to rotate1
.text:00541209 dec      edx
.text:0054120A jz       short call_rotate2 ; .. if it is 1, go to rotate2
.text:0054120C dec      edx
.text:0054120D jnz      short next_character_in_password
.text:0054120F test     ebx, ebx
.text:00541211 jle      short next_character_in_password
.text:00541213 mov      edi, ebx

如果余数为2,那么就会调用rotate3()。此时,EDI寄存器存储的是rotate_all_with_password()函数的第二个参数。前文介绍过,在加密过程中这个值为1,在解密过程中这个值为3。函数进行了循环处理。在加密时,rotate1/2/3的调用次数与函数的第一个参数相等。

.text:00541215 call_rotate3:
.text:00541215                  push    esi
.text:00541216                  call    rotate3
.text:0054121B                  add     esp, 4
.text:0054121E                  dec     edi
.text:0054121F                  jnz     short call_rotate3
.text:00541221                  jmp     short next_character_in_password
.text:00541223
.text:00541223 call_rotate2:
.text:00541223                  test    ebx, ebx
.text:00541225                  jle     short next_character_in_password
.text:00541227                  mov     edi, ebx
.text:00541229
.text:00541229 loc_541229:
.text:00541229                  push    esi
.text:0054122A                  call    rotate2
.text:0054122F                  add     esp, 4
.text:00541232                  dec     edi
.text:00541233                  jnz     short loc_541229
.text:00541235                  jmp     short next_character_in_password
.text:00541237
.text:00541237 call_rotate1:
.text:00541237                  test    ebx, ebx
.text:00541239                  jle     short next_character_in_password
.text:0054123B                  mov     edi, ebx
.text:0054123D
.text:0054123D loc_54123D:
.text:0054123D                  push    esi
.text:0054123E                  call    rotate1
.text:00541243                  add     esp, 4
.text:00541246                  dec     edi
.text:00541247                  jnz     short loc_54123D
.text:00541249

从密码字符串中提取第二个字符:

.text:00541249 next_character_in_password:   
.text:00541249                   mov     al, [ebp+1]

密码字符串指针递增:

.text:0054124C                  inc     ebp
.text:0054124D                  test    al, al
.text:0054124F                  jnz     loop_begin
.text:00541255                  pop     edi
.text:00541256                  pop     esi
.text:00541257                  pop     ebx
.text:00541258
.text:00541258 exit:  
.text:00541258                  pop     ebp
.text:00541259                  retn
.text:00541259 rotate_all_with_password endp

通过上面的分析,我们可整理出rotate_all ()的源代码如下:

void rotate_all (char *pwd, int v)
{
        char *p=pwd;

        while (*p)
        {
                char c=*p;
                int q;

                c=tolower (c);

                if (c>='a' && c<='z')
                {
                        q=c-'a';
                        if (q>24)
                                q-=24;

                        int quotient=q/3;
                        int remainder=q % 3;

                        switch (remainder)
                        {
                        case 0: for (int i=0; i<v; i++) rotate1 (quotient); break;
                        case 1: for (int i=0; i<v; i++) rotate2 (quotient); break;
                        case 2: for (int i=0; i<v; i++) rotate3 (quotient); break;
                        };
                };

                p++;
        };
};

然后我们再分析rotate1/2/3函数。这三个函数都会调用它们之外的两个函数。根据函数功能,笔者把它们命名为set_bit()和get_bit()。

首先来分析get_bit():

.text:00541050 get_bit          proc near    
.text:00541050
.text:00541050 arg_0            = dword ptr  4
.text:00541050 arg_4            = dword ptr  8
.text:00541050 arg_8            = byte ptr  0Ch
.text:00541050
.text:00541050                  mov      eax, [esp+arg_4]
.text:00541054                  mov      ecx, [esp+arg_0]
.text:00541058                  mov      al, cube64[eax+ecx*8]
.text:0054105F                  mov      cl, [esp+arg_8]
.text:00541063                  shr      al, cl
.text:00541065                  and      al, 1
.text:00541067                  retn
.text:00541067 get_bit          endp

上述指令先计算cube64的数组索引“arg_4+arg0*8”,然后将数组中的单个字节向右位移arg_8位,隔离最低位再返回数值。

接下来我们分析set_bit()函数:

.text:00541000 set_bit          proc near    
.text:00541000
.text:00541000 arg_0            = dword ptr  4
.text:00541000 arg_4            = dword ptr  8
.text:00541000 arg_8            = dword ptr  0Ch
.text:00541000 arg_C            = byte ptr  10h
.text:00541000
.text:00541000                  mov      al, [esp+arg_C]
.text:00541004                  mov      ecx, [esp+arg_8]
.text:00541008                  push     esi
.text:00541009                  mov      esi, [esp+4+arg_0]
.text:0054100D                  test     al, al
.text:0054100F                  mov      eax, [esp+4+arg_4]
.text:00541013                  mov      dl, 1
.text:00541015                  jz       short loc_54102B

DL寄存器的值目前为1。函数将其左移arg_8位。例如,如果arg_8是4,那么DL寄存器的值将变为0x10,即二进制的1000b。

.text:00541017                  shl      dl, cl
.text:00541019                  mov      cl, cube64[eax+esi*8]

从数组中提取1个位,然后进行设置:

.text:00541020                  or       cl, dl

存储运算结果:

.text:00541022                  mov      cube64[eax+esi*8], cl
.text:00541029                  pop      esi
.text:0054102A                  retn
.text:0054102B
.text:0054102B loc_54102B:
.text:0054102B                  shl      dl, cl

如果arg_C的值不是0:

.text:0054102D                  mov      cl, cube64[eax+esi*8]

对DL的值求“非”。例如,如果前一步位移使DL为0x10(即二进制的1000b),在进行非运算之后,它的值将变为0xEF(即二进制的11101111b)。

.text:00541034                  not      dl

下述指令将过滤相关位。如果DL寄存器的某个位是1,那么它会保留CL寄存器的对应位;否则将把CL寄存器的相关位设置为0。如果刚才DL的值不变,还是11101111b,那么CL的第5位(从数权最低位开始数)将变为0,其余位保持不变。

.text:00541036                  and      cl, dl

存储运算结果:

.text:00541038                  mov      cube64[eax+esi*8], cl
.text:0054103F                  pop      esi
.text:00541040                  retn
.text:00541040 set_bit          endp

Get_bit()函数的工作模式也差不多。在arg_C为0的情况下,函数将清除数组中的特定比特位,否则设置相关比特位为1。

我们已经知道数组占用64字节。无论是set_bit()函数还是get_bit()函数,它们的前两个参数都相当于2D坐标。然后数组变为8×8矩阵。

通过上面的分析,我们可整理出set_bit()和get_bit()的源代码如下:

#define IS_SET(flag, bit)       ((flag) & (bit))
#define SET_BIT(var, bit)       ((var) |= (bit))
#define REMOVE_BIT(var, bit)    ((var) &= ~(bit))

static BYTE cube[8][8];

void set_bit (int x, int y, int shift, int bit)
{
        if (bit)
                SET_BIT (cube[x][y], 1<<shift);
        else
                REMOVE_BIT (cube[x][y], 1<<shift);
};

bool get_bit (int x, int y, int shift)
{
        if ((cube[x][y]>>shift)&1==1)
                return 1;
        return 0;
};

回顾rotate1/2/3函数,可看到:

.text:00541070 rotate1          proc near  
.text:00541070

函数使用局部数据栈给数组分配了64个字节:

.text:00541070 internal_array_64 = byte ptr -40h
.text:00541070 arg_0             = dword ptr  4
.text:00541070
.text:00541070                   sub      esp, 40h
.text:00541073                   push     ebx
.text:00541074                   push     ebp
.text:00541075                   mov      ebp, [esp+48h+arg_0]
.text:00541079                   push     esi
.text:0054107A                   push     edi
.text:0054107B                   xor      edi, edi   ;EDI is loop1 counter

EBX寄存器是指向内部数组的指针:

.text:0054107D                   lea      ebx, [esp+50h+internal_array_64]
.text:00541081

函数使用了2层循环:

.text:00541081 first_loop1_begin:
.text:00541081     xor      esi, esi       ; ESI is loop 2 counter
.text:00541083
.text:00541083 first_loop2_begin:
.text:00541083     push    ebp             ; arg_0
.text:00541084     push    esi             ; loop 1 counter
.text:00541085     push    edi             ; loop 2 counter
.text:00541086     call    get_bit
.text:0054108B     add     esp, 0Ch
.text:0054108E     mov     [ebx+esi], al   ; store to internal array
.text:00541091     inc     esi             ; increment loop 1 counter
.text:00541092     cmp     esi, 8
.text:00541095     jl      short first_loop2_begin
.text:00541097     inc     edi             ; increment loop 2 counter

; increment internal array pointer by 8 at each loop 1 iteration
.text:00541098     add     ebx, 8
.text:0054109B     cmp     edi, 8
.text:0054109E     jl      short first_loop1_begin

这两个循环的控制变量,不仅取值范围都在0~7之间,而且它们分别充当了get_bit()函数的第一个、第二个参数。而get_bit()函数使用的第三个参数是rotate1()函数的唯一一个参数。而后,get_bit()函数的返回值存储在内部数组里。

函数再次制备了指向内部数组的指针:

.text:005410A0     lea     ebx, [esp+50h+internal_array_64]
.text:005410A4     mov     edi, 7          ; EDI is loop1 counter, initial state is 7
.text:005410A9
.text:005410A9 second_loop1_begin:  
.text:005410A9     xor     esi, esi        ; ESI is loop2 counter
.text:005410AB
.text:005410AB second_loop2_begin:  
.text:005410AB     mov     al, [ebx+esi]   ; value from internal array
.text:005410AE     push    eax
.text:005410AF     push    ebp             ; arg_0
.text:005410B0     push    edi             ; loop1 counter
.text:005410B1     push    esi             ; loop2 counter
.text:005410B2     call    set_bit
.text:005410B7     add     esp, 10h
.text:005410BA     inc     esi             ; increment loop2 counter
.text:005410BB     cmp     esi, 8
.text:005410BE     jl      short second_loop2_begin
.text:005410C0     dec     edi             ; decrement loop2 counter
.text:005410C1     add     ebx, 8          ; increment pointer in internal array
.text:005410C4     cmp     edi, 0FFFFFFFFh
.text:005410C7     jg      short second_loop1_begin
.text:005410C9     pop     edi
.text:005410CA     pop     esi
.text:005410CB     pop     ebp
.text:005410CC     pop     ebx
.text:005410CD     add     esp, 40h
.text:005410D0     retn
.text:005410D0 rotate1         endp

虽然上述代码通过 set_bit()函数把内部数组里的数据复制到了全局数组里,但是数组的排列顺序完全不一样。第一层循环的循环控制变量从7逐渐变为0,呈递减的变化趋势。

通过上面的分析,我们可整理出rotate1()的源代码如下:

void rotatel (int v)
{
        bool tmp[8][8];//internal array
        int i, j;

        for (i=0; i<8; i++)
                for (j=0; j<8; j++)
                        tmp[i][j]=get_bit (i,j, v);

        for (i=0; i<8; i++)
                for (j=0; j<8; j++)
                        set_bit (j, 7-i, v, tmp[x][y]);
};

既然已经搞清了rotate1()函数,那么我们继续研究rotate2()函数:

.text:005410E0 rotate2 proc near
.text:005410E0
.text:005410E0 internal_array_64 = byte ptr -40h
.text:005410E0 arg_0 = dword ptr  4
.text:005410E0
.text:005410E0     sub     esp, 40h
.text:005410E3     push    ebx
.text:005410E4     push    ebp
.text:005410E5     mov     ebp, [esp+48h+arg_0]
.text:005410E9     push    esi
.text:005410EA     push    edi
.text:005410EB     xor     edi, edi       ; loop 1 counter
.text:005410ED     lea     ebx, [esp+50h+internal_array_64]
.text:005410F1
.text:005410F1 loc_5410F1:
.text:005410F1     xor     esi, esi       ; loop 2 counter
.text:005410F3
.text:005410F3 loc_5410F3:
.text:005410F3     push    esi            ; loop 2 counter
.text:005410F4     push    edi            ; loop 1 counter
.text:005410F5     push    ebp            ; arg_0
.text:005410F6     call    get_bit
.text:005410FB     add     esp, 0Ch
.text:005410FE     mov     [ebx+esi], al  ; store to internal array
.text:00541101     inc     esi            ; increment loop 1 counter
.text:00541102     cmp     esi, 8
.text:00541105     jl      short loc_5410F3
.text:00541107     inc     edi            ; increment loop 2 counter
.text:00541108     add     ebx, 8
.text:0054110B     cmp     edi, 8
.text:0054110E     jl      short loc_5410F1
.text:00541110     lea     ebx, [esp+50h+internal_array_64]
.text:00541114     mov     edi, 7         ; loop 1 counter is initial state 7
.text:00541119
.text:00541119 loc_541119:
.text:00541119     xor     esi, esi       ; loop 2 counter
.text:0054111B
.text:0054111B loc_54111B:
.text:0054111B     mov     al, [ebx+esi]  ; get byte from internal array
.text:0054111E     push    eax
.text:0054111F     push    edi            ; loop 1 counter
.text:00541120     push    esi            ; loop 2 counter
.text:00541121     push    ebp            ; arg_0
.text:00541122     call    set_bit
.text:00541127     add     esp, 10h
.text:0054112A     inc     esi            ; increment loop 2 counter
.text:0054112B     cmp     esi, 8
.text:0054112E     jl      short loc_54111B
.text:00541130     dec     edi            ; decrement loop 2 counter
.text:00541131     add     ebx, 8
.text:00541134     cmp     edi, 0FFFFFFFFh
.text:00541137     jg      short loc_541119
.text:00541139     pop     edi
.text:0054113A     pop     esi
.text:0054113B     pop     ebp
.text:0054113C     pop     ebx
.text:0054113D     add     esp, 40h
.text:00541140     retn
.text:00541140 rotate2 endp

在调用get_bit()和set_bit()时,rotate1/2以不同的顺序传递参数。除此之外,rotate1/2基本相同。

可得rotate1()的源代码如下:

void rotate2 (int v)
{
        bool tmp[8][8]; // internal array
        int i, j;

        for (i=0; i<8; i++)
                for (j=0; j<8; j++)
                        tmp[i][j]=get_bit (v, i, j);

        for (i=0; i<8; i++)
                for (j=0; j<8; j++)
                        set_bit (v, j, 7-i, tmp[i][j]);
};

下面我们分析rotate3()函数:

void rotate3 (int v)
{
        bool tmp[8][8];
        int i, j;

        for (i=0; i<8; i++)
                for (j=0; j<8; j++)
                        tmp[i][j]=get_bit (i, v, j);

        for (i=0; i<8; i++)
                for (j=0; j<8; j++)
                        set_bit (7-j, v, i, tmp[i][j]);
};

这个函数更容易分析。如果把cube64当作8×8×8的3D立方体、每个元素都是一个比特位,那么get_bit()函数和set_bit()函数的作用就是从相应坐标读取1个比特位。

如此来看,rotate1/2/3的作用就是以特定隔层旋转所有比特位。这三个参数分别旋转魔方的不同平面,而参数v代表的是设置隔层的位置(0~7)。

或许,这个算法的作者就是把数据当作8×8×8的魔方来处理的。

再次整理decrypt()函数的分析结果后,可推导出下列源代码:

void decrypt (BYTE *buf, int sz, char *pw)
{
        char *p=strdup (pw);
        strrev (p);
        int i=0;

        do 
        {
                memcpy (cube, buf+i, 8*8);
                rotate_all (p, 3);
                memcpy (buf+i, cube, 8*8);
                i+=64;
        }
        while (i<sz);

        free (p);
};

该函数通过标准C函数strrev()[2]对密码字符串进行逆序排列,而且传递给rotate_all()函数的参数是3。除此之外,它和crypt()函数基本一致。

因此,解密过程调用了3次rotate1/2/3。

这和魔方的玩法没什么区别!如果要撤销调整魔方的操作,那么就要用相反的次序和相反方向进行等幅操作。举例来说,您刚刚顺时针转动了魔方的某层方块、后来又要撤销那次操作,那么就把那层方块顺时针转回来,要不然就再顺时针旋转3次。

如果说rotate1()函数旋转的是魔方的“正”面,那么rotate2()函数旋转的则是“上”面,而rotate3()函数则用于旋转魔方的侧面。

不妨再回顾一下rotate_all()函数的代码:

q=c-'a';
if (q>24)
        q-=24;

int quotient=q/3; // in range 0..7
int remainder=q % 3;

switch (remainder)
{
    case 0: for (int i=0; i<v; i++) rotate1 (quotient); break; // front
    case 1: for (int i=0; i<v; i++) rotate2 (quotient); break; // top
    case 2: for (int i=0; i<v; i++) rotate3 (quotient); break; // left
};

综合上述分析可知:密码中的每个字符都描述了旋转的“面”(0~2)和“层”(0~7)信息。3个面×8个层=24(密码字节的取值范围)。为了把26个字母映射到24个数据元素,该程序把字母表的最后两个字母重新映射为0和1。

但是这种算法非常脆弱:在使用短密码的情况下,加密文件的密文篇幅和明文原文的尺寸相等。

重新分析整个程序,可整理得源代码如下:

#include <windows.h>

#include <stdio.h>
#include <assert.h>

#define IS_SET(flag, bit)        ((flag) & (bit))
#define SET_BIT(var, bit)        ((var) |= (bit))
#define REMOVE_BIT(var, bit)     ((var) &= ~(bit))

static BYTE cube[8][8];

void set_bit (int x, int y, int z, bool bit)
{
        if (bit)
                SET_BIT (cube[x][y], 1<<z);
        else
                REMOVE_BIT (cube[x][y], 1<<z);
};

bool get_bit (int x, int y, int z)
{
        if ((cube[x][y]>>z)&1==1)
                return true;
        return false;
};

void rotate_f (int row)
{
        bool tmp[8][8];
        int x, y;

        for (x=0; x<8; x++)
                for (y=0; y<8; y++)
                        tmp[x][y]=get_bit (x, y, row);

        for (x=0; x<8; x++)
                for (y=0; y<8; y++)
                        set_bit (y, 7-x, row, tmp[x][y]);
};

void rotate_t (int row)
{
        bool tmp[8][8];
        int y, z;

        for (y=0; y<8; y++)
                for (z=0; z<8; z++)
                        tmp[y][z]=get_bit (row, y, z);

        for (y=0; y<8; y++)
                for (z=0; z<8; z++)
                        set_bit (row, z, 7-y, tmp[y][z]);  
};

void rotate_l (int row)
{
        bool tmp[8][8];
        int x, z;

        for (x=0; x<8; x++)
                for (z=0; z<8; z++)
                        tmp[x][z]=get_bit (x, row, z);

        for (x=0; x<8; x++)
                for (z=0; z<8; z++)
                        set_bit (7-z, row, x, tmp[x][z]);
};

void rotate_all (char *pwd, int v)
{
        char *p=pwd;

        while (*p) 
        {
                char c=*p;
                int q;

                c=tolower (c);

                if (c>='a' && c<='z')
                {
                        q=c-'a';
                        if (q>24)
                                q-=24;

                        int quotient=q/3;
                        int remainder=q % 3;

                        switch (remainder)
                        {
                        case 0: for (int i=0; i<v; i++) rotate_f (quotient); break;
                        case 1: for (int i=0; i<v; i++) rotate_t (quotient); break;
                        case 2: for (int i=0; i<v; i++) rotate_l (quotient); break;
                        };
                };
                p++;
        };
};

void crypt (BYTE *buf, int sz, char *pw)
{
        int i=0;

        do
        {
                memcpy (cube, buf+i, 8*8);
                rotate_all (pw, 1);
                memcpy (buf+i, cube, 8*8);
                i+=64;
        };
        while (i<sz);
};

void decrypt (BYTE *buf, int sz, char *pw)
{
        char *p=strdup (pw);
        strrev (p);
        int i=0;

        do 
        {
                memcpy (cube, buf+i, 8*8);
                rotate_all (p, 3);
                memcpy (buf+i, cube, 8*8);
                i+=64;
        }
        while (i<sz);

        free (p); 
};

void crypt_file(char *fin, char* fout, char *pw)
{
        FILE *f;
        int flen, flen_aligned;
        BYTE *buf;

        f=fopen(fin, "rb");

        if (f==NULL)
        {
                printf ("Cannot open input file!\n");
                return;
        };

        fseek (f, 0, SEEK_END);
        flen=ftell (f);
        fseek (f, 0, SEEK_SET);

        flen_aligned=(flen&0xFFFFFFC0)+0x40;

        buf=(BYTE*)malloc (flen_aligned);
        memset (buf, 0, flen_aligned);

        fread (buf, flen, 1, f);

        fclose (f);

        crypt (buf, flen_aligned, pw);

        f=fopen(fout, "wb");

        fwrite ("QR9", 3, 1, f);
        fwrite (&flen, 4, 1, f);
        fwrite (buf, flen_aligned, 1, f);

        fclose (f);

        free (buf);

};

void decrypt_file(char *fin, char* fout, char *pw)
{
        FILE *f;
        int real_flen, flen;
        BYTE *buf;

        f=fopen(fin, "rb");

        if (f==NULL)
        {
                printf ("Cannot open input file!\n");
                return;
        };

        fseek (f, 0, SEEK_END);
        flen=ftell (f);
        fseek (f, 0, SEEK_SET);

        buf=(BYTE*)malloc (flen);

        fread (buf, flen, 1, f);

        fclose (f);

        if (memcmp (buf, "QR9", 3)!=0)
        {
                printf ("File is not encrypted!\n");
                return;
        };

        memcpy (&real_flen, buf+3, 4);

        decrypt (buf+(3+4), flen-(3+4), pw);

        f=fopen(fout, "wb");

        fwrite (buf+(3+4), real_flen, 1, f);

        fclose (f);

        free (buf);
};

// run: input output 0/1 password
// 0 for encrypt, 1 for decrypt

int main(int argc, char *argv[])
{
        if (argc!=5)
        {
                printf ("Incorrect parameters!\n");
                return 1;
        };

        if (strcmp (argv[3], "0")==0)
                crypt_file (argv[1], argv[2], argv[4]);
        else
                if (strcmp (argv[3], "1")==0)
                        decrypt_file (argv[1], argv[2], argv[4]);
                else
                        printf ("Wrong param %s\n", argv[3]);

        return 0;
};

[1] 实际上calloc()函数可替代malloc()和memset()两个函数。

[2] 请参考MSDN的说明:https://msdn.microsoft.com/en-us/library/9hby7w40(VS.80).aspx。