One quick look at silkroad files and I immediately came to a conclusion that the files were packed. Gameguard files are named '.des' rather than '.exe' or '.sys,' indicating foul play. Also, opening sro_client.exe in a disassembler yeilds a very noticeable packed output. The first step into understanding and breaking Gameguard is to unpack all sensitive code. We all know that packed code == useless code. Packing is the quick-and-dirty and often times sloppy way of protecting code. There is no need to pack code using commercial packers if it is secure and compiled with the correct directives ( they used MSVC 6.0/7.0 so I know for a fact that optimizing the compiler options would yeild file sizes competing with packed ones ). I quickly opened up my copy of PEiD (
) and ran a scan on the directory. Note that you should have your scan mode set to 'Hardcore' or it pretty much picks up nothing unless it has not been modified at all. Here's what I found:
If you will read down a few posts you can see where I explain about these UPX packed files. The following is the reversal of the auto-unpacking code. Note this is preliminary reversing as it's not in any language with which you can compile. I will eventually post a compileable C++ source-code using this simple reversal that will unpack any files packed with this UPX variant.
Code:
.rdata:00426C30 xor edi, edi
//EDI = 0
.rdata:00426C32 jz short loc_426C3A
//jump condition always true
//UNREACHABLE CODE BEGIN ====
.rdata:00426C34 popa
.rdata:00426C35 jmp near ptr 3092B984h
//==== UNREACHABLE CODE END
.rdata:00426C3A loc_426C3A: ; CODE XREF: .rdata:00426C32j
.rdata:00426C3A pop edx
//POP
.rdata:00426C3B mov edx, 7Dh
//edx = 7Dh
.rdata:00426C40 cmp byte ptr [esp+8], 1
//unused comparison statement, result never checked
.rdata:00426C45 jmp $+5
//standard skip jump, jump statement jumps to next instruction
.rdata:00426C4A pusha
//PUSHA - PUSHA LEVEL 1
.rdata:00426C4B mov esi, offset dword_419000
//ESI = base of DWORD data
.rdata:00426C50 xor edi, edi
//EDI = 0
.rdata:00426C52 jz short loc_426C5A
//jump condition always true
//UNREACHABLE CODE BEGIN ====
.rdata:00426C54 popa
.rdata:00426C55 jmp near ptr 3092B9A4h
//==== UNREACHABLE CODE END
.rdata:00426C5A loc_426C5A: ; CODE XREF: .rdata:00426C52j
.rdata:00426C5A lea edi, [esi-18000h]
//EDI = base of .text section
.rdata:00426C60 xor ecx, ecx
//ECX = 0
.rdata:00426C62 jz short loc_426C6A
//condition of jump always true
//UNREACHABLE CODE BEGIN ====
.rdata:00426C64 popa
.rdata:00426C65 jmp near ptr 3092B9B4h
//==== UNREACHABLE CODE END
.rdata:00426C6A loc_426C6A: ; CODE XREF: .rdata:00426C62j
.rdata:00426C6A mov eax, 7Dh
//EAX = 7Dh
.rdata:00426C6F cmp edx, eax
//EDX ( 7Dh ) == EAX ( 7Dh ), result is 0 ( zero flag set )
.rdata:00426C71 mov eax, 4Ch
//EAX = 4Ch
.rdata:00426C76 not eax
//EAX = 0FFFFFFB3h
.rdata:00426C78 jnz short loc_426CB9
//jump condition always false
.rdata:00426C7A mov eax, large fs:30h
//EAX = _PEB
.rdata:00426C80 test eax, eax
.rdata:00426C82 js short loc_426CA7
//condition true if OS is 9x, false if NT
.rdata:00426C84 mov eax, [eax+0Ch]
//EAX = _PEB->Ldr <<MODULE LIST HEAD>>
.rdata:00426C87 mov eax, [eax+0Ch]
//EAX = _PEB->Ldr->InLoadOrderModuleList->Flink
.rdata:00426C8A mov dword ptr [eax+20h], 1000h
//(*_LDR_MODULE)( _PEB->Ldr->InLoadOrderModuleList->Flink )->SizeOfImage = 1000h
//THE FOLLOWING APPEARS TO BE DEBUGGER DETECTION CODE
.rdata:00426C91 mov eax, large fs:18h
//EAX = _TEB
.rdata:00426C97 mov eax, [eax+30h]
//EAX = _TEB->ProcessEnvironmentBlock
.rdata:00426C9A movzx eax, byte ptr [eax+2]
//EAX = _TEB->ProcessEnvironmentBlock->BeingDebugged ( TRUE IF DEBUGGER IS ACTIVE, NT VERSION ONLY )
.rdata:00426C9E test eax, eax
.rdata:00426CA0 jnz short loc_426CB8
//condition is true if a debugger is active ( if debugger flag is set in TEB/PEB )
.rdata:00426CA2 jmp loc_426CB9
//called if debugger is not active ( if debugger is not set in TEB/PEB )
.rdata:00426CA7 loc_426CA7: ; CODE XREF: .rdata:00426C82j
.rdata:00426CA7 xor eax, eax
//EAX = 0
.rdata:00426CA9 mov al, large fs:20h
//EAX = TIB->DebugContext
.rdata:00426CAF test eax, eax
.rdata:00426CB1 jnz short loc_426CB8
//condition is true if a debugger is active ( if debug context is true, 9X check! )
.rdata:00426CB3 jmp loc_426CB9
//called if debugger is not active ( if debug context is false )
.rdata:00426CB8 loc_426CB8: ; CODE XREF: .rdata:00426CA0j
.rdata:00426CB8 ; .rdata:00426CB1j
.rdata:00426CB8 popa
//POPA - PUSHA LEVEL 0 ( clear ) ( ONLY EXECUTED IF A DEBUGGER IS FOUND )
.rdata:00426CB9 loc_426CB9: ; CODE XREF: .rdata:00426C78j
.rdata:00426CB9 ; .rdata:00426CA2j ...
.rdata:00426CB9 push edi
//push the .text section base on the stack
.rdata:00426CBA or ebp, 0FFFFFFFFh
//EBP == 0FFFFFFFFh
.rdata:00426CBD jmp short loc_426CCA
.rdata:00426CBD; ---------------------------------------------------------------------------
.rdata:00426CBF align 4
//jmp + NOP x4
.rdata:00426CC0 loc_426CC0: ; CODE XREF: .rdata:00426CD1j
.rdata:00426CC0 mov al, [esi]
//AL = first byte of the second dword
.rdata:00426CC2 inc esi
//ESI points to next byte
.rdata:00426CC3 mov [edi], al
//first byte in the text section now equals the first byte in the second dword
.rdata:00426CC5 inc edi
//edi points to the second byte in the text section
.rdata:00426CC6 loc_426CC6: ; CODE XREF: .rdata:00426D5Ej
.rdata:00426CC6 ; .rdata:00426D75j
.rdata:00426CC6 add ebx, ebx
//0 extension left shift in ebx when the zero flag is set, the carry flag will be cleared here when ebx is less than 0EFFFFFFFh
.rdata:00426CC8 jnz short loc_426CD1
//condition is true if ebx is not 0
.rdata:00426CCA loc_426CCA: ; CODE XREF: .rdata:00426CBDj
.rdata:00426CCA mov ebx, [esi]
//EBX = first dword in DWORD data
.rdata:00426CCC sub esi, 0FFFFFFFCh
//ESI is now 4 bytes bigger and carry flag has been set
.rdata:00426CCF adc ebx, ebx
//1 extension left shift in ebx
.rdata:00426CD1 loc_426CD1: ; CODE XREF: .rdata:00426CC8j
.rdata:00426CD1 jb short loc_426CC0
//condition true until carry flag cleared
.rdata:00426CD3 mov eax, 1
//EAX = 1
.rdata:00426CD8 loc_426CD8: ; CODE XREF: .rdata:00426CE7j
.rdata:00426CD8 ; .rdata:00426CF2j
.rdata:00426CD8 add ebx, ebx
//0 extension left shift in ebx
.rdata:00426CDA jnz short loc_426CE3
//jumps if ebx is 0
.rdata:00426CDC mov ebx, [esi]
//if ebx is greater than 0 ( but carry flag was cleared previously ), ebx becomes the next dword in the table pointed to by esi
.rdata:00426CDE sub esi, 0FFFFFFFCh
//carry flag set, esi incremented 4 bytes
.rdata:00426CE1 adc ebx, ebx
//1 extension left shift in ebx
.rdata:00426CE3 loc_426CE3: ; CODE XREF: .rdata:00426CDAj
.rdata:00426CE3 adc eax, eax
//1 extension left shift in eax ( 1 becomes 3 )
.rdata:00426CE5 add ebx, ebx
//0 extension left shift in ebx
.rdata:00426CE7 jnb short loc_426CD8
//jump if the carry flag is not set
.rdata:00426CE9 jnz short loc_426CF4
//jump if the carry flag is set and ebx is not zero ( ebx == 0 - 0EFFFFFFFh )
//if the carry flag is set and ebx is zero, the following code will execute
.rdata:00426CEB mov ebx, [esi]
//ebx becomes the next dword
.rdata:00426CED sub esi, 0FFFFFFFCh
//esi is incremented and carry flag is set
.rdata:00426CF0 adc ebx, ebx
//1 extension left shift in ebx
.rdata:00426CF2 jnb short loc_426CD8
//jump if the carry flag is not set
.rdata:00426CF4 loc_426CF4: ; CODE XREF: .rdata:00426CE9j
.rdata:00426CF4 xor ecx, ecx
//ECX = 0
.rdata:00426CF6 sub eax, 3
.rdata:00426CF9 jb short loc_426D08
//jump if carry flag is set
.rdata:00426CFB shl eax, 8
//0 extension left shift in eax 8 bits
.rdata:00426CFE mov al, [esi]
//the cleared bits in al become the byte pointed to by esi in the data table
.rdata:00426D00 inc esi
//esi is incremented to the next byte in the table
.rdata:00426D01 xor eax, 0FFFFFFFFh
.rdata:00426D04 jz short loc_426D7A
//jump if eax is zero
.rdata:00426D06 mov ebp, eax
//EBP = EAX
.rdata:00426D08 loc_426D08: ; CODE XREF: .rdata:00426CF9j
.rdata:00426D08 add ebx, ebx
//0 extension left shift in ebx
.rdata:00426D0A jnz short loc_426D13
//jump if ebx is 0
.rdata:00426D0C mov ebx, [esi]
//ebx becomes the next dword
.rdata:00426D0E sub esi, 0FFFFFFFCh
//data pointer incremented by 4 and carry flag set
.rdata:00426D11 adc ebx, ebx
//1 extension left shift in ebx
.rdata:00426D13 loc_426D13: ; CODE XREF: .rdata:00426D0Aj
.rdata:00426D13 adc ecx, ecx
//if carry flag is set, 1 extension left shift in ecx, otherwise 0 extension left shift
.rdata:00426D15 add ebx, ebx
//0 extension left shift in ebx
.rdata:00426D17 jnz short loc_426D20
//jump if ebx is not 0
.rdata:00426D19 mov ebx, [esi]
//ebx becomes the next dword
.rdata:00426D1B sub esi, 0FFFFFFFCh
//carry flag set, esi incremented 4 bytes
.rdata:00426D1E adc ebx, ebx
//1 extension left shift in ebx
.rdata:00426D20 loc_426D20: ; CODE XREF: .rdata:00426D17j
.rdata:00426D20 adc ecx, ecx
//if carry flag is set, 1 extension left shift in ecx, otherwise 0 extension left shift
.rdata:00426D22 jnz short loc_426D44
//jump if ecx is not zero
.rdata:00426D24 inc ecx
//ECX += 1
.rdata:00426D25 loc_426D25: ; CODE XREF: .rdata:00426D34j
.rdata:00426D25 ; .rdata:00426D3Fj
.rdata:00426D25 add ebx, ebx
//0 extension left shift in ebx
.rdata:00426D27 jnz short loc_426D30
//jump if ebx is not zero
.rdata:00426D29 mov ebx, [esi]
//ebx becomes the next dword
.rdata:00426D2B sub esi, 0FFFFFFFCh
//carry flag set, esi incremented 4 bytes
.rdata:00426D2E adc ebx, ebx
//1 extension left shift in ebx
.rdata:00426D30 loc_426D30: ; CODE XREF: .rdata:00426D27j
.rdata:00426D30 adc ecx, ecx
//if carry flag is set, 1 extension left shift in ecx, otherwise 0 extension left shift
.rdata:00426D32 add ebx, ebx
//0 extension left shift in ebx
.rdata:00426D34 jnb short loc_426D25
//jump if carry flag is not set
.rdata:00426D36 jnz short loc_426D41
//jump if carry flag is set and ebx is not 0
//the following code will be executed if the carry flag is set and ebx is 0
.rdata:00426D38 mov ebx, [esi]
//ebx becomes the next dword
.rdata:00426D3A sub esi, 0FFFFFFFCh
//carry flag is set and esi is incremented 4 bytes
.rdata:00426D3D adc ebx, ebx
//1 extension left shift in ebx
.rdata:00426D3F jnb short loc_426D25
//jump is carry flag is cleared
.rdata:00426D41 loc_426D41: ; CODE XREF: .rdata:00426D36j
.rdata:00426D41 add ecx, 2
.rdata:00426D44 loc_426D44: ; CODE XREF: .rdata:00426D22j
.rdata:00426D44 cmp ebp, 0FFFFF300h
//ebp - 0FFFFF300h implied
.rdata:00426D4A adc ecx, 1
//if ebp is less than the compared value, ecx is incremented by 2, otherwise just 1
.rdata:00426D4D lea edx, [edi+ebp]
//edx becomes the the value stored in memory at edi+ebp
.rdata:00426D50 cmp ebp, 0FFFFFFFCh
//ebp - 0FFFFFFFCh implied
.rdata:00426D53 jbe short loc_426D64
//jump if the carry flag or zero flag is set ( if ebp is less than or equal to the operand )
.rdata:00426D55 loc_426D55: ; CODE XREF: .rdata:00426D5Cj
.rdata:00426D55 mov al, [edx]
//al becomes the byte pointed to by edx
.rdata:00426D57 inc edx
//edx points to the next byte
.rdata:00426D58 mov [edi], al
//value at edi becomes the value moved into al
.rdata:00426D5A inc edi
//edi points to next value
.rdata:00426D5B dec ecx
//ecx is decremented
.rdata:00426D5C jnz short loc_426D55
//jump if ecx is not zero
.rdata:00426D5E jmp loc_426CC6
//otherwise jump here
.rdata:00426D5E; ---------------------------------------------------------------------------
.rdata:00426D63 align 4
//NOP x4
.rdata:00426D64 loc_426D64: ; CODE XREF: .rdata:00426D53j
.rdata:00426D64 ; .rdata:00426D71j
.rdata:00426D64 mov eax, [edx]
//eax becomes the dword pointed to by edx
.rdata:00426D66 add edx, 4
//edx is incremented to the next dword
.rdata:00426D69 mov [edi], eax
//the value pointed to by edi is set to the value moved into eax
.rdata:00426D6B add edi, 4
//edi is incremented to the next dest. dword
.rdata:00426D6E sub ecx, 4
//ecx is decremented by 4
.rdata:00426D71 ja short loc_426D64
//jump if carry flag and zero flag are set
.rdata:00426D73 add edi, ecx
//edi incremented by ecx
.rdata:00426D75 jmp loc_426CC6
.rdata:00426D7A; ---------------------------------------------------------------------------
.rdata:00426D7A loc_426D7A: ; CODE XREF: .rdata:00426D04j
.rdata:00426D7A pop esi
//POP
.rdata:00426D7B mov edi, esi
//edi becomes the popped value
.rdata:00426D7D mov ecx, 636h
//ecx becomes 636
.rdata:00426D82 loc_426D82: ; CODE XREF: .rdata:00426D89j
.rdata:00426D82 ; .rdata:00426D8Ej
.rdata:00426D82 mov al, [edi]
//al becomes the byte pointed to by edi
.rdata:00426D84 inc edi
//edi points to the next byte
.rdata:00426D85 sub al, 0E8h
//al is decremented by 0E8h
.rdata:00426D87 loc_426D87: ; CODE XREF: .rdata:00426DACj
.rdata:00426D87 cmp al, 1
.rdata:00426D89 ja short loc_426D82
//jump if al is greater than 1
//the following code is executed if al is 0 or 1
.rdata:00426D8B cmp byte ptr [edi], 13h
.rdata:00426D8E jnz short loc_426D82
//jump if the byte pointed to by edi is not equal to 13h
.rdata:00426D90 mov eax, [edi]
//eax becomes the dword pointed to by edi
.rdata:00426D92 mov bl, [edi+4]
//bl becomes the first byte in the next dword
.rdata:00426D95 shr ax, 8
//0 extension right shift in ax 8 bytes
.rdata:00426D99 rol eax, 10h
//left rotation in eax 16 bits
.rdata:00426D9C xchg al, ah
//al and ah are exchanged
.rdata:00426D9E sub eax, edi
//eax decremented by edi
.rdata:00426DA0 sub bl, 0E8h
//bl decremented by 0E8h
.rdata:00426DA3 add eax, esi
//eax incremented by esi
.rdata:00426DA5 mov [edi], eax
//value pointed to by edi becomes eax
.rdata:00426DA7 add edi, 5
//edi incremented by 5
.rdata:00426DAA mov eax, ebx
//eax becomes ebx
.rdata:00426DAC loop loc_426D87
//loop the last code ecx times
.rdata:00426DAE lea edi, [esi+24000h]
//edi becomes the value pointed to by esi+24000h
.rdata:00426DB4 loc_426DB4: ; CODE XREF: .rdata:00426DD6j
.rdata:00426DB4 mov eax, [edi]
//eax becomes the value pointed to by edi
.rdata:00426DB6 or eax, eax
.rdata:00426DB8 jz short loc_426DFF
//jump if eax is zero
.rdata:00426DBA mov ebx, [edi+4]
//ebx becomes dword at edi+4
.rdata:00426DBD lea eax, [eax+esi+26B90h]
//eax becomes the value pointed to by eax+esi+26B90h
.rdata:00426DC4 add ebx, esi
//ebx incremented by esi
.rdata:00426DC6 push eax
//eax pushed onto stack
.rdata:00426DC7 add edi, 8
//edi incremented by 8
.rdata:00426DCA call dword ptr [esi+26C08h]
//code at esi+26C08 called
.rdata:00426DD0 xchg eax, ebp
//eax becomes ebp, ebp becomes eax
.rdata:00426DD1 loc_426DD1: ; CODE XREF: .rdata:00426DF7j
.rdata:00426DD1 mov al, [edi]
//al becomes the byte pointed to by edi
.rdata:00426DD3 inc edi
//edi points to the next byte
.rdata:00426DD4 or al, al
.rdata:00426DD6 jz short loc_426DB4
//jump if the byte is 0
.rdata:00426DD8 mov ecx, edi
//ecx becomes edi
.rdata:00426DDA jns short near ptr loc_426DE2+1
//jump if the sign flag is cleared
.rdata:00426DDC movzx eax, word ptr [edi]
//eax becomes the word pointed to by edi
.rdata:00426DDF inc edi
//edi points to the next byte
.rdata:00426DE0 push eax
//eax is pushed
.rdata:00426DE1 inc edi
//edi points to the next byte
.rdata:00426DE2 loc_426DE2: ; CODE XREF: .rdata:00426DDAj
.rdata:00426DE2 mov ecx, 0AEF24857h
//ecx becomes this weird-ass number
.rdata:00426DE7 push ebp
//push the base pointer onto the stack
.rdata:00426DE8 call dword ptr [esi+26C0Ch]
//call this code
.rdata:00426DEE or eax, eax
.rdata:00426DF0 jz short loc_426DF9
//jump if eax is zero
.rdata:00426DF2 mov [ebx], eax
//the value pointed to by ebx becomes eax if it's not zero
.rdata:00426DF4 add ebx, 4
//ebx incremented by 4 bytes
.rdata:00426DF7 jmp short loc_426DD1
.rdata:00426DF9 loc_426DF9: ; CODE XREF: .rdata:00426DF0j
.rdata:00426DF9 call dword ptr [esi+26C10h]
//call this code..
.rdata:00426DFF loc_426DFF: ; CODE XREF: .rdata:00426DB8j
.rdata:00426DFF popa
.rdata:00426E00 jmp near ptr dword_40BCB7
//popa and jump to the original entrypoint
Yeah.. that's some fun stuff. I'll be working on the unpacker.
Also, the win32 executables are packed with the same exact variant and the device drivers ( npgg9x.des and npggNT.des ) are packed with a very slightly different variant ( the last 15-20 lines of code are different ), but we don't need to unpack the drivers as they will never be loaded in the final patched version =).