ez-cdda sleep

James Hawkins truiken at gmail.com
Wed Sep 29 11:47:32 CDT 2004


On Wed, 29 Sep 2004 12:38:32 -0400, James Hawkins <truiken at gmail.com> wrote:
> > Let's engage on a little log analysis shall we? This is something that
> > just comes with practice ...
> 
> Wow...very impressive.
> 
> > Here's an idea. Hack the Sleep() call like this:
> >
> > if (delay == 64) delay = 3000;
> 
> ok I will try this out and get back to you on what happens.
> 
> 
> 
> 
> On Wed, 29 Sep 2004 16:51:23 +0100, Mike Hearn
> <m.hearn at signal.qinetiq.com> wrote:
> > 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
> >
> 
> 
> --
> James Hawkins
> 

> > 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.

I modified Sleep to do an ERR("USING MODIFIED SLEEP!\n") and then
added the check for timeout==64, set it to 3000, but it still calls
sleep over and over again without any progress.  I even took out the
check for timeout==64 and just timeout to 3000 for the second test,
but I get the same results.

-- 
James Hawkins



More information about the wine-devel mailing list