ez-cdda sleep
Mike Hearn
m.hearn at signal.QinetiQ.com
Wed Sep 29 10:51:23 CDT 2004
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
More information about the wine-devel
mailing list