Join the discord

MiniPro : Reverse engineering the InfoIC.dll

13 Sep, 2015 00:22
From quite some time already I got interested in the art of electronics and more specifically into hardware reverse engineering.
Since I'm mostly interested in the device's firmware, I have to deal with EEPROM or Flash chips.
MiniPro turned out to be my weapon of choice for reading or programming memory chips.
However, sometimes the device I'm playing with, uses a chip that MiniPro doesn't yet have in its database.

Unfortunately, MiniPro doesn't use "user friendly" database for the supported chips and I'm left with no other option, than wait for the authors to _eventually_ add it into the database, or, alternatively, reverse engineer the database, and add whatever I want by myself.

The author choose a DLL file as a database - InfoIC.dll

Removing the DLL from the program folder leads to error message and program crash.


Something I should mention is that the program is really, really badly coded. There are a lot of places with obvious buffer overflows, and crashing the program instead of safely terminating it when the crucial DLL is not found, is the most obvious sign, about its code quality.

Whatever, lets dug into InfoIC.dll itself:


A hand full of export functions. Seems easy enough.
??0CInfoIC@@QAE@XZ, ??4CInfoIC@@QAEAAV0@ABV0@@Z and ?nInfoIC@@3HA are junk exports, so I'll ignore them.

From the executable side, the important functions are initialized here:
Assembly.text:004424E2                 push    offset aInfoic_dll	; "infoIC.dll"
.text:004424E7                 call    ds:LoadLibraryA
.text:004424ED                 cmp     eax, ebp
.text:004424EF                 mov     hInstance, eax
.text:004424F4                 jz      loc_44259B
.text:004424FA                 mov     edi, ds:GetProcAddress
.text:00442500                 push    offset aGeticstru	; "GetIcStru"
.text:00442505                 push    eax
.text:00442506                 call    edi ; GetProcAddress
.text:00442508                 mov     ecx, hInstance
.text:0044250E                 push    offset aGeticmfc		; "GetIcMFC"
.text:00442513                 push    ecx
.text:00442514                 mov     _GetIcStru, eax
.text:00442519                 call    edi ; GetProcAddress
.text:0044251B                 mov     edx, hInstance
.text:00442521                 push    offset aGeticlist	; "GetIcList"
.text:00442526                 push    edx
.text:00442527                 mov     _GetIcMFC, eax
.text:0044252C                 call    edi ; GetProcAddress
.text:0044252E                 mov     _GetIcList, eax
.text:00442533                 mov     eax, hInstance
.text:00442538                 push    offset aGetmfcstru	; "GetMfcStru"
.text:0044253D                 push    eax
.text:0044253E                 call    edi ; GetProcAddress
.text:00442540                 mov     ecx, hInstance
.text:00442546                 push    offset aGetdllinfo	; "GetDllInfo"
.text:0044254B                 push    ecx
.text:0044254C                 mov     _GetMfcStru, eax
.text:00442551                 call    edi ; GetProcAddress
.text:00442553                 mov     ecx, _GetIcStru
.text:00442559                 mov     _GetDllInfo, eax
.text:0044255E                 cmp     ecx, ebp
.text:00442560                 jz      short loc_44257E
.text:00442562                 cmp     _GetIcMFC, ebp
.text:00442568                 jz      short loc_44257E
.text:0044256A                 cmp     _GetIcList, ebp
.text:00442570                 jz      short loc_44257E
.text:00442572                 cmp     _GetMfcStru, ebp
.text:00442578                 jz      short loc_44257E
.text:0044257A                 cmp     eax, ebp
.text:0044257C                 jnz     short loc_442593
.text:0044257E loc_44257E:
.text:0044257E                 mov     edx, hInstance
.text:00442584                 push    edx
.text:00442585                 call    ds:FreeLibrary
.text:0044258B                 mov     hInstance, ebp
.text:00442591                 jmp     short loc_44259B
.text:00442593 loc_442593:
.text:00442593                 cmp     hInstance, ebp
.text:00442599                 jnz     short loc_4425A7
.text:0044259B loc_44259B:
.text:0044259B                 push    ebp
.text:0044259C                 push    ebp
.text:0044259D                 push    offset aIcListDllFileE	; "IC List Dll file error!"
.text:004425A2                 call    sub_488284
So, it seems like this the only check that verifies if InfoIC.dll lib is valid or not.

I've compiled a DLL, containing the exports GetDllInfo, GetIcList, GetIcMFC, GetIcStru and GetMfcStru and made all of them as voids, with no arguments.
C#include <windows.h>

#define DLL_EXPORT __declspec(dllexport)

void DLL_EXPORT GetIcStru() { }
void DLL_EXPORT GetIcMFC() { }
void DLL_EXPORT GetIcList() { }
void DLL_EXPORT GetMfcStru() { }
void DLL_EXPORT GetDllInfo() { }

BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    switch (fdwReason) {
        case DLL_PROCESS_ATTACH: break;
        case DLL_PROCESS_DETACH: break;
        case DLL_THREAD_ATTACH: break;
        case DLL_THREAD_DETACH: break;
    }
    return TRUE;
}
Now the program no longer shows the warning message, but it crashed again.

The next thing I did, was to load MiniPro.exe in OllyDbg with its original InfoIC.dll, and set a breakpoint:
Assembly.text:004424E2        push    offset aInfoic_dll	; "infoIC.dll"
.text:004424E7        call    ds:LoadLibraryA		; here
.text:004424ED        cmp     eax, ebp
.text:004424EF        mov     hInstance, eax
.text:004424F4        jz      loc_44259B
...
When the library is loaded, set breakpoints on the entry points of the exports and write down the execution order.

At execution, only GetMfcStru got called, so I've start the reverse engineering here:
Assembly.text:10001020                 public GetMfcStru
.text:10001020 GetMfcStru      proc near
.text:10001020
.text:10001020 arg_0           = dword ptr  4
.text:10001020 arg_4           = dword ptr  8
.text:10001020
.text:10001020                 mov     eax, [esp+arg_0]		; Manufacturer ID
.text:10001024                 push    esi
.text:10001025                 push    edi
.text:10001026                 mov     edi, [esp+8+arg_4]	; memcpy : destination address
.text:1000102A                 lea     ecx, [eax+eax*8]		; / calculate offset
.text:1000102D                 lea     edx, [eax+ecx*2]		; \
.text:10001030                 mov     ecx, 13h			; memcpy : number of DWORDs to copy
.text:10001035                 xor     eax, eax			; return 0
.text:10001037                 lea     esi, unk_101691E0[edx*4]	; memcpy : source address
.text:1000103E                 rep movsd			; memcpy
.text:10001040                 pop     edi
.text:10001041                 pop     esi
.text:10001042                 retn
.text:10001042 GetMfcStru      endp

The program obviously accept two arguments - arg_0 and arg_4. The rest is simply a memcpy() implementation, copying a piece of data from address of the DLL to a buffer provided as arg_4 based on a offset provided by arg_0.
Knowing the size of the copied buffer (0x13 DWORDs), I've took some data from unk_101691E0:
InfoIC.dllOffset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

001691E0  00 00 00 00 65 00 00 00 41 43 45 00 00 00 00 00  ....e...ACE.....
001691F0  00 00 00 00 00 00 00 00 00 00 00 00 41 43 45 20  ............ACE 
00169200  54 65 63 68 6E 6F 6C 6F 67 79 00 00 00 00 00 00  Technology......
00169210  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00169220  00 00 00 00 B0 47 04 10 47 00 00 00              ....°G..G...

It looks like some pretty simple structure, and after some research back and forward through main executable and DLL, I was able to obtain a valid structure:
Ctypedef struct MfcStru {
    DWORD	mfc_id;			// ID of the Manufacturer
    DWORD	mfc_logo;		// Manufacturer logo ID. Logos are hardcoded as BMP resources into the DLL
    char	mfc_name_short[20];	// Short manufacturer name (eg. "ATMEL", "ALI", etc.)
    char	mfc_name_full[40];	// Full manufacturer name (eg. "Atmel Corporation", "Acer Laboratories Inc", etc.)
    byte*	mfc_ic_struc;		// Pointer to the ICs structures buffer
    DWORD	mfc_ic_count;		// Number of ICs for this manufacturer
} MfcStru;

Having this information, I've updated my DLL like so:
Cvoid DLL_EXPORT GetMfcStru(DWORD id, byte *result) {
    MfcStru Mfc = {0, 0, "TEST (short)", "TEST (long)", NULL, 0};
    memcpy(result, &Mfc, sizeof(MfcStru));
}

Executing it as-is, again crashed the program here:
Assembly00461DC9  |. 8D4424 00      LEA EAX,DWORD PTR SS:[ESP]		; ESP is holding the result
00461DCD  |. 56             PUSH ESI
00461DCE  |. 57             PUSH EDI
00461DCF  |. 50             PUSH EAX				; result
00461DD0  |. 51             PUSH ECX				; id
00461DD1  |. FF15 B0CB5200  CALL DWORD PTR DS:[52CBB0]		; call to GetMfcStru
00461DD7  |. A1 18C75200    MOV EAX,DWORD PTR DS:[52C718]	; EAX = 0, as iterator for MfcStru.mfc_ic_struc
00461DDC  |. 83C4 08        ADD ESP,8
00461DDF  |. 8D0440         LEA EAX,DWORD PTR DS:[EAX+EAX*2]
00461DE2  |. 8D14C0         LEA EDX,DWORD PTR DS:[EAX+EAX*8]
00461DE5  |. 8B4424 4C      MOV EAX,DWORD PTR SS:[ESP+4C]	; points to MfcStru.mfc_ic_struc
00461DE9  |. 8A0C90         MOV CL,BYTE PTR DS:[EAX+EDX*4]	; crash occurs here

I can easily calculate the size of MfcStru.mfc_ic_struc structure like so:
if, EAX = 1 (eg. second entry, since the first is positioned at 0)
1+1*2 = 3;
3+3*8 = 27;

Because those are DWORDs, 27*4 gives me 108 or 0x06C as final size in bytes for MfcStru.mfc_ic_struc entry.

The starting address is basically MfcStru.mfc_ic_struc value 0x100447B0, minus the DLL image size 0x10000000, or simply 0x000447B0:
InfoIC.dllOffset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

000447B0  01 00 00 00 00 00 00 00 01 00 00 00 41 43 45 32  ............ACE2
000447C0  34 43 30 32 41 00 00 00 00 00 00 00 00 00 00 00  4C02A...........
000447D0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000447E0  00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00  ................
000447F0  00 00 00 00 00 00 80 00 08 00 00 00 08 00 00 00  ......€.........
00044800  10 27 00 00 00 00 00 00 00 00 00 00 00 00 00 00  .'..............
00044810  00 00 00 00 00 00 00 08 80 00 00 00              ........€...

So, what I did next was to put those bytes in a buffer, assign it to MfcStru.mfc_ic_struc and set the MfcStru.mfc_ic_count to 1, as if there's only one entry in the IC database.
Cbyte ic_struc[] = "\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x41\x43\x45\x32"\
                  "\x34\x43\x30\x32\x41\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\
                  "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\
                  "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"\
                  "\x00\x00\x00\x00\x00\x00\x80\x00\x08\x00\x00\x00\x08\x00\x00\x00"\
                  "\x10\x27\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\
                  "\x00\x00\x00\x00\x00\x00\x00\x08\x80\x00\x00\x00";

void DLL_EXPORT GetMfcStru(DWORD id, byte *result) {
    MfcStru Mfc = {0, 0, "TEST (short)", "TEST (long)", ic_struc, 1};
    memcpy(result, &Mfc, sizeof(MfcStru));
}

Alright, the program no longer crashes on execution.

However, clicking on the button for Device select, still crashes it.
Same way as before, I've set breakpoints to the export functions entry points, clicked the button and it turned out, now GetIcMFC is executed.
Stepping over it, crashes the program, so I took a look what happens inside the original InfoIC.dll:
Assembly.text:10001090                 public GetIcMFC
.text:10001090 GetIcMFC        proc near
.text:10001090
;...
.text:10001090 arg_0           = dword ptr  4
.text:10001090 arg_4           = dword ptr  8
.text:10001090 arg_8           = dword ptr  0Ch
.text:10001090
.text:10001090                 sub     esp, 7Ch
.text:10001093                 push    ebx
.text:10001094                 push    ebp
.text:10001095                 push    edi
.text:10001096                 mov     edi, [esp+88h+arg_0]
.text:1000109D                 or      ecx, 0FFFFFFFFh
.text:100010A0                 xor     eax, eax
.text:100010A2                 xor     ebp, ebp
.text:100010A4                 xor     edx, edx
.text:100010A6                 repne scasb
.text:100010A8                 not     ecx
.text:100010AA                 dec     ecx
.text:100010AB                 xor     ebx, ebx
.text:100010AD                 mov     eax, ecx
.text:100010AF                 mov     ecx, ds:dword_1016BA40
.text:100010B5                 cmp     ecx, ebp
.text:100010B7                 mov     [esp+88h+var_7C], edx
.text:100010BB                 mov     [esp+88h+var_70], eax
.text:100010BF                 mov     [esp+88h+var_6C], ebx
.text:100010C3                 jle     loc_1000131F
;...
.text:10001313                 mov     eax, [esp+8Ch+var_7C]	; return value (case 1)
.text:10001317                 pop     esi
.text:10001318                 pop     edi
.text:10001319                 pop     ebp
.text:1000131A                 pop     ebx
.text:1000131B                 add     esp, 7Ch
.text:1000131E                 retn
.text:1000131F loc_1000131F:
.text:1000131F                 pop     edi
.text:10001320                 pop     ebp
.text:10001321                 mov     eax, edx			; return value (case 2)
.text:10001323                 pop     ebx
.text:10001324                 add     esp, 7Ch
.text:10001327                 retn
.text:10001327 GetIcMFC        endp

I was interested in the arguments passed and the eventual returned value only.

Obviously there are three arguments arg_0, arg_4 and arg_8 and a DWORD return value.
Some executions later, I've found that arg_0 and arg_4 are memory buffers, while arg_8 is a DWORD/int value.
That's enough information to tweak my implementation of GetIcMFC:
CDWORD DLL_EXPORT GetIcMFC(byte *arg_0, byte *arg_4, DWORD arg_8) {
    return 0;
}

The program no longer crashes when I click the device select button, but the device select window doesn't contain anything either:


Before digging deep into GetIcMFC and actually reverse engineer it, I've once again set breakpoints at the exports EPs, to get the complete exports call order.

After GetIcMFC, GetIcList and GetDllInfo were executed.
To obtain arguments and return values, I've proceed the same way like I did so far with GetIcMFC and the result was this:
C// Four arguments, and DWORD return value
DWORD DLL_EXPORT GetIcList(byte *arg_0, byte *arg_4, DWORD arg_8, DWORD arg_C) {
    return 0;
}

// Two arguments, and DWORD return value
DWORD DLL_EXPORT GetDllInfo(byte *arg_0, byte *arg_4) {
    return 0;
}

GetDllInfo looks short and simple, so I started with it:
C.text:100014E0                 public GetDllInfo
.text:100014E0 GetDllInfo      proc near
.text:100014E0                 mov     eax, [esp+arg_0]
.text:100014E4                 mov     edx, [esp+arg_4]
.text:100014E8                 mov     dword ptr [eax], 64h			; arg1
.text:100014EE                 mov     ecx, ds:dword_1016BA40
.text:100014F4                 mov     [edx], ecx				; arg2
.text:100014F6                 mov     edx, ds:dword_1016BA40
.text:100014FC                 xor     eax, eax					; zero EAX
.text:100014FE                 test    edx, edx
.text:10001500                 jle     short locret_10001513
.text:10001502                 push    esi
.text:10001503                 mov     ecx, offset dword_10169228
.text:10001508 loc_10001508:
.text:10001508                 mov     esi, [ecx]
.text:1000150A                 add     ecx, 4Ch
.text:1000150D                 add     eax, esi
.text:1000150F                 dec     edx
.text:10001510                 jnz     short loc_10001508
.text:10001512                 pop     esi
.text:10001513 locret_10001513:
.text:10001513                 retn
.text:10001513 GetDllInfo      endp

Both arguments are used as "out" parameters, and are set to 0x64 for arg_0 and pointer to dword_1016BA40 for arg_4.
That dword_1016BA40 is pointing to a constant value of 0x88, and after some research in the InfoIC's guts I found out 0x88 is the total number of IC Manufacturers in the whole database.
Nice.

dword_10169228 is pointing to the MfcStru.mfc_ic_count value of the first Manufacturer, that pointer is increased by 0x4C and there's an obvious loop between 10001508 and 10001513.
With a little more research it turned out that GetDllInfo iterates through the Manufacturers list and adds up their mfc_ic_count parameters.
The result is then returned and it's used for the static "IC Total:" counter.

I have only one hardcoded Manufacturer with one IC, so I can set arg_4 to 1 (as one Manufacturer) and return 1 (as one IC) for now:
CDWORD DLL_EXPORT GetDllInfo(DWORD *arg_0, DWORD *arg_4) {
    *arg_0 = 0x64;
    *arg_4 = 0x01;
    return 1;
}


The device select window now shows there's one IC in the database. Good.

Next I took a look at GetIcMFC's guts. The function code is way to bulky to just paste it here.
After a hour or so in debugging, I was finally able to figure it out completely.
The function takes three arguments that at the end, I called "search", "IDlist" and "type".
search is the first argument, that holds the search word typed by the user inside the Device search field;
IDlist is used as return buffer, where the IDs of the Manufacturers, which ICs contains the user typed search word are stored;
type is the type of the IC, taken from the radio boxes - "ALL", "ROM/FLASH/NVRAM", "MCU/MPU", "PLD/GAL/CPLD", "SRAM/DRAM" or "Logic IC"

Basically, the idea of the function is to obtain a list of Manufacturers IDs (IDlist) as DWORD values in the buffer accepted as second argument.
If there's a "type" chosen by the user, the manufacturers got filtered by that, and only manufacturers having IC's of the chosen type are added to the list.
Same goes for the "search" argument, so only Manufacturers who's ICs contain the search phrase are added to the list.
The return value is the numbers of the obtained manufacturers in the IDlist.

For now I will just return 1, as i have only one Manufacturer in my test list:
CDWORD DLL_EXPORT GetIcMFC(char *search, byte *IDlist, DWORD type) {
    return 1;
}

The GetIcList function comes next and it's quite similar to GetIcMFC.
It takes four parameters - search, IDlist, id and type.
The most significant difference is that, it has an "id" argument, that specifies which Manufacturer is about to get searched.

IDlist now holds the IDs of the ICs, that are again filtered by a "search" phrase and/or "type".
The return value is the number of ICs in the IDlist.

Again, I will just return 1.
CDWORD DLL_EXPORT GetIcList(char *search, byte *IDlist, DWORD id, DWORD type) {
    return 1;
}


I'm getting closer.

The last export function was GetIcStru.
I wasn't able to find any call to this function in the main executable.
However the function itself is simple:
Assembly.text:10001050 GetIcStru       proc near
.text:10001050
.text:10001050 arg_0           = dword ptr  4
.text:10001050 arg_4           = dword ptr  8
.text:10001050 arg_8           = dword ptr  0Ch
.text:10001050
.text:10001050                 mov     eax, [esp+arg_0]			; Manufacturer ID
.text:10001054                 push    esi
.text:10001055                 push    edi
.text:10001056                 lea     ecx, [eax+eax*8]
.text:10001059                 lea     edx, [eax+ecx*2]			; Manufacturer offset
.text:1000105C                 mov     eax, [esp+8+arg_4]		; MfcStru.mfc_ic_struc
.text:10001060                 test    eax, eax
.text:10001062                 mov     esi, ds:off_10169224[edx*4]	; Manufacturer data
.text:10001069                 jle     short loc_10001074
.text:1000106B                 lea     eax, [eax+eax*2]
.text:1000106E                 lea     eax, [eax+eax*8]			; IC offset
.text:10001071                 lea     esi, [esi+eax*4]			; IC data
.text:10001074 loc_10001074:
.text:10001074                 mov     edi, [esp+8+arg_8]
.text:10001078                 mov     ecx, 1Bh
.text:1000107D                 rep movsd							
.text:1000107F                 pop     edi
.text:10001080                 xor     eax, eax				; Always return 0
.text:10001082                 pop     esi
.text:10001083                 retn
.text:10001083 GetIcStru       endp

That's basically all the information I need, to do something useful from here. So I did.
Instead of using hardcoded database, I've made my own implementation of InfoIC.dll, that uses external and easy to edit XML file.
Though I didn't reverse engineered the whole IC Structure format, I was able to push it to the point where you can easily change some of the IC information.

This is still work in progress, but you can download the attached DLL source, XML database and notes about the database format.

DOWNLOAD COMPLETE SOURCE, BUILDS AND XML

Comments

* You have an opinion? Let us all hear it!

Guest 21 Jun, 2023 19:39
Epic work, thank you sir
Guest 22 Jan, 2020 06:08
Can you change the SPI speed for a chip?
Guest 03 May, 2018 16:27
This is excellent work! Expect a donation soon :)
Guest 21 Jan, 2018 13:03
Thanks for the work you've done!!
XpoZed 10 Mar, 2016 20:24
My answer is a little late, but here you go: http://www.nullsecurity.org/i/showoff/parse_InfoIC.c

Ignore the shit quality of my code.
Guest 15 Feb, 2016 00:48
Can you post the code to generate the xml definitions file from the factory InfoIC.dll?
© nullsecurity.org 2011-2024 | legal | terms & rules | contacts