Deposit 1.2.0 (Using RevEng) 11/2001 by Zadig. Tools needed: RevEng (at least v0.9) vim a debugger: db Target is available at BeBits Introduction: The aim of this tutorial is to show you how to use reveng (at least how I think it should be used), because I didn't write any user doc for it. Since it is easier to learn with an example, we will study Deposit. Deposit is "a pretty desktop launcher with a smart graphic interface" (from the author). Since this type of soft is not really usefull on BeOS (in my opinion) and the last release is dated 1999, I think this tut won't harm the author. So, today I will show you why vim is the best way to reverse on BeOS. In all this text I assume that you already know the basics of vim, and that you have an AZERTY keyboard. Moreover, shortcuts wil be printed this way: CTRL-key. For BeOS users (is there anyone else reading this?) you will have to press ALT instead of CTRL because of a bug in vim (The doc says that they can't monitor the ctrl key). I- RevEng configuration 1- Command line options. 2- Using an alias. 3- The config file. II- Deposit 1- First approach. 2- Serial fishing. 3- Keygen. III- Conclusion I- RevEng configuration: I.1- Command line options. RevEng supports only 3 options: split : This was the first option available on reveng, and is one of the most insteresting. It will split the disassembly in 2 files: The first file will contain the disassembly of your target and ONLY this. The second one will contain all other informations: strings, objects, classes, and functions references. The second file has the same name as the first one but with the ".sym" extention. These 2 files allow you to navigate easier when you're looking for interesting functions/strings because the symbols file is much smaller ctags : This is a new option (in v0.9.0). We will have a deeper look at it later. For those that don't know what are tags, let's just say that it is very usefull to go to a function starting code. plt : This will print the ".plt" section in the disassembly. It is useless but present just in case someone need it (to redirect dynamic calls for example). I.2- Using an alias. Know it is evident that reveng should always be launched with options. Personnaly, I always use the "split" and (recently;-) "ctags" options. To make it automatic, just add this to your ".profile": alias reveng="reveng -split -ctags" For those that don't know what is this file, have a look in a unix book, or in the BeOS bible. I.3- The config file. Finally, reveng uses an optional config file. You can run reveng without it but you'll get a warning and your dead listing will not be as complete as it may be. This file contains 2 sections: Messages : This section is specific to BeOS. It contains most of BeOS messages values and their meaning. You can add other messages here (new ones or target specific ones). Parameters : This section contains the size of most known structs/classes used exept char, int, and long. The config file available with reveng bundle is for BeOS. If you are running another os, you may write your own file, with classes/structs specific to your os (The messages part won't be used). I- Deposit: II.1- First approach. Let's start easily with basic things. Launch deposit and look a it. In the about box you can enter your name, company, and serial number. So lets try: name : Zadig company : La destinee serial : 6655 And now validate: Nothing but a beep. Now disasemble it and open the disasm and symbols files in vim. Searching for register gives interesting results: - "register_firm" - "register_name" - "register_key" - "This version is registered for :" - TheApplication::ReadRegisterName(void) - TheApplication::Register(char const *) - TheApplication::WriteRegisterName(void) Now go on Register (on the line TheApplication::Register...) and hit CTRL-$. Whaooo! you jumped directly on the begining of this function. This is what tags allow to do. With this you can easily trace into functions calls in the dead-listing. You can also go back to the calling place by hiting CTRL-T. That's cool no? You can see that our function is called in only one place (offset 0x24019). You can jump directly to that calling place by hitting SHIFT-$ on the offset. This is not a tag feature. It just takes the whole word that is under the cursor and search for another occurence of it: Just what we need. To go back to our function you can hit n.The code here is: ... Reference to function "BTextControl::Text(void) const" 00024010: call 15a18 ; Get serial text 00024015: push %eax 00024016: mov (%edi),%eax 00024018: push %eax Reference to function "TheApplication::Register(char const *)" 00024019: call 18654 0002401e: add $0xc,%esp 00024021: test %eax,%eax ; Serial correct ? 00024023: je 24051 ; Jump good boy Reference to function "beep(void)" ; Bad boy ... That's a good starting point. There is something interesting to note: The Register method only uses the serial number. This means that there is no link between the serial number, the name, and the company name. Looking into Register (CTRL-$), you can see that it calls AlgoCode, so let's have a look at it. It is referenced in 2 places: - TheApplication::ReadRegisterName - TheApplication::Register (What a surprise ;-) That's perfect, It is the function we will have to patch because it is used to register deposit and check if it is registered. For that task we do not need to study it. A quick look is enough: ... Some code ... Reference to function "strchr" 000186d8: call 16f58 ; look for a char 000186dd: mov %eax,%edi 000186df: add $0x14,%esp 000186e2: test %edi,%edi ; char not found ? 000186e4: je 18711 ; jump bad boy ... Some code ... Referenced by (conditionnal) jump(s) at Address(es): 000186E4 00018711: xor %eax,%eax ; bad boy : eax = 0 00018713: jmp 18717 Referenced by (conditionnal) jump(s) at Address(es): 0001870F 00018715: mov $0x1,%al ; good boy : eax = 1 Referenced by (conditionnal) jump(s) at Address(es): 00018713 00018717: lea 0xfffffee4(%ebp),%esp 0001871d: pop %ebx 0001871e: pop %esi 0001871f: pop %edi 00018720: mov %ebp,%esp 00018722: pop %ebp 00018723: ret Finished! you can do several things here: - at 186e4 force a jump to 18715 - at the begining of the function, put 1 in eax and return - ... II.2- Serial fishing. This part will be a little bit more interesting but is also easy. Now we need to know what is doing AlgoCode. Here it is with comments: Function AlgoCode(char const *) Referenced at Address(es): 000182EB 0001866D 000186a8: push %ebp 000186a9: mov %esp,%ebp 000186ab: sub $0x110,%esp 000186b1: push %edi 000186b2: push %esi 000186b3: push %ebx 000186b4: call 186b9 000186b9: pop %ebx 000186ba: add $0x39c73,%ebx 000186c0: mov 0x8(%ebp),%eax ; param 1 (char const *) 000186c3: push $0x100 000186c8: push %eax ; param 1 (char const *) 000186c9: lea 0xffffff00(%ebp),%esi 000186cf: push %esi Reference to function "strncpy" ; strncpy(esi, param1, 128) 000186d0: call 163a8 000186d5: push $0x23 ; push '#' 000186d7: push %esi Reference to function "strchr" ; search for # in serial 000186d8: call 16f58 000186dd: mov %eax,%edi 000186df: add $0x14,%esp 000186e2: test %edi,%edi ; # not found ? 000186e4: je 18711 ; then end 000186e6: movb $0x0,(%edi) ; replace # with 0 000186e9: push %esi Reference to function "Code(char const *)" ; generates a code 000186ea: call 18724 000186ef: push %eax 000186f0: lea 0xffff090d(%ebx),%eax Possible reference to string "%08lX" 000186f6: push %eax 000186f7: lea 0xfffffef0(%ebp),%esi 000186fd: push %esi Reference to function "sprintf" ; converts Code ret to string 000186fe: call 16918 00018703: lea 0x1(%edi),%eax ; eax = # offset + 1 00018706: push %eax 00018707: push %esi Reference to function "strcmp" ; code ret == string at #+1 ? 00018708: call 16878 0001870d: test %eax,%eax 0001870f: je 18715 ; Then serial is good Referenced by (conditionnal) jump(s) at Address(es): 000186E4 00018711: xor %eax,%eax ; ret value = 0 (bad) 00018713: jmp 18717 Referenced by (conditionnal) jump(s) at Address(es): 0001870F 00018715: mov $0x1,%al ; ret value = 1 (good) Referenced by (conditionnal) jump(s) at Address(es): 00018713 00018717: lea 0xfffffee4(%ebp),%esp 0001871d: pop %ebx 0001871e: pop %esi 0001871f: pop %edi 00018720: mov %ebp,%esp 00018722: pop %ebp 00018723: ret Pretty simple no? Okey, maybe not at first. So here is what we know: The serial's max lenght is 128 chars. There must be a '#' in the serial. The "Code" function returns a code that is compared with what is after the '#' in the serial. This means that the end of the serial is an hex number. The end of the serial is an 8 digits hex number, forwarded with 0's if it is too small ("%08lX"). All this means that the serial is something like xxxx#YYYYYYYY. In fact, the xxxx number is used by "Code" to generate the yyyyyyyy one. All we have to do is put a breakpoint on strcmp and look at esi: It contains the yyyyyyyy number corresponding to the xxxx one. Note 1: xxxx can be any string of any lenght (but < 128 - 1 - 8). You don't have to put a number in it. Note 2: There is a funny bug in the serial check. Try to begin your serial with '#' (nothing before it), and see how simple is the corresponding serial! II.3- Keygen. Final step and the more interesting: The keygen. Since we know the structure of the serial number, it will more be easy. We know that the Code function returns what we want. This means that we can do several things: - Copy/paste the asm code of "Code" in our keygen: Simple but boring. - Understand what is doing "Code" and translate it to C: I prefer this! Let's see step by step this function: Function Code(char const *) Referenced at Address(es): 000186EA 00018724: push %ebp 00018725: mov %esp,%ebp 00018727: push %edi 00018728: push %esi 00018729: mov 0x8(%ebp),%edi ; param 1 (char const *) 0001872c: xor %ecx,%ecx ; ecx = 0 0001872e: mov %edi,%eax ; param 1 (char const *) 00018730: mov %edi,%edx ; param 1 (char const *) 00018732: xor %esi,%esi ; esi = 0 00018734: and $0x3,%edx ; serial buffer is 4 bytes aligned ? 00018737: je 1874f ; then jump 00018739: jp 1874a ... Other alignment tests ... This first part tests the alignment of our buffer. This is strange because it will always be 4 bytes aligned. Maybe this is for a ppc version? Anyway we will always jump to 1874f. From now, I will consider eax as "string1": 0001874f: mov (%eax),%edx ; Get 4 chars from serial 00018751: test %dh,%dl ; if char1 & char0 != 0 00018753: jne 1875d ; then jump 00018755: test %dl,%dl ; if char0 == o 00018757: je 18775 ; then jump 00018759: test %dh,%dh ; if char1 == 0 0001875b: je 18774 ; then jump Referenced by (conditionnal) jump(s) at Address(es): 00018753 0001875d: test $0xff0000,%edx ; if char2 == 0 00018763: je 18773 ; then jump 00018765: add $0x4,%eax ; string1 += 4 (get next 4 chars) 00018768: test $0xff000000,%edx ; if char3 != 0 0001876e: jne 1874f ; then search again for a 0 00018770: sub $0x3,%eax ; string1 -= 3 Referenced by (conditionnal) jump(s) at Address(es): 00018763 00018773: inc %eax ; string1++ Referenced by (conditionnal) jump(s) at Address(es): 0001875B 00018774: inc %eax ; string1++ Referenced by (conditionnal) jump(s) at Address(es): 00018742 00018747 0001874C 00018757 00018775: cmp %edi,%eax ; if string1 == param1 00018777: je 187d9 ; then end 00018779: lea 0x0(%esi,1),%esi ; esi = 0 This part searches for a 0 in the serial, that is to say where was the # (remember that it was negated in AlgoCode). At 18775 string1 (eax) points on the null char. Then esi is reset: This register will be used to calculate the second part of the serial. Let's call it result. Then we have: 00018780: movsbl (%ecx,%edi,1),%eax ; cur_char = param1 + nb_loop 00018784: lea 0xc007(%eax,%esi,1),%esi ; result += (*cur_char + 0xc007) 0001878b: mov %edi,%eax 0001878d: mov %edi,%edx 0001878f: inc %ecx ; nb_loop++ 00018790: and $0x3,%edx ; test for alignment again... 00018793: je 187ab This is the first part for the serial generation. There not mush to say here (the comments should be clear enough). After that you will find the same code than at the begining: alignment check, and # location search. The second part of the calculation is at 187d1. Here eax is a pointer to the #: 000187d1: add %esi,%esi ; result *= 2 000187d3: sub %edi,%eax ; get # offset 000187d5: cmp %eax,%ecx ; if # offset != nb_loop 000187d7: jb 18780 ; then continue Referenced by (conditionnal) jump(s) at Address(es): 00018777 000187d9: mov %esi,%eax ; eax = result 000187db: lea 0xfffffff8(%ebp),%esp 000187de: pop %esi 000187df: pop %edi 000187e0: mov %ebp,%esp 000187e2: pop %ebp 000187e3: ret This is the final step. The result is multiplied by 2, and we continue the generation until we have done as many loops as the # offset value. A very simple algo! Here is the same in C: result : will contain the second part of the code. null_offset : offset of the # in the serial. cur_char : char* param1 : string that contains the first part of the serial and the # result = 0; for(i=0; i < null_offset; i++) { cur_char = param1 + i; resultat += (*cur_char + 0xC007); result *= 2; } III- Conclusion: That's all for this time. Hope you enjoyed it and learned something. Before leaving you I would say that even if you don't like vim, try it: It is not trivial when you start but once you know the keyboard shortcuts it is really efficient (This is the same for editing code). Concerning the target, there is not much to say: easy and no new things... almost (Look where is saved your serial number...unusual isn't it?). See you later, Zadig.