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!Ignore the shit quality of my code.