How to code
your own Iparty client
- Reverse Engineering Tutorial by
sn00pee -
Some Notes
Enjoy this nice essay and learn a bit :)
... please note that my english may suck sometimes, I do my best ;)
Some lil' notes 'bout the format I use in this tut: normal text is written in
white, links in blue,
headlines in yellow, asm code in green
and code in red, just
to keep it clear. I highly recommend using a resolution of 1024x768, else the
file may look weird. Long listings of code are separated with Cuts.
What you need
- | Iparty v1.2(I think it's the only version out there), get it from http://www.bumpkinland.com/iparty/download.asp |
- |
SoftICE(I use version 4 under Win2k, but older ones should work too), get it from http://w3.to/protools |
- | W32DSM v8.9 or higher, get it from http://w3.to/protools |
- | a C compiler(I used Visual C++6, get it from http://www.microsoft.com :P) |
- | (good) knowledge about C, if you didn't code in C yet then go get some books and learn it, C is by far the most powerfull high-level programming-language |
- | (very good) knowledge
about Win32, get iczelion tutorials from http://win32.cjb.net,
I also recommend Win95/98 Programming(author is Charles Petzold, released by Microsoft Press) |
Please note that this tutorial wasn't
written for newbies, if you've never encountered a win32-loop then better go and
get some books/other tutorials, I don't wanna get bombed with hundreds of
fucking newbie-mails.
First Words
Well, after using the Iparty-client
quite often, I've got stuck by its fucking gfx. So I had the idea of coding my
own client(especially after ultraschall reversed the server ;), but there is
only one big problem: Iparty uses some audio compression I dunno, that's why there
seems to be only one solution: Coding a program, which starts the 'real' Iparty and then
gains control over it.
Sounds a bit utopian, but I'll show that it is
possible...prepare for a looooong tutorial(I recommend some cokes or coffee ;).
PART 1 - Doing all the init stuff and
getting the window handles
Here we go, first I created a new resource file with Visual C++6 which contains the controls of the original Iparty, here's a small screenshot:
Wow, what a hard work ;) ... after taking
a long breath and doing a lil break, I now feel ready to go on ;)...
Let's do some brainstorming... think what we need to gain control over the Iparty
client...i give you some time ;)...well, got it? Since we are good win32 coders, we know that(because of the
windows event based message-system) the only thing we need to control a window
is his handle(->hwnd). If we have it, we can do everything we want with the
respective control(for example a editbox, listbox, button etc.)...but how tfe
f*** do we get the window handles? :)
My first idea was to fire up Iparty, get the main window handle via FindWindow
and then search for the child controls(FindWindowEx), but unfortunately, this is a bit complex
since the client uses some MFC-Classes(btw: MFC sux ;) what makes it very
difficult for us...so we need to find another solution: Let's do some debugging,
fire up SoftICE. We know that Iparty has to create the controls via a call to
CreateWindowEx, let's find out where this happens. Set a breakpoint on CreateWindowExA(->bpx
CreateWindowExA for all those dummies out there ;). Now close SICE and start
Iparty.exe...after some seconds, we break at memory location 41F0AD, in W32DSM
it looks like this:
:0041F089 FF75D0
push [ebp-30]
:0041F08C FF75D4 push [ebp-2C]
:0041F08F FF75D8 push [ebp-28]
:0041F092 FF75DC push [ebp-24]
:0041F095 FF75E0 push [ebp-20]
:0041F098 FF75E4 push [ebp-1C]
:0041F09B FF75E8 push [ebp-18]
:0041F09E FF75EC push [ebp-14]
:0041F0A1 FF75F0 push [ebp-10]
:0041F0A4 FF75F4 push [ebp-0C]
:0041F0A7 FF75F8 push [ebp-08]
:0041F0AA FF75FC push [ebp-04]
* Reference To: USER32.CreateWindowExA, Ord:0052h
|
:0041F0AD FF15ECD74300 Call dword ptr [0043D7EC]
:0041F0B3 8BD8 mov ebx, eax
:0041F0B5 E81BFFFFFF call 0041EFD5
:0041F0BA 85C0 test eax, eax
If you take a close look at it you
should figure out, that this memory position called 15 times(as far as I
remember ;), everytime it creates a new control and by looking at the parameters in
ebp-8 and ebp-c we learn which ones. So let's freeze the program
at location 41F0B3 and read the value in eax(which contains the window handle
returned by CreateWindowExA). Here is the corresponding code(explanation follows
below):
-------------------------------------------------------- CUT
--------------------------------------------------------
First the constants(I use 'em to make it clearer)
#define FIRST_BREAKPOINT 0x41F0B3
#define SECOND_BREAKPOINT 0x41F0BA
#define MAX_LOOP 15
#define EVENT_MEMBER 1
#define EVENT_SPEECHWND 2
#define EVENT_TALK 3
#define EVENT_SKIP 4
#define EVENT_SENDING 6
#define EVENT_RECEIVING 7
#define EVENT_QUEUE 8
#define EVENT_MESSAGE 11
#define EVENT_CHAT 12
#define EVENT_CONTROLBAR 14
Real code starts here...initialize the
structures
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
Execute original iparty.exe, note the
CREATE_SUSPENDED flag, which indicates, we want the process to be created
in freezed state to memory patch the program first.
if(!CreateProcess("iparty.exe", GetCommandLine(), NULL, NULL, FALSE, CREATE_SUSPENDED, NULL,
NULL, &StartupInfo, &ProcessInfo))
{
MessageBox(hwnd, "Could not execute iparty.exe", "Error",
MB_ICONERROR | MB_OK);
return 0;
}
Prepare for memory-patching,
otherwise we might get a general protection fault.
VirtualProtectEx(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, sizeof(WriteBuffer),
PAGE_EXECUTE_READWRITE, &OldProtected);
VirtualProtectEx(ProcessInfo.hProcess, (LPVOID)SECOND_BREAKPOINT, sizeof(WriteBuffer),
PAGE_EXECUTE_READWRITE, &OldProtected);
Read initial values and store them in buffers.
ReadProcessMemory(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, &SaveBuffer, sizeof(SaveBuffer),
NULL);
ReadProcessMemory(ProcessInfo.hProcess, (LPVOID)SECOND_BREAKPOINT, &SaveBuffer2,
sizeof(SaveBuffer2), NULL);
Write loop at breakpoint 1.
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, &WriteBuffer,
sizeof(WriteBuffer), NULL);
Resume thread and enter loop.
ProcContext.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
ResumeThread(ProcessInfo.hThread);
Do it 15 times.
for(iCounter = 1; iCounter <= MAX_LOOP;)
{
Sleep(50);
if(!GetThreadContext(ProcessInfo.hThread, (CONTEXT*)&ProcContext))
return 0;
if(ProcContext.regEip != FIRST_BREAKPOINT)
continue;
Program has reached breakpoint 1,
now get the handle.
else {
switch(iCounter)
{
case EVENT_MEMBER:
hwndMember = (HWND)ProcContext.regEax;
break;
case EVENT_SPEECHWND:
hwndSpeechWnd = (HWND)ProcContext.regEax;
break;
case EVENT_TALK:
hwndTalk = (HWND)ProcContext.regEax;
break;
case EVENT_SKIP:
hwndSkip = (HWND)ProcContext.regEax;
break;
case EVENT_CHAT:
hwndChat = (HWND)ProcContext.regEax;
break;
case EVENT_MESSAGE:
hwndMessage = (HWND)ProcContext.regEax;
break;
case EVENT_CONTROLBAR:
hwndControlBar = (HWND)ProcContext.regEax;
break;
}
Write loop at breakpoint 2 and restore value at breakpoint 1.
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)SECOND_BREAKPOINT, &WriteBuffer,
sizeof(WriteBuffer), NULL);
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, &SaveBuffer,
sizeof(SaveBuffer), NULL);
Enter second loop.
for(;;)
{
Sleep(10);
if(!GetThreadContext(ProcessInfo.hThread, (CONTEXT*)&ProcContext))
return FALSE;
if(ProcContext.regEip != SECOND_BREAKPOINT)
continue;
else {
Program has reached breakpoint 2.
Write loop at breakpoint 1 and restore value at breakpoint 2.
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, &WriteBuffer,
sizeof(WriteBuffer), NULL);
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)SECOND_BREAKPOINT, &SaveBuffer2,
sizeof(SaveBuffer2), NULL);
break;
}
}
iCounter++;
}
Restore
original data.
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, &SaveBuffer,
sizeof(SaveBuffer), NULL);
--------------------------------------------------------
CUT --------------------------------------------------------
That's it, please keep in
mind that I already wrote a tutorial about this technich, that's why you won't
find much explanations in the above code(I included the tutorial in this zip,
its name is "tutold.txt", you should read it if you don't understand
the code).
You may ask why I implemented 2 loops, well after we get the
handle we need, it is necessary to kill the first loop because the program has
to go on, the only possibility we have is to write a second loop at the next
asm-instruction, wait till the program reached this point, write back teh first
loop and kill the second one(confusing, isn't it? ;). Some might think we
can simply do the following:
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, &SaveBuffer,
sizeof(SaveBuffer), NULL);
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)FIRST_BREAKPOINT, &WriteBuffer,
sizeof(WriteBuffer), NULL);
This won't work since we're either
too fast(in this case nothing would happen) or to slow(we might miss some
breakpoints).
The next bit confusing think is the switch-instruction and its cases. The order
in which Iparty creates its control windows will always be the same, we need to
know this order...fortunately I already figured it out and wrote a small list of
constants that indicates when which control is created.
Another thing, let's take a look at the declaration of the ProcContext struct:
FAKECONTEXT
ProcContext;
Maybe you wonder why I didn't use
the default PROCCONTEXT struct delivered in the winnt.h. In contrast to the
masm-includes,
the visual c++ structure definition doesn't support members like regEax, regEip,
regEbx etc., to prevent us from from the silly standard ones I copied the
masm structure definiton and converted it to C-style, here's the code:
typedef struct _FAKECONTEXT {
DWORD ContextFlags;
DWORD iDr0;
DWORD iDr1;
DWORD iDr2;
DWORD iDr3 ;
DWORD iDr6;
DWORD iDr7;
FLOATING_SAVE_AREA FloatSave;
DWORD regGs;
DWORD regFs;
DWORD regEs;
DWORD regDs;
DWORD regEdi;
DWORD regEsi;
DWORD regEbx;
DWORD regEdx;
DWORD regEcx;
DWORD regEax;
DWORD regEbp;
DWORD regEip;
DWORD regCs;
DWORD regFlag;
DWORD regEsp;
DWORD regSs;
char ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} FAKECONTEXT;
At last, we have to get rid of the annoying Nag and to hide the main window, what is easy. Switch to SICE, set a breakpoint on ShowWindow and start SICE. We land here:
:0040198C 6A05
push 00000005
:0040198E 8B07 mov eax, dword ptr [edi]
:00401990 8B481C mov ecx, dword ptr [eax+1C]
:00401993 51 push ecx
* Reference To: USER32.ShowWindow, Ord:0215h
|
:00401994 FF157CD94300 Call dword ptr [0043D97C]
This is the Nag, let's see what our win32.hlp says:
BOOL ShowWindow(
HWND hWnd, // handle to window
int nCmdShow // show state of window );
The push 05 is the second parameter, 05 means SW_SHOW, so let's simply change the push 5 to push 0(0 means SW_HIDE), so the window will be shown in hidden state. Here is the code we need:
#define NAG_OFFSET 0x40198D
#define NAG_BYTES 0
temp1 = (char)NAG_BYTES;
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)NAG_OFFSET, &temp1, sizeof(temp1), NULL);
(keep in mind: temp1 is a
single byte, temp2 a WORD and temp3 a DWORD, I didn't implement variable-declarations
in this file, look into the source-file if you want to have further infos)
Type F5 in SICE and it will break a second time:
:0042C0ED E81C000000
call 0042C10E
:0042C0F2 83FEFF cmp esi, FFFFFFFF
:0042C0F5 7412 je 0042C109
:0042C0F7 56 push esi
:0042C0F8 FF771C push [edi+1C]
* Reference To: USER32.ShowWindow, Ord:0215h
|
:0042C0FB FF157CD94300 Call dword ptr [0043D97C]
This is where the main window is shown to the user, let's do a simple je->jmp patch, heres the source:
#define MAINWINDOW_HIDE_OFFSET 0x42C0F5
#define MAINWINDOW_HIDE_BYTES 0xEB
temp1 = (char)MAINWINDOW_HIDE_BYTES;
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)MAINWINDOW_HIDE_OFFSET, &temp1,
sizeof(temp1), NULL);
Poo, lean back and relax, we finally finished part 1 ;)
PART 2 - Doing the neccesary reverse engineering
1.) The message-editbox
The message-editbox(how I call it), is the window where you enter your
chat-messages. If someone types a text in our editbox(remember the resource.rc
file) we have to send Iparty a message to make it believe, someone typed it in its
message-window. Now since we have the handle, this is quite easy, I wrote a
small function which will do the work for us:
void SendMessageString(char *lpString)
{
int i;
for(i = 0; i < (int)strlen(lpString); i++)
SendMessage(hwndMessage, WM_CHAR, lpString[i], 0);
SendMessage(hwndMessage, WM_CHAR, '\n', 0);
return;
}
It simply sends WM_CHAR messages and a terminating linefeed. To catch the return button in our program I subclassed(read iczelion-tutorials if you dunno what subclassing is all about) the editbox, here's the code of the callback-function:
BOOL CALLBACK MsgEditWndProc(HWND hButton, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
char Buffer1[1000];
switch(iMsg)
{
case WM_KEYDOWN:
switch(wParam)
{
case VK_RETURN:
GetDlgItemText(hwnd, EDIT_MESSAGE, Buffer1, sizeof(Buffer1));
SendMessageString(Buffer1);
Buffer1[0] = '\0';
SetDlgItemText(hwnd, EDIT_MESSAGE, Buffer1);
return 0;
} break;
}
return CallWindowProc((WNDPROC)OldMsgEditWndProc, hButton, iMsg, wParam, lParam);
}
To get it working you first
have to do the following instruction in the WM_INITDIALOG message:
OldMsgEditWndProc = SetWindowLong(GetDlgItem(hwnd, EDIT_MESSAGE), GWL_WNDPROC,
(long)MsgEditWndProc);
Easy, isn't it? ;)
2.) The chat-editbox
It took a while till I finally found a good way to solve this problem. The idea
behind this trick is to create a thread after having received the window
handles. This thread will check in a constant interval if the text of the
chat-window has changed and if so, copy this text into our chat-window. Here
is the code:
-------------------------------------------------------- CUT --------------------------------------------------------
500kb should be enough
#define MAX_CHAT_SIZE 500000
DWORD WINAPI RuntimeChecks(void)
{
Initialize chat window variables
if(!(hmemChat = GlobalAlloc(GPTR, MAX_CHAT_SIZE)))
{
MessageBox(hwnd, "Not enough memory!", "Error", MB_ICONERROR | MB_OK);
SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
return 0;
}
if(!(hmemChat2 = GlobalAlloc(GPTR, MAX_CHAT_SIZE)))
{
MessageBox(hwnd, "Not enough memory!", "Error", MB_ICONERROR | MB_OK);
SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
return 0;
}
SendMessage(hwndChat, WM_GETTEXT, MAX_CHAT_SIZE, (LPARAM)hmemChat);
Enter thread loop
for(;;)
{
Sleep(100);
Check if Iparty is still loaded
if(!FindWindow(NULL, "Internet Party Line - IPARTY"))
{
MessageBox(hwnd, "Iparty has been shut down, exiting...", "Error",
MB_ICONERROR | MB_OK);
SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
return 0;
}
Check if chat-window
content has changed
SendMessage(hwndChat, WM_GETTEXT, MAX_CHAT_SIZE, (LPARAM)hmemChat);
if(strcmp(hmemChat, hmemChat2) != 0)
{
SetDlgItemText(hwnd, EDIT_CHAT, hmemChat);
strcpy(hmemChat2, hmemChat);
SetFocus(GetDlgItem(hwnd, EDIT_MESSAGE));
}
}
}
--------------------------------------------------------
CUT --------------------------------------------------------
Please note this
isn't the whole code of the thread-function, only the one that has something to
do with our chatbox.
That's it, the textbox is now under our control ;).
3.) The listbox
The listbox contains the actual members of the session, this is maybe the
easiest part, we simply add some lines of code to the above thread which checks
whether the listbox-members changed and if so, copy/delete the corresponding
nick to/from our list. Here's the code:
-------------------------------------------------------- CUT --------------------------------------------------------
Check if someone joined
the session
for(i = 0; i < SendMessage(hwndMember, LB_GETCOUNT, 0, 0); i++)
{
SendMessage(hwndMember, LB_GETTEXT, i, (LPARAM)Buffer1);
if(SendDlgItemMessage(hwnd, LIST_MEMBER, LB_FINDSTRING, 0, (LPARAM)Buffer1) == LB_ERR)
{
SendDlgItemMessage(hwnd, LIST_MEMBER, LB_ADDSTRING, 0, (LPARAM)Buffer1);
wsprintf(Buffer2, "*** %s joined the session", Buffer1);
AddChatMessage(Buffer2);
}
}
Check if someone left
the session
for(i = 0; i < SendDlgItemMessage(hwnd, LIST_MEMBER, LB_GETCOUNT, 0, 0); i++)
{
SendDlgItemMessage(hwnd, LIST_MEMBER, LB_GETTEXT, i, (LPARAM)Buffer1);
if(SendMessage(hwndMember, LB_FINDSTRING, 0, (LPARAM)Buffer1) == LB_ERR)
{
SendDlgItemMessage(hwnd, LIST_MEMBER, LB_DELETESTRING, i, 0);
wsprintf(Buffer2, "*** %s left the session", Buffer1);
AddChatMessage(Buffer2);
}
}
Synchronize the selection
SendDlgItemMessage(hwnd, LIST_MEMBER, LB_SETCURSEL, SendMessage(hwndMember,
LB_GETCURSEL, 0, 0), 0);
--------------------------------------------------------
CUT --------------------------------------------------------
4.) The Controlbar
The controlbar is this nice label on the button of the Iparty window which shows the current status of the program(for example if you are connect/disconnected to a server). Of course we need this information, let's store it in the status window on the top of our program. Once again, we add some code to the thread function:
Check control bar
SendMessage(hwndControlBar, WM_GETTEXT, sizeof(Buffer1), (LPARAM)Buffer1);
if(strcmp(StatusText, Buffer1) != 0)
{
SetDlgItemText(hwnd, STATIC_INFO, Buffer1);
strcpy(StatusText, Buffer1);
}
That's it, nothing more and nothing less :).
5.) Opening a connection
Now we come to the real interesting part ;)...we need to get our connect-button
working. Therefor, we have to open the Iparty connection-dialog, enter the
server-address in the edit-box and then perform a click on the OK-Button. Sounds
hard, let's begin: Start the original iparty, open SICE and type "hwnd
iparty"(without quotation marks), you will now see all the Iparty windows
with their handles in front. The only one we're interested in, is the dialog
handle(it should be the first handle SICE shows you, simply look for a "(Dialog)" note). Since we're good win32 coders, we know that if the
user clicks on the "File/Open Location" button(to connect to a server)
Windows sends Iparty a WM_COMMAND message. We have to catch this message... no
problem with sice, enter "BMSG dialoghandle WM_COMMAND". Now go back
to Iparty click on "File/Open Location" and SICE pops up. Type
"dd esp" to see the parameters of our callback-function, forget the
first DWORD, then we have the handle of the window the WM_COMMAND message was
send to(this is not the handle we entered, but who cares as long as it's working
;), then there's a 111 DWORD, which stands for WM_COMMAND, the next value is the
wParam parameter, this is the thing we need, write it down this (should be
00008003). Now we have the message we need to pop up the connection-dialog.
Next
step: We need the handle of the edit-box to paste the server-address. We
will do this by calling FindWindowEx, the first parameter is the handle to the
connection-dialog-window(we get it by calling FindWindow), while the third parameter is the classname(for edit boxes "edit"). After we pasted the
server-address, we have to perform a click, once again solved via WM_COMMAND,
now we have everything we need. Here's the code:
-------------------------------------------------------- CUT --------------------------------------------------------
Note that we need to call
PostMessage since SendMessage would only return after the connection-dialog has
been shut down.
case BUTTON_CONNECT:
PostMessage(ihwnd, WM_COMMAND, 0x00008003, 0);
while(!(chwnd = FindWindow(NULL, "Go To URL")));
GetDlgItemText(hwnd, EDIT_CONNECT, Buffer1, sizeof(Buffer1));
hwndOKButton = FindWindowEx(chwnd, NULL, "EDIT", NULL);
for(i = 0; i < (int)strlen(Buffer1); i++)
SendMessage(hwndOKButton, WM_CHAR, Buffer1[i], 0);
SendMessage(chwnd, WM_COMMAND, 1,0);
return TRUE;
--------------------------------------------------------
CUT --------------------------------------------------------
6.) The talk-button
Another interesting one. We will to the same steps as in the last part. Fire up
SICE, type "hwnd iparty", scroll down and get the handle of the
talk-button(in fact it's not a button, it's a AfxWnd-class(the last one in the
list)). Now enter "BMSG buttonhandle WM_COMMAND", quit SICE and click
on the "Push to Talk" button, SICE will popup, let's look what the
stack says, we see that wParam has the value 00000069, this is the figure we need
to push down the button. Of course, we also need to know the message to switch the button back to his initial, unpushed state. Go back to Iparty, release your
mouse-button and SICE will once again pop up. This time wParam contains
0000006A. Now we have everything we need. Note that once again I subclassed our
push-button, here's the source we need:
-------------------------------------------------------- CUT --------------------------------------------------------
BOOL CALLBACK TalkBtnWndProc(HWND hButton, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
case WM_LBUTTONDOWN:
if(!bTalkSending)
{
SendMessage(hwndSpeechWnd, WM_COMMAND, 0x69, 0);
bTalkSending = TRUE;
}
break;
case WM_LBUTTONUP:
if(bTalkSending)
{
SendMessage(hwndSpeechWnd, WM_COMMAND, 0x6A, 0);
bTalkSending = FALSE;
}
break;
case WM_KEYDOWN:
switch(wParam)
{
case VK_SPACE:
if(!bTalkSending)
{
SendMessage(hwndSpeechWnd, WM_COMMAND, 0x69, 0);
bTalkSending = TRUE;
}
break;
} break;
case WM_KEYUP:
switch(wParam)
{
case VK_SPACE:
if(bTalkSending)
{
SendMessage(hwndSpeechWnd, WM_COMMAND, 0x6A, 0);
bTalkSending = FALSE;
}
break;
} break;
}
return CallWindowProc((WNDPROC)OldTalkBtnWndProc, hButton, iMsg, wParam, lParam);
}
--------------------------------------------------------
CUT --------------------------------------------------------
7.) The skip-button
Nothing new in this part, do "BMSG buttonhandle WM_COMMAND", click on
the button, we see that wParam is 0000006B, that's all we need:
-------------------------------------------------------- CUT --------------------------------------------------------
case BUTTON_SKIP:
SendMessage(hwndSpeechWnd, WM_COMMAND, 0x6B, (LPARAM)hwndSkip);
return TRUE;
--------------------------------------------------------
CUT --------------------------------------------------------
8.) The queue(or progress) bar
Now we come to the real difficult part ;), no other control made me more
headaches than this one ;). After hours of debugging I finally found a solution.
The problem with this queue-bar is, that it isn't a standard control which
reacts to SendMessage-calls. My first idea was to blit the content of the
queue-window in our own control, but since the Iparty main-window is hidden,
this doesn't work. So we only have one possibility: Memory-patching the
executable(like we did with the Nag, etc.).
It's time for another brilliant idea :p ... let's switch on our brain. We know
that Iparty has to draw the queue-window itself(since it ain't a standard
control, as mentioned above). To begin drawing, it has to call GetDC(which
returns a HDC). Here is the funtion proto of GetDC:
HDC GetDC( HWND hWnd // handle to a window );
To get this HDC, it
needs the handle of the corresponding window, in this case the queue bar. So
wouldn't it be kewl if we could "fake" this, so that Iparty doesn't
draw in his, but in our window? Therefor we need change the handle with which
GetDC is called. Unfortunately this isn't easy, so pay attention now ;)...I
traced through many memory locations till I found the best RVA where we could
"implant" our patch. Here is the asm-code:
:0040BB4C 50
push eax
:0040BB4D E89E1B0000 call 0040D6F0
:0040BB52 8B8EF4010000 mov ecx, dword ptr [esi+000001F4]
:0040BB58 51
push ecx
:0040BB59 8D8EB0010000 lea ecx, dword ptr [esi+000001B0]
:0040BB5F E88C1B0000 call 0040D6F0
:0040BB64 5E
pop esi
:0040BB65 C20400 ret 0004
The call at 40BB5F leads
to the GetDC, I figured out, that at this offset, esi+1CC will point to the
handle we have to replace with our control's one. To do this, we have execute
the following instructions:
mov eax,
HANDLE
mov dword ptr[esi+1CC], eax
This sounds good, but also leads a
problem: We don't have enough space. So we need to replace the
lea
ecx, dword ptr [esi+000001B0]
instruction with a call
to a function that was written by us and looks like this:
mov eax,
HANDLE
mov dword ptr[esi+1CC], eax
lea
ecx, dword ptr [esi+000001B0]
ret
We will patch this code
at RVA 42EC20. I will now show the whole code:
-------------------------------------------------------- CUT --------------------------------------------------------
First the constants:
#define NEW_CALL_OFFSET 0x40BB59
//We will implant our call here
#define NEW_CALL_BYTESA 0xE8
//Opcode for call
#define NEW_CALL_BYTESB 0xB8
//Opcode for mov eax,XXX
#define NEW_CALL_BYTESC 0x000001CC8689
//Opcode for mov dword
ptr[esi+1CC], eax
#define NEW_CALL_BYTESD 0x000001B08E8D
//Opcode for lea
ecx, dword ptr [esi+000001B0]
#define NEW_FUNCTION_ADDRESS 0x42EC20
//This is where we store the function
#define NEW_FUNCTION_ADDRESS2 0x0230C2
//needed for the call instruction
code:
Write the call with one NOP
temp1 = (char)NEW_CALL_BYTESA;
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)NEW_CALL_OFFSET, &temp1, sizeof(temp1), NULL);
temp3 = NEW_FUNCTION_ADDRESS2;
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)(NEW_CALL_OFFSET + sizeof(temp1)), &temp3,
sizeof(temp3), NULL);
temp1 = (char)0x90;
WriteProcessMemory(ProcessInfo.hProcess,
(LPVOID)(NEW_CALL_OFFSET + sizeof(temp1) + sizeof(temp3)), &temp1,
sizeof(temp1), NULL);
Write the "mov
eax,xxx" intrcuction
temp1 = (char)NEW_CALL_BYTESB;
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)NEW_FUNCTION_ADDRESS, &temp1, sizeof(temp1),
NULL);
temp3 = (DWORD)GetDlgItem(hwnd, STATIC_QUEUE);
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)(NEW_FUNCTION_ADDRESS + 1), &temp3,
sizeof(temp3), NULL);
Write the "mov
dword ptr[esi+1CC], eax"
instruction
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)(NEW_FUNCTION_ADDRESS + 5), &NewCallBytes2,
sizeof(NewCallBytes2), NULL);
Write the "lea
ecx, dword ptr [esi+000001B0]"
instruction and the ret
NewCallBytes2 = NEW_CALL_BYTESD;
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)(NEW_FUNCTION_ADDRESS + 11), &NewCallBytes2,
sizeof(NewCallBytes2), NULL);
temp1 = (char)0xC3;
WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)(NEW_FUNCTION_ADDRESS + 17), &temp1,
sizeof(temp1), NULL);
--------------------------------------------------------
CUT --------------------------------------------------------
It's complicated, but it works! ;)
That's it! Maybe you
wonder why I didn't implement a sending/receive option, the answer is very
simple: I'm not motivated any more ;)
PART 3 - The ending
I'm sure you've learned
much about reverse engineering in this tutorial, feel the power of win32! ;)
If you have further questions, I'm reachable in #learn2crack in efnet under my
nick.
Greets(no order, sorry if
I forgot someone):
Ultraschall(thx for publishing ;) - BerSerkA -
Mendoza - PeeGee - abex - LaZaRuS - risc - Defiler - Dead-Mike
CrazyKnight - Blade - BlackB - viny - Pitou - Gizmo and the whole BLiZZARD crew!