Log in

View Full Version : IDC script project


ZaiRoN
October 24th, 2002, 23:54
from Kayaker's last post:
"Yes, I've definitely just become a real fan of IDC scripts, lol. Made very good use of Toteu's Icedump-to-IDA script for a start, pretty sweet. I see there's a few interesting script examples at the Datarescue site as well, particulary the Bit Fields tutorial. For a long time I've been wanting to come up with an app/technique to identify the MSG parameter of SendMessage/ SendDlgItemMessage calls, a suitable script just might be the ticket and I think I'll play with the idea..."

This might be an interesting project

I have wrote a little script and might be a good starting point for those who want to try. The program look for Msg parameter of SendMessage functions:
Code:
#include <idc.idc>

static main()
{
auto start, end, temp;

start = SegStart(BeginEA()); // start address
end = SegEnd(BeginEA()); // end address

// you can cut this message. i have put them for...indeed i don't know why pp
Message("\n\n\nSimple script that try to find SendMessageA function";
Message("and try to identify his Msg parameter.";
Message("\nSearch starts from address: "+atoa(start)+"\n\n";

// walk through the entire section
for (start;start<end;start=NextAddr(start))
{
temp = Rfirst0(start);

if (Name(temp) == "SendMessageA"
{ // the function has been found
Message(atoa(start)+" refers to "+Name(temp));

// walk back and looks for 'push Msg'
start = PrevAddr(start);
while (GetOpnd(start,0) == 0)
{
Message("\nno valid instruction at: "+atoa(start));
start = PrevAddr(start);
}
Message("\npush hWnd found at: "+atoa(start));
start = PrevAddr(start);
while (GetOpnd(start,0) == 0)
{
Message("\nno valid instruction at: "+atoa(start));
start = PrevAddr(start);
}

// i have found the param...
Message("\nmsg param pushed at:" +atoa(start)+" is: "+GetOpnd(start, 0));

// now we have the Msg and we can make the check!
// we can use a very long switch that covers each wm_command
// or maybe we can work directly on a specific file
// filled with commands and values

start = start + ItemSize(start);
start = start + ItemSize(start);
start = start + ItemSize(start);
Message("\nrestart from: "+atoa(start)+"\n\n";
}
}

Message("\ngame over!!!\n";
}

nothing special, i'm not a guru_script. there are many useless messages; only for debug, you can cut.
from now, everyone can put here his ideas, comments, source and everything else.

for informations on idc take a look at: http://www.datarescue.com/idabase/
there's also an interesting directory under ida called 'IDC' with lots of scripts

I hope that it is what kayaker wanted to make...

good work!
regards,
ZaRoN

ZaiRoN
October 26th, 2002, 17:25
the new version of the script.
- sendmessage_param.idc : the script
- wm_messages : i have put some wm_ messages inside

you have to open the file sendmessage_param.idc and to change the path of the wm_messages file.

it's not perfect (i know...) but i hope to receive feedback like bugs, comments, improvements or everything else.

cheers,
ZaiRoN

Kayaker
October 26th, 2002, 19:25
Hi Zairon

I finally got a chance to start working with your first script, using it as a template with a small test program to begin to learn the *hundreds* of IDC script commands. There's not a lot of script examples out there, full-on plugins being the next step, though I found a few, and Mammon_'s Tales to Fravia's Grandson ...An IDA Primer...had a few good ones.

To debug the script I used a Warning command at various points to output diagnostic info to a message box, rather than the Message command which doesn't allow you to stop the code flow. I don't know if this is a bit of a bug, but if I stopped the displaying of the message boxes with "Hide", I could only get them back by closing IDA and reopening the file. I couldn't find an option for this, but do you know if there's a way to "Unhide" displaying of message box messages after you've selected to Hide them and decide to rerun a script?

Your new script runs fine and looks to be a good strategy, and seemed to catch the WM messages that were included. A minor arguable point, but the inner loop might run a bit faster with a small check before testing for SendMessageA. Instead of testing on a byte-by-byte basis it would be on a valid address-by-address basis, i.e.

Code:

Instead of

for (start ; start < end ; start = NextAddr(start))
{
temp = Rfirst0(start);
if (Name(temp) == "SendMessageA"

I used

for (start ; start < end ; start = NextAddr(start))
{
temp = Rfirst0(start);

while ( temp != BADADDR)
{
if (Name(temp) == "SendMessageA"



There is one more serious problem I came across though, with some programs such as Notepad (!!), IDA gave an error message on beginning to disassemble it that "Imports segment could be destroyed, uncheck 'make imports section' at loading time". Even though all imports were defined in the subsequent disassembly, i.e. SendMessageA calls were correctly disassembled, the idc script missed them entirely and the whole strategy failed. This even happened with calc.exe, where there was no import error message but again the idea failed. Can you imagine what this might mean for rebuilt dumped files? This might be a failing in Rfirst0 where the instruction isn't recognized as an Xref. Maybe we could use AddCodeXref to create a user defined call type? I don't know if this could solve the problem, just an idea.

Cheers,
Kayaker

nikolatesla20
October 27th, 2002, 05:00
Kayaker,

Hey

Just reading your post - my theory on why Notepad and Calc. exe fail is because some M$ programs like that, do not have a conventional IAT table. In effect, the first thunks are bound imports I beleive, so they do not point to the function names as normal exe's do.

Try out my tool, First Thunk Rebuilder, on the file, and running your script again, and see what happens.


http://www.woodmann.net/protools/files/utilities/firstthunk.zip

For dumped files to operate normally with ALL tools, THIS tool is very handy.

Let me repeat, YOU SHOULD ALWAYS USE THIS TOOL ON A DUMPED FILE. It brings the new IAT in the dump to 100% compatibility to how most files are linked.

enjoy...

-nt20

Kayaker
October 27th, 2002, 07:34
Hey, just thought I'd pass this on. I found a handy command to step back a set number of disassembled instructions to find the MSG parameter, rather than using the PrevAddr / GetOpnd combination to do it byte-wise. PrevNotTail - This function searches for the previous displayable address in the program.

Once you've found the SendMessageA call:

Code:

Call_Address = start; // Save original address

for ( i=0 ; i<2 ; i++ ) {
// MSG param is pushed 2nd last for SendMessageA
start = PrevNotTail(start);
}

// Warning(" MSG Param pushed at " + atoa(start) + " is " + GetOpnd(start, 0));

start = Call_Address; // Restore original address


For SendDlgItemMessageA it would be:
for ( i=0 ; i<3 ; i++ ) {
// MSG param is pushed 3rd last for SendDlgItemMessageA


Thanks nikolatesla20, I'll try it out and let you know

Kayaker

Kayaker
October 27th, 2002, 09:05
Hi nt20

As your app says, Bugs? What Bugs?

Sorry to say, minor problem, notepad and calc already contain the correct import addresses in the First Thunk in the raw file. (as you mentioned). The IMAGE_THUNK_DATA RVAs are not replaced (or don't need to be anyway) during PE loading. Before using your rebuilder the First Thunk contained i.e. BFE815EA, afterwards it contained the correct RVA to RegSetValueExA of 00006D88. However on loading the First Thunk is apparently not initialized so still contains the addy 00006D88 instead of the correct API address and crashes with an invalid page fault.

I didn't try it on a dumped file, but doesn't the same situation occur with a dump, the First Thunk is already intialized with the valid addresses for the system it was dumped on? I'm using the Win98SE version of notepad, maybe newer versions were linked differently.


Actually, I was just doing a bit more research on this trying to figure out if there is something defined in the PE header to "tell" the loader to initialize the First Thunk or not, or whether the PE Loader (which I've seen almost NO documentation on how it really works) somehow decides itself, when I found this interesting snippet from
Peering Inside the PE: A Tour of the Win32 Portable Executable File Format by Matt Pietrek which pretty much describes the situation:

----------------------------------------------
Why are there two parallel arrays of pointers to the IMAGE_IMPORT_BY_NAME structures? The first array (the one pointed at by the Characteristics field) is left alone, and never modified. It's sometimes called the hint-name table. The second array (pointed at by the FirstThunk field) is overwritten by the PE loader. The loader iterates through each pointer in the array and finds the address of the function that each IMAGE_IMPORT_BY_NAME structure refers to. The loader then overwrites the pointer to IMAGE_IMPORT_BY_NAME with the found function's address. The [XXXXXXXX] portion of the JMP DWORD PTR [XXXXXXXX] thunk refers to one of the entries in the FirstThunk array. Since the array of pointers that's overwritten by the loader eventually holds the addresses of all the imported functions, it's called the Import Address Table.

For you Borland users, there's a slight twist to the above description. A PE file produced by TLINK32 is missing one of the arrays. In such an executable, the Characteristics field in the IMAGE_IMPORT_DESCRIPTOR (aka the hint-name array) is 0. Therefore, only the array that's pointed at by the FirstThunk field (the Import Address Table) is guaranteed to exist in all PE files. The story would end here, except that I ran into an interesting problem when writing PEDUMP. In the never ending search for optimizations, Microsoft "optimized" the thunk array in the system DLLs for Windows NT (KERNEL32.DLL and so on). In this optimization, the pointers in the array don't point to an IMAGE_IMPORT_BY_NAME structure—rather, they already contain the address of the imported function. In other words, the loader doesn't need to look up function addresses and overwrite the thunk array with the imported function's addresses. This causes a problem for PE dumping programs that are expecting the array to contain pointers to IMAGE_IMPORT_BY_NAME structures. You might be thinking, "But Matt, why don't you just use the hint-name table array?" That would be an ideal solution, except that the hint-name table array doesn't exist in Borland files. The PEDUMP program handles all these situations, but the code is understandably messy.
-------------------------------------------

Interesting eh? MS strikes again, lol.

Kayaker

nikolatesla20
October 27th, 2002, 15:01
Sorry to hear about it not working - I tried it on my XP notepad and calc and they both ran just fine afterward..

Ya the whole IAT thing is messed up ! lol. The FirstThunks , if bound , are used if the PE loader sees the versions are correct. Come to think of it, really how would it know eh? Anyway then its supposed to look at the orginal table. But in some files that doesn't exist. HOwever, in all the microsoft linked files I've looked at it's always been there. (The "Original First Thunks"

That's the whole reason I made the tool, is on dumped files, we are rebuilding the Original First Thunks. Which works fine, but it leaves the FirstThunks pointing in the wrong place, and tools like Code Snippet Creator only look at the First Thunks *bug IMHO* so they need to be corrected.


Well, enough self-promotion.

-nt20

ZaiRoN
October 27th, 2002, 17:19
hi!
>I couldn't find an option for this, but do you know if there's a way to "Unhide" displaying of message box messages after you've selected to Hide them and decide to rerun a script?

imho, there's no way...

>for (start ; start < end ; start = NextAddr(start))
>...
>while ( temp != BADADDR)

sorry but i don't understand the reason why you have added the while statement.
I tryed this code but it goes in loop; I suppose you have also added some instruction -inside while statement- to update start or temp variables... anyway, there's another nice instruction: NextNotTail (do you remember PrevNotTail?)
now, I have a testing instruction_by_instruction:
Code:

instead of:
for (start;start<end;start=NextAddr(start))
{
temp = Rfirst0(start);
if (Name(temp) == "SendMessageA"

use:
for (start;start<end;start=NextNotTail(start))
{
temp = Rfirst0(start);
if (Name(temp) == "SendMessageA"


notepad and calc.exe:
this is a BIG problem. hmmm...I don't know if AddCodeXref could solve the problem but there's a stupid way to solve the problem.
from my notepad.exe file:

.text:00401378 call ds:SendMessageA

the script doesn't recognize the call. you try to use:

temp = 0x401378;
Message("\ncall ds:message points to: "+atoa(Dfirst(temp)));
Message("\nand the function is: "+Name(Dfirst(temp)));

and look at what happens. it would be an interesting way.
i'm trying the AddCodeXref function, i'll tell you next.

ZaiRoN

ZaiRoN
October 27th, 2002, 18:47
some bugs

we need to recognize the right 'push Msg' instruction:
.text:00401A7F push 0Eh ; Msg
.text:00401A81 mov eax, hWnd ; Msg = unknown message. please, update wm_messages file
.text:00401A86 push eax ; hWnd
.text:00401A87 call ds:SendMessageA

we need to recognize the right wm command:
.text:00404B06 mov ebx, 0C2h
.text:00404B0B push esi ; wParam
.text:00404B0C mov eax, hWnd
.text:00404B11 push ebx ; Msg = unknown message. please, update wm_messages file
.text:00404B12 push eax ; hWnd
.text:00404B13 call ds:SendMessageA

it will not be an easy thing...

ZaiRoN

ZaiRoN
October 27th, 2002, 20:29
you can solve the first problem using the function GetMnem that get mnemonics of instruction.
the idea is to go to the search of two previous push instruction.
Code:

Call_Address = start;// Save original address

for ( i=0 ; i<2 ; i++ ) {
// i'm looking for 2 push
if (GetMnem(PrevNotTail(start)) != "push"
i = i-1;
start = PrevNotTail(start);
}


ZaiRoN

Kayaker
October 28th, 2002, 06:25
Yeah, there are definitely some issues with implementing this well. Imports not recognized if the IAT is fupped, offset and indirect pushes of the MSG param (heh, hadn't thought of that one Z. ;-), and what else? Oh, how about support for non-WM_ messages that might be of interest? Not everyone needs to know that an app is sending a BN_DISABLE message to a button for example, but it could come in handy.

I've been thinking along the lines of creating message "families" and allowing the user to select any of them to try to ferret out any undefined non-WM_ messages. The main WM_ script can be automatic over the whole file, then any missing values can be selected with the mouse, a message family selected, and a new script can be run on one line via a hotkey. Lol, at this point I'd just be happy having the WM script run on notepad, but what the heck, ya gotta dream ;p

What I did was to get IDA to do the dirty work of creating an Enum table of WM message constants, which is included in the main idc file, and having the MSG param replaced with a descriptive definition with one simple instruction. To create the Enum table you simply select any one WM_ MSG operand with your mouse, right click and select "Use standard symbolic constant". Then select File/Produce/Dump typinfo to IDC file, which will spit out *all* the message constants within that family (wm, bm/bn, lb/lbs,...). Then you can paste the Enum table into your idc file and access the fields with

OpEnumEx(start, 0, GetEnum("MACRO_WM", 0);

This will parse through the table (named MACRO_WM) and match up the MSG constant with a valid text definition for it.

Here's what I've got so far, which seems to work on "normal" test programs, but these other few issues with a nonstandard setup really need to be looked at next! It looks like you've got some good ideas there Zairon. If anyone has any comments on any of this it'd be great.

Kayaker

ZaiRoN
October 28th, 2002, 17:17
>heh, hadn't thought of that one Z. ;-)
This *mini* project is being becoming a *large* project

I like your new strategy; it is much elegant and clear, good work!

it works well on *normal* programs but for the others? maybe the best thing to do is to study a new case at the time. Which will be the first? Seems to be there the embarrassment of the choice ;ppp

later,
ZaiRoN

Kayaker
October 28th, 2002, 19:41
Quote:
Originally posted by ZaiRoN
>
This *mini* project is being becoming a *large* project


Lol, yes once you get into it it kind of takes on a life of its own doesn't it?. It is fun though...

Case in point, I just updated the above zip file so it avoids totally the problem with the "funny" imports of notepad et al. It seems that using the combination

----------------
for (start ; start < end ; start = NextNotTail(start)) {
temp = Rfirst0(start);

if (Name(temp) == "SendMessageA" {
-----------------

only works if the First Thunks aren't bound, as per earlier discussions. Instead, the updated file seems to work no matter what, plus it catches the differences in how an import can be defined by IDA

---------------
for (start ; start < end ; start = NextNotTail(start)) {
temp = Rfirst0(start);

if (GetOpnd(start, 0) == "SendMessageA" || GetOpnd(start, 0) == "ds:SendMessageA" {
----------------


There's at least one other situation that needs to be taken care of, when you get something like
mov esi, ds:SendMessageA
...
call esi ; SendMessageA

Note that the *comment* is put in by IDA, it recognizes what the call is, so what we need is a way to return comments and check them as well. I haven't found a command yet to do that actually, there is MakeComm (make comment), but I haven't seen a corresponding 'GetComm' or something like that, any ideas?

Mind you, my eyes are getting blurred by this point from reading the damned idc help file and being frustrated with the vagueness of it all and lack of cross referencing! Anyway, the new file should work better.

cya,
Kayaker

ZaiRoN
October 28th, 2002, 22:14
Quote:
There's at least one other situation that needs to be taken care of, when you get something like
mov esi, ds:SendMessageA
...
call esi ; SendMessageA
Seems there is no way to get comments; is it possible!?! References about this language are not much.
In the meanwhile we can use something like this to solve the problem:

if ((GetOpnd(start,1) == "ds:SendMessageA" || (GetOpnd(start,1) == "dword ptr ds:SendMessageA")
{
Message("\n"+atoa(start)+" mov "+GetOpnd(start,0)+", "+GetOpnd(start,1));
--- here we have to *trace* the code looking for 'call <register>' ---
}

later...
ZaiRoN

ZaiRoN
October 29th, 2002, 00:51
Hi Kayaker,

I have noticed that we use instructions Rfirst and Rnext uselessly. Try to remove them and see with your eyes. Only GetOpnd is needed.
Look at this:

if ((GetOpnd(start,1) == "ds:SendMessageA" || (GetOpnd(start,1) == "dword ptr ds:SendMessageA")
{
temp = start;
for (temp;temp<end;temp=NextNotTail(temp))
{
if (GetMnem(temp) == "call"
{
if (GetOpnd(temp, 0) == GetOpnd(start,0))
{
addComment(temp);
temp = end;
}
}
}
}

This only recognizes the first 'call <reg>'. We need to check when the time to stop the 'call <reg>' search is. For today it is enough, I go to bed...

'night!
ZaiRoN

[yAtEs]
October 29th, 2002, 17:37
Hi,

Just reading through your posts and saw you labelling up API
params, this was probably just an example for your IDC scripts
but i thought i'd mention you can parse C header files and
function prototypes into IDA (:

yates.

crUsAdEr
October 29th, 2002, 18:06
Hi guys,

Just jump back to join the board and i simply think this is such a good idea, thanx for reminding me of it... my scripting skill is about 2 hrs old, C programming a few days old ... but it already helped me inline patch our fav protector ...

Regards,
crUsAdEr

P.S : Global : i hope you use idc script to break the first 4 layers ... if not then maybe you should try now

Kayaker
October 29th, 2002, 19:26
Quote:
Originally posted by crUsAdEr
Hi guys,

Just jump back to join the board and i simply think this is such a good idea, thanx for reminding me of it... my scripting skill is about 2 hrs old, C programming a few days old ... but it already helped me inline patch our fav protector ...

Regards,
crUsAdEr

P.S : Global : i hope you use idc script to break the first 4 layers ... if not then maybe you should try now


Howdy Crusader,

I think many of us are wallowing around in the shallow end right now with idc scripting ;-) There's just as many IDA "Pro's" around as well (forgive the pun), but in general there just doesn't seem to be much in the line of 'primers' on the subject of scripts and/or plugins. It seems we're stuck with the IDA help file and what info we can glean from the few examples that seem to be around. Or maybe this project might be enough to kickstart people on the subject...

Funny you mention that combination. How's your Russian?

Plugin ??? IDA Pro: AsPack/ASPR ????????????

http://reversing.net/tools/004/aspr.html

Cheers,
Kayaker