I think Marcus is right, this looks like copy protection.

Let's engage on a little log analysis shall we? This is something that just comes with practice ...

Just after the program starts, it does a CreateProcess:

0023:Call kernel32.CreateProcessA(406cd2f0 "C:\\Program Files\\Easy CD-DA Extractor 7\\ezcddax.exe",403810d8 "ezcddax.exe",00000000,00000000,00000001,00000004,00000000,00000000,406cd2ac,406cd61c) ret=00750ec9

ezcddax is the real program and what you launched is just a stub.

Note the 6th parameter: it's 4, which is CREATE_SUSPENDED. So, the new process isn't supposed to start.

[ snip lots of traces from CreateProcess ]

0023:Ret kernel32.CreateProcessA() retval=00000001 ret=00750ec9

Here we are at the end.

0023:Call kernel32.GetModuleHandleA(00000000) ret=00750fdb
0023:Ret  kernel32.GetModuleHandleA() retval=00400000 ret=00750fdb
0023:Call kernel32.GetModuleHandleA(00000000) ret=00750ffe
0023:Ret  kernel32.GetModuleHandleA() retval=00400000 ret=00750ffe
0023:Call kernel32.GetModuleHandleA(00000000) ret=00751012
0023:Ret  kernel32.GetModuleHandleA() retval=00400000 ret=00751012

Interesting. GetModuleHandle(NULL) returns the HMODULE of the current process. An HMODULE is simply a pointer to the base of the file which is mapped in: in the case of an EXE it'll be the headers.


So, it looks like this program is walking its own headers in memory - probably inspecting them for signs of tampering. More and more likely that this is copy protection.

0023:Call kernel32.ReadProcessMemory(00000058,00761060,0078c96c,00000002,406cbc0c) ret=007556bd

It then reads the memory of the newly created process, 2 bytes from 0x761060. I wonder what is at that address?


Grep the log for it and bingo!

0025:Starting process L"C:\\Program Files\\Easy CD-DA Extractor 7\\ezcddax.exe" (entryproc=0x761060)

So it's reading the first two bytes of the entry point. Checking for a breakpoint perhaps?


0023:Call ntdll.NtReadVirtualMemory(00000058,00761060,0078c96c,00000002,406cbc0c) 
ret=404fab7a
0023:Ret  ntdll.NtReadVirtualMemory() retval=00000000 ret=404fab7a
0023:Ret  kernel32.ReadProcessMemory() retval=00000001 ret=007556bd
0023:Call kernel32.WriteProcessMemory(00000058,00761060,406cbc08,00000002,406cbc0c) 
ret=00755723

Then it writes back 2 bytes to the same address. Maybe this is the bit that lets ezcddax know it was started by the launcher program and not directly by the user. I suspect if you suppress this WPM call, the program will pop up an error asking you to run the launcher app directly. It's probably writing a jump opcode.


0023:Call ntdll.NtWriteVirtualMemory(00000058,00761060,406cbc08,00000002,406cbc0c) 
ret=404fabea
0023:Ret  ntdll.NtWriteVirtualMemory() retval=00000000 ret=404fabea
0023:Ret  kernel32.WriteProcessMemory() retval=00000001 ret=00755723
0023:Call kernel32.GetExitCodeProcess(00000058,0078ca58) ret=0074f235

Now it goes into a loop, attempting to get the exit code of the process.

0023:Call 
ntdll.NtQueryInformationProcess(00000058,00000000,406cb86c,00000018,00000000) 
ret=404f5dfa
0023:Ret  ntdll.NtQueryInformationProcess() retval=00000000 ret=404f5dfa
0023:Ret  kernel32.GetExitCodeProcess() retval=00000001 ret=0074f235

It's trying to find out what the exit code of the process was. Unfortunately, we don't know what the answer is because the return code is a success/failure bool, not the actual exit code. You'd have to whack an ERR in here or something to find out.


0023:Call kernel32.ResumeThread(0000005c) ret=007557c5
0023:Call ntdll.NtResumeThread(0000005c,406cb8a0) ret=4050e85e
0023:Ret  ntdll.NtResumeThread() retval=00000000 ret=4050e85e
0023:Ret  kernel32.ResumeThread() retval=00000001 ret=007557c5
0023:Call kernel32.Sleep(00000064) ret=007557cd

Then it tries to wake it up (remember, the remote process was started suspended) and sleeps for a moment.


0023:Call ntdll.NtDelayExecution(00000000,406cb888) ret=40507cff
trace:relay:RELAY_InitDebugLists RelayExclude = 
L"RtlEnterCriticalSection;RtlLeaveCriticalSection;_EnterSysLevel;_LeaveSysLevel;LOCAL_Lock;LOCAL_Unlock;TlsGetValue;kernel32.GetLastError;kernel32.SetLastError"
0025:Call PE DLL (proc=0x401d3bb4,module=0x401c0000 
L"ntdll.dll",reason=PROCESS_ATTACH,res=0x1)

At this point the kernel does a context switch into the new process, and it begins initializing. Note that CREATE_SUSPENDED doesn't mean nothing runs in the new process. It still gets the ATTACH notifications (at least, it does in Wine ... maybe not in real windows). So there's a lot of stuff we can ignore here generated by the startup sequence.


Let's find out what the first process is doing:

0025:Ret  ntdll.RtlAllocateHeap() retval=40393550 ret=4083acae
0023:Ret  ntdll.NtDelayExecution() retval=00000000 ret=40507cff
0023:Ret  kernel32.Sleep() retval=00000000 ret=007557cd
0023:Call kernel32.SuspendThread(0000005c) ret=007557da
0023:Call ntdll.NtSuspendThread(0000005c,406cb8a0) ret=4050e80e
0023:Ret  ntdll.NtSuspendThread() retval=00000000 ret=4050e80e
0023:Ret  kernel32.SuspendThread() retval=00000000 ret=007557da
0023:Call kernel32.GetThreadContext(0000005c,406cb948) ret=0075580e
0023:Call ntdll.NtGetContextThread(0000005c,406cb948) ret=4050e7ae
0023:Ret  ntdll.NtGetContextThread() retval=00000000 ret=4050e7ae
0023:Ret  kernel32.GetThreadContext() retval=00000001 ret=0075580e

Context switch after the first line, and it awakens from its sleep.

It then suspends the thread, and grabs its context. Why does it suspend? Reading MSDN reveals that the target thread has to be suspended for GetThreadContext to work. The CONTEXT structure holds the register state of the thread. I wonder what it's looking for in this structure?

0023:Call kernel32.GetExitCodeProcess(00000058,0078ca58) ret=0074f235
0023:Call 
ntdll.NtQueryInformationProcess(00000058,00000000,406cb86c,00000018,00000000) 
ret=404f5dfa
0023:Ret  ntdll.NtQueryInformationProcess() retval=00000000 ret=404f5dfa
0023:Ret  kernel32.GetExitCodeProcess() retval=00000001 ret=0074f235
0023:Call kernel32.ResumeThread(0000005c) ret=007557c5
0023:Call ntdll.NtResumeThread(0000005c,406cb8a0) ret=4050e85e
0023:Ret  ntdll.NtResumeThread() retval=00000000 ret=4050e85e
0023:Ret  kernel32.ResumeThread() retval=00000001 ret=007557c5
0023:Call kernel32.Sleep(00000064) ret=007557cd

OK, and we go back into a loop. In fact, this is an infinite loop.

Probably it looks like this:

while (1)
{
    int code;
    CONTEXT86 context;

    GetExitCodeProcess(process, &code);

    if (code == ???) do something;

    ResumeThread(thread);

    Sleep(64);

    SuspendThread(thread);
    GetThreadContext(thread, &context);

    // do something with context here
    if (context.???) break ???
}

So the question is, what condition will make it break out of the loop, and why isn't it getting it in Wine?

It looks like it's waiting for some condition to become true in the remote process. This will never happen because ResumeThread here doesn't seem to be waking it up! We just loop over and over, resuming it, sleeping for a while, grabbing its context to check something which never changes, and starting over.

So, I guess the problem is that ResumeThread isn't actually waking up the suspended process. Question is, why not?

Here's an idea. Hack the Sleep() call like this:

if (delay == 64) delay = 3000;

Ie, rule out the possibility that the delay between resume and suspend is so short Wine can't react in time. Then continue your investigation from there.

Good luck!

thanks -mike



Reply via email to