; ----------------------------------------
;
;  (C) Alex Demchenko (alex@ritlabs.com)
;          http://www.cobans.net
;
; ----------------------------------------


;
; Decode 99b-2002a passwords from .dat (using .idx) files
;
;
;
; #########################################################################

; Idx entry
ICQ99B_IDX struct
  Code          DWORD ? ; If entry is valid the it's set to -2
  Number        DWORD ? ; DAT entry number
  Next          DWORD ? ; Next IdxRec offset
  Prev          DWORD ? ; Previous IdxRec offset
  DatPos        DWORD ? ; Offset in .dat file
ICQ99B_IDX ends

; Dat entry
ICQ99B_DAT struct
  Len           DWORD ?
  FillType      DWORD ?
  Number        DWORD ?
  Command       BYTE  ?
  Signature     BYTE 15 dup(?)
ICQ99B_DAT ends

; Idx file header
ICQ99B_IDX_HDR struct
  V             BYTE 12 dup(?)  ; 3 DWORDS: Version
  IdxRoot       DWORD ?         ; Pointer to Root entry
  DbVersion     DWORD ?         ; .dat file version
ICQ99B_IDX_HDR ends

; Database versions
DBV_99B equ 14   ; 99b
DBV_2000a equ 17 ; 2000a
DBV_2000b equ 18 ; 2000b
DBV_2001a equ 19 ; 2001a, 2001b, 2002a, 2003a

; Property types
DBT_CHAR equ 64h
DBT_BYTE equ 65h
DBT_WORD equ 66h
DBT_INTEGER equ 67h
DBT_DWORD equ 68h
DBT_LONGINT equ 69h
DBT_LNTS equ 6bh
DBT_SUBLIST equ 6dh
DBT_REC equ 6eh
DBT_BLOB equ 6fh

DBT_MYDETAILS equ 0e4h

DatOfsCount equ 5 ; ! Do not forget about count while changing DatOfs's

        .data
                IdxSignature    db      04,00,00,00,20,00,00,00,08,00,00,00
                DatOfs          db      "99b",0,0,0
                                db      "2000a",0
                                db      "2000b",0
                                db      "2001a",0
                                db      "2002a",0

                szICQ99bKeyPath db      "SOFTWARE\Mirabilis\ICQ\DefaultPrefs",0
                szICQ99bSearch  db      "\*.dat",0

        .data?
                ICQ99bPass      db      100h dup(?)
                ICQ99bCryptIV   dd      ?
                ICQ99bUIN       dd      ?
        .code

; #########################################################################

Read99bProperty proc uses edi esi hDat: DWORD
        LOCAL   dwNameLen, bRead, dwDataType: DWORD

        mov     dwNameLen, 0
        invoke  ReadFile, hDat, addr dwNameLen, 2, addr bRead, NULL
        invoke  ReadFile, hDat, offset lpBuf3, dwNameLen, addr bRead, NULL
        mov     dwDataType, 0
        invoke  ReadFile, hDat, addr dwDataType, 1, addr bRead, NULL

        .IF     (dwDataType == DBT_CHAR || dwDataType == DBT_BYTE)
                invoke  SetFilePointer, hDat, 1, NULL, FILE_CURRENT
        .ELSEIF (dwDataType == DBT_WORD || dwDataType == DBT_INTEGER)
                invoke  SetFilePointer, hDat, 2, NULL, FILE_CURRENT
        .ELSEIF (dwDataType == DBT_DWORD || dwDataType == DBT_LONGINT)
                szText  sz99bCryptIV, "99BCryptIV"
                invoke  lstrcmp, offset lpBuf3, addr sz99bCryptIV
                .IF     ((eax == 0) && ([ICQ99bCryptIV] == 0))
                        invoke  ReadFile, hDat, offset ICQ99bCryptIV, 4, addr bRead, NULL
                .ELSE
                        szText  sz99bUIN, "UIN"
                        invoke  lstrcmp, offset lpBuf3, addr sz99bUIN
                        .IF     ((eax == 0) && ([ICQ99bUIN] == 0))
                                invoke  ReadFile, hDat, offset ICQ99bUIN, 4, addr bRead, NULL
                        .ELSE
                                invoke  SetFilePointer, hDat, 4, NULL, FILE_CURRENT
                        .ENDIF
                .ENDIF
        .ELSEIF (dwDataType == DBT_LNTS)
                mov     dwNameLen, 0
                invoke  ReadFile, hDat, addr dwNameLen, 2, addr bRead, NULL

                invoke  lstrcmpi, offset lpBuf3, offset szPassword
                .IF     ((eax == 0) && (byte ptr[ICQ99bPass] == 0) && (dwNameLen > 0))
                        invoke  ReadFile, hDat, offset ICQ99bPass, dwNameLen, addr bRead, NULL
                .ELSE
                        invoke  SetFilePointer, hDat, dwNameLen, NULL, FILE_CURRENT
                .ENDIF
        .ELSEIF (dwDataType == DBT_SUBLIST)
                ; Fuck this type many times up
                invoke  ReadFile, hDat, addr dwNameLen, 4, addr bRead, NULL
                invoke  ReadFile, hDat, addr dwDataType, 1, addr bRead, NULL
                mov     esi, dwNameLen
                test    esi, esi
                jz      @yep
@loop:
                .IF     dwDataType == DBT_LNTS
                        mov     dwNameLen, 0                        
                        invoke  ReadFile, hDat, addr dwNameLen, 2, addr bRead, NULL
                        invoke  SetFilePointer, hDat, dwNameLen, NULL, FILE_CURRENT
                .ELSEIF dwDataType == DBT_REC
                        invoke  SetFilePointer, hDat, 2, NULL, FILE_CURRENT
                        invoke  ReadFile, hDat, addr dwNameLen, 4, addr bRead, NULL
                        mov     edi, dwNameLen
                        test    edi, edi
                        jz      @rec_end
@rec_loop:              
                        ; Yeah, call recursively
                        invoke  Read99bProperty, hDat
                        dec     edi
                        jnz     @rec_loop
@rec_end:
                .ENDIF
                dec     esi
                jnz     @loop
@yep:
        .ELSEIF (dwDataType == DBT_BLOB)
                invoke  ReadFile, hDat, addr dwNameLen, 4, addr bRead, NULL
                invoke  SetFilePointer, hDat, dwNameLen, NULL, FILE_CURRENT
        .ENDIF
                
        ret
Read99bProperty endp

; #########################################################################

Read99bDatEntry proc uses esi edi hDat: DWORD
        LOCAL   DatRec: ICQ99B_DAT
        LOCAL   bRead, dwSeparator, dwDword: DWORD

        invoke  ReadFile, hDat, addr DatRec, sizeof ICQ99B_DAT, addr bRead, NULL

        cmp     DatRec.Command, DBT_MYDETAILS
        jne     @end
        cmp     DatRec.Number, 1005
        jne     @end

        mov     dwSeparator, 0
        invoke  ReadFile, hDat, addr dwSeparator, 2, addr bRead, NULL

        invoke  ReadFile, hDat, addr dwDword, 4, addr bRead, NULL
        cmp     dwDword, 'USER'
        jne     @end
        invoke  ReadFile, hDat, addr dwDword, 4, addr bRead, NULL
        cmp     dwDword, 6
        jne     @end
        invoke  SetFilePointer, hDat, 6, NULL, FILE_CURRENT

        ; Wav info
        .IF     ([ebx][ICQ99B_IDX_HDR.DbVersion] == DBV_2000a || [ebx][ICQ99B_IDX_HDR.DbVersion] == DBV_2000b)
                invoke  ReadFile, hDat, addr dwDword, 4, addr bRead, NULL                
                mov     esi, dwDword
                test    esi, esi
                jz      @wav_end
@wav_loop:
                invoke  SetFilePointer, hDat, 10, NULL, FILE_CURRENT
                mov     dwDword, 0
                invoke  ReadFile, hDat, addr dwDword, 2, addr bRead, NULL
                invoke  SetFilePointer, hDat, dwDword, NULL, FILE_CURRENT

                dec     esi
                jnz     @wav_loop
@wav_end:        
                invoke  SetFilePointer, hDat, 2, NULL, FILE_CURRENT
        .ENDIF

        ; Some modifications in ICQ2002a
        .IF     ((dwSeparator >= 533) && ([ebx][ICQ99B_IDX_HDR.DbVersion] == DBV_2001a))
                invoke  SetFilePointer, hDat, 6, NULL, FILE_CURRENT
        .ENDIF


        ; Read property block
        invoke  ReadFile, hDat, addr dwDword, 4, addr bRead, NULL
        mov     esi, dwDword
        test    esi, esi
        jz      @end

@block_loop:
        invoke  SetFilePointer, hDat, 2, NULL, FILE_CURRENT        
        invoke  ReadFile, hDat, addr dwDword, 4, addr bRead, NULL
        mov     edi, dwDword
        test    edi, edi
        jz      @block_end

@block_read:
        invoke  Read99bProperty, hDat
        dec     edi
        jnz     @block_read

@block_end:
        dec     esi
        jnz     @block_loop

@end:

        ret
Read99bDatEntry endp

; #########################################################################
DoCreateFile proc
        invoke  CreateFile, esi, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0
        mov     [edi], eax
        inc     eax
        ret
DoCreateFile endp

GetICQ99bPassword proc uses esi edi ebx lpIdxPath, lpDatPath: DWORD
        LOCAL   hIdx, hDat: DWORD
        LOCAL   lpIdxFileHdr: ICQ99B_IDX_HDR
        LOCAL   IdxRec: ICQ99B_IDX
        LOCAL   bRead: DWORD

        mov     byte ptr[ICQ99bPass], 0
        mov     [ICQ99bCryptIV], 0
        mov     [ICQ99bUIN], 0

        lea     ebx, lpIdxFileHdr
        
        ; Open files
        mov     esi, lpIdxPath
        lea     edi, hIdx
        call    DoCreateFile
        jz      @end

        mov     esi, lpDatPath
        lea     edi, hDat
        call    DoCreateFile
        jz      @end

        ; Check db headers & version
        invoke  ReadFile, hIdx, addr lpIdxFileHdr, sizeof ICQ99B_IDX_HDR, addr bRead, NULL
        invoke  CompareMem, addr lpIdxFileHdr, offset IdxSignature, 12
        test    eax, eax
        jz      @end

        mov     eax, lpIdxFileHdr.DbVersion

        cmp     eax, DBV_99B
        je      @read_indexes
        cmp     eax, DBV_2000a
        jl      @end
        cmp     eax, DBV_2001a
        jg      @end

        ; Read idx entries
        mov     eax, lpIdxFileHdr.IdxRoot
        mov     IdxRec.Next, eax

@read_indexes:
        cmp     IdxRec.Next, -1
        je      @end

        invoke  SetFilePointer, hIdx, IdxRec.Next, NULL, FILE_BEGIN
        inc     eax
        jz      @end

        invoke  ReadFile, hIdx, addr IdxRec, sizeof ICQ99B_IDX, addr bRead, NULL
        cmp     bRead, sizeof ICQ99B_IDX
        jne     @end
        .IF     (IdxRec.Code == -2 ) && (IdxRec.DatPos != -1)
                invoke  SetFilePointer, hDat, IdxRec.DatPos, NULL, FILE_BEGIN
                inc     eax
                jz      @end
        
                ; Parse dat entry
                invoke  Read99bDatEntry, hDat
        .ENDIF
        
        jmp     @read_indexes

@end:
        ; Close open file handles
        inc     hIdx
        jz      @F
        invoke  CloseHandle, hIdx
@@:
        inc     hDat
        jz      @F
        invoke  CloseHandle, hDat
@@:
        ret

        ret
GetICQ99bPassword endp

; #########################################################################

IScanForDatFiles proc uses edi lpDat: DWORD
        LOCAL   FindFileData: WIN32_FIND_DATA
        LOCAL   hFind: DWORD
        LOCAL   lpIdx: DWORD

        invoke  LocalAlloc, LMEM_FIXED, 1024
        mov     lpIdx, eax

        invoke  lstrlen, lpDat
        mov     edi, eax
        inc     edi

        invoke  lstrcat, lpDat, offset szICQ99bSearch

        invoke  FindFirstFile, lpDat, addr FindFileData
        mov     hFind, eax
        inc     eax
        jz      @end

@find_loop:
        mov     eax, lpDat
        mov     byte ptr[edi+eax], 0
        invoke  lstrcat, lpDat, addr FindFileData.cFileName

        invoke  lstrcpy, lpIdx, lpDat
        invoke  lstrlen, lpIdx
        mov     edx, lpIdx
        mov     dword ptr[edx+eax-3], 'xdi'

        invoke  GetICQ99bPassword, lpIdx, lpDat

        .IF     (([ICQ99bUIN] != 0) && (byte ptr[ICQ99bPass] != 0))
                szText  szICQ99bFmt, "UIN(%lu): [%lu] %s",13,10
                invoke  wsprintf, offset lpBuf4, addr szICQ99bFmt, ICQ99bUIN, ICQ99bCryptIV, offset ICQ99bPass
                invoke  lstrcat, GlobalBuffer, offset lpBuf4
        .ENDIF

        invoke  FindNextFile, hFind, addr FindFileData
        test    eax, eax
        jnz     @find_loop

        invoke  FindClose, hFind

@end:
        invoke  LocalFree, lpIdx

        ret        
IScanForDatFiles endp

; #########################################################################

GetICQ99bRegPaths proc hKey: DWORD
        LOCAL   hkHandle, DataType, BufSize: DWORD

        invoke  RegCreateKey, hKey, offset szICQ99bKeyPath, addr hkHandle
        test    eax, eax
        jnz     @end

        szText  szICQ99bdbBase, "%s Database"
        invoke  wsprintf, offset lpBuf2, addr szICQ99bdbBase, edi

        mov     BufSize, MAX_PATH
        invoke  RegQueryValueEx, hkHandle, offset lpBuf2, NULL, addr DataType, offset lpBuf1, addr BufSize
        test    eax, eax
        jnz     @end_and_free

        ; Snac For Passwords
        invoke  IScanForDatFiles, offset lpBuf1

@end_and_free:
        invoke  RegCloseKey, hkHandle

@end:
        ret
GetICQ99bRegPaths endp

; #########################################################################

GetICQ99bOutput proc uses edi esi
        ; Write the greet message
        szText  szICQ99bGreet, "[99b]",13,10
        invoke  lstrcat, GlobalBuffer, addr szICQ99bGreet

        mov     esi, DatOfsCount
        lea     edi, DatOfs
@loop:
        invoke  GetICQ99bRegPaths, HKEY_LOCAL_MACHINE
        invoke  GetICQ99bRegPaths, HKEY_CURRENT_USER

        add     edi, 6
        dec     esi
        jnz     @loop

        ret
GetICQ99bOutput endp
