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!