Analysis of the WiBu Key Dongle by SnakeByte [ SnakeByte@kryptocrew.de ] http://www.kryptocrew.de/snakebyte/ The WiBu ( www.wibu.de ) Key is a hardware protection to prevent unauthorized people to copy and use your software, its goal is to prevent privacy. This paper is an analysis on how good this protection is implemented and how it might be possible to bypass it. The dongle uses the new AES algorithm and is therefore pretty secure. I tested it with the software kit 3.01 and a WinME system, this analysis was done very quickly ( took about 2 hours ) so I present here just the things I saw directly. The first thing I did, was to create a simple goat file in Win32ASM, and crypted it as described in the handbook. --------------------------------------------------------------------< .586p .model flat jumps extrn ExitProcess:PROC # these are the api's we use extrn MessageBoxA:PROC .data MSG db 'This is my baitfile',0 # a message to display .code start: push 0 # display the message push offset MSG push offset MSG push 0 call MessageBoxA push 0 # quit call ExitProcess end start --------------------------------------------------------------------< After encryption I run the file one time without the dongle and one time with the dongle. It worked as expected ( not at all without the dongle, just displaying a message box ). The next step was to do a disassembly using win32dasm, because the size of the file grew from 4 KB to 9 KB and I wanted to see what was changed. After a first glance to the disassembly, i quickly noticed the following : +++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++ //********************** Start of Code in Object .wkcrypt ************** Program Entry Point = 00405000 (goat.EXE File Offset:00000E00) :00401000 6A00 push 00000000 :00401002 6800204000 push 00402000 :00401007 6800204000 push 00402000 :0040100C 6A00 push 00000000 :0040100E E80D000000 call 00401020 :00401013 6A00 push 00000000 :00401015 E800000000 call 0040101A This was exactly my code, it wasnt crypted at all, how could this happen, did something went wrong ? I launched my debugger and set the initial EIP to 0040100, to make it execute my code. It didn't work, because the API Calls went to some strange data segments. I did the same with notepad, because I thought it might be possible, that this is not enough data, to be encrypted, but the code of notepad wasnt encrypted too. What the dongle system has done is the following : The original Import Table is removed, and new code gets inserted into the file. After some debugging I found out that the new code just checks the dongle, to decrypt the api names , still stored ( but encrypted ) in the file. Then retrieves the API offsets by using GetProcAddress and writes them to the new API Table. All what has to be done to make this file work without the dongle is to change the initial EIP, and to overwrite the WiBu Code with an own one wich retrieves the API's and writes the offsets, where the program expects them. This is pretty easy if we have the dongle and pretty hard if we don't. When talking about pirated software, there is always someone with an original copy, who places it on the net, or distributes it in other ways. This person is able to patch the file in less than an hour of work and make it run without the dongle. This way is described first. Ok, first of all, we need to find out which API calls the programm uses, to do this we insert the dongle and run our debugger. In Win32Dasm, we have the following lines : * Reference To: KERNEL32.GetProcAddress, Ord:013Eh | :00405145 FF1500604000 Call dword ptr [00406000] This is the call to GetProcessAddress, which retrieves the Api offsets, and fill the new API Table. We simply set a breakpoint to 00405145 and run it. Then we can easily read the values on the stack and get the one which points to the API Names. Notice them all, and you are ready to patch the code, by inserting your own API Search Routine, which fills the new API Table and then jumps to the original code. The libarie names can be retrieved by placing a breakpoint on offset 40509F in this example which is a call to LoadLibaryA : * Reference To: KERNEL32.LoadLibraryA, Ord:01C2h | :0040509F FF1508604000 Call dword ptr [00406008] To be sure to get the right ones, just place a breakpoint on all LoadLibaryA and GetProcAddress API's becaus both are used two times in the loading process. Now we need some code which we can use to overwrite the one by WiBu to load the old API's. Here is some kind of "generic" code to do this, it uses no API's at all. This code is used by a lot of viruses, so don't wonder if your Virus Scanner gives you a false alert ;) The following code was written by Lethalmind and distributed with the 29a Zine, its nicely commented : --------------------------------------------------------------------< call Delta Delta: pop ebp sub ebp, offset Delta ; make this code relative StartOfYourVirus: mov ecx,[esp] ; Return adress of call from ; CreateProcess GetKrnlBaseLoop: ; Get Kernel32 module base adress xor edx,edx ; dec ecx ; Scan backward mov dx,[ecx+03ch] ; Take beginning of PE header test dx,0f800h ; Is it a PE header ? jnz GetKrnlBaseLoop ; No, forget about it cmp ecx,[ecx+edx+34h] ; Compare current adress with the ; address that PE should be loaded at jnz GetKrnlBaseLoop ; Different ? Search again mov [KernelAdress+ebp],ecx ; ecx hold KernelBase... Store it ; Now we got the Kernel Address and can start to retrieve ; the API Offsets we need later mov eax,ecx ; EAX = KernelBase mov ebx,eax ; EBX = KernelBase add eax,[eax.MZ_lfanew] ; Get address of PE header mov esi,ebx ; Get address of Export ; directory add esi,[eax.NT_OptionalHeader.OH_DirectoryEntries.DE_Export.DD_VirtualAddress] mov edx,ebx ; Get address of exported add edx,[esi.ED_AddressOfNames] ; API names mov ecx,[esi.ED_NumberOfNames] ; Get number of exported xor eax,eax ; API names lea edi,WORD PTR [ebp+BeginAPIList] ; Point to beginning of list mov [SESI+ebp],esi ; Save some regs mov [SEAX+ebp],eax ; mov [SECX+ebp],ecx ; mov [SEBX+ebp],ebx ; mov [SEDX+ebp],edx ; Search_for_API_name: mov esi,ebx ; Get address of next exported add esi,[edx+eax*4] ; API name Next_API_name: pusha xor edx,edx mov edx,DWORD PTR[edi] ; Take the checksum add edi,4 ; Point To Next test edx,edx ; Is this checksum 0 ? jz EndAPIRetrieving ; Yeah, we're done with them LoopChsksm: xor eax,eax lodsb ; Take a char (ie : "X") shl ax,8 ; move it left (ie : "X_") sub edx,eax ; Substract that from checksum cmp ax,0 ; Is the char 0 ? jz LoopConti ; Yes, check if checksum too xor ax,ax ; lodsb ; Load another char (ie : "Y") sub edx,eax ; Substract that from checksum cmp ax,0 ; Is the char 0 ? jnz LoopChsksm ; No, Continue looping LoopConti: test edx,edx ; Have we zeroed our checksum ? jz FoundAPI ; YES, we have found the right API popa ; inc eax ; Nope, next name.... loop Search_for_API_name FoundAPI: popa mov esi,[SESI+ebp] mov edx,ebx ; Get address of exp. API ordinal add edx,[esi.ED_AddressOfOrdinals] movzx eax,word ptr [edx+eax*2] ; Get index into exp.API functions Check_Index: mov edx,ebx ; Get address of exported API function add edx,[esi.ED_AddressOfFunctions] add ebx,[edx+eax*4] ; Get address of requested API function mov eax,ebx ; End_GetProcAddressET: add edi,4 stosd ; Save API's adress into our array mov eax,[SEAX+ebp] ; Restore some regs mov ebx,[SEBX+ebp] ; mov ecx,[SECX+ebp] ; mov edx,[SEDX+ebp] ; jmp Search_for_API_name ; Next Name SESI dd 0 SEAX dd 0 SECX dd 0 SEBX dd 0 SEDX dd 0 ; here are the API's we want to retrieve BeginAPIList: sCloseHandle dd 'Cl'+'os'+'eH'+'an'+'dl'+'e'*100h aCloseHandle dd 0 sCreateFileA dd 'Cr'+'ea'+'te'+'Fi'+'le'+'A'*100h aCreateFileA dd 0 sCreateFileMappingA dd 'Cr'+'ea'+'te'+'Fi'+'le'+'Ma'+'pp'+'in'+'gA' aCreateFileMappingA dd 0 dd 0 KernelAdress dd ? --------------------------------------------------------------------< Another way to do this might be to let the dongle decode the api names and patch the original code. Overwrite the crypted API names with the original ones and patch the dongle routines with nops, and let the rest of the code retrieve the API Offsets. How can we do this, if we don't have the dongle ? We also need the original API's ? There are just two things which come into my mind, the first is to start a brute force attack on the API Names, which wouldn't be that a good idea, even if we know some plain text ( things like ExitProcess and similar API's are used by every application ) this will take too long to be successful. The other way would be code analysation, just disassemble the code and search for known things, take a look at the arguments which gets pushed and similar things. Which API call would make sense on a certain place. This could be done with small applications and bigger ones, if you got enough spare time, so you can also crack the application without having a dongle or even the original copy.