The Brain 8 Keygen
Introduction In the past weeks I enjoyed working on reversing a piece of software (don’t ask me the name), to study how serial numbers are validated. The story the user has to follow is pretty common: download the trial, pay, get the serial number, use it in the annoying nag screen to get the fully functional version of the software. Since my purpose is to not damage the company developing the software, I will not mention the name of the software, nor I will publish the final key generator in binary form, nor its source code. My goal is instead to study a real case of serial number validation, and to highlight its weaknesses.
In this post we are going to take a look at the steps I followed to reverse the serial validation process and to make a key generator using symbolic virtual machine. We are not going to follow all the details on the reversing part, since you cannot reproduce them on your own.
We will concentrate our thoughts on the key-generator itself: that is the most interesting part. Getting acquainted The software is an x86 executable, with no anti-debugging, nor anti-reversing techniques. When started it presents a nag screen asking for a registration composed by: customer number, serial number and a mail address. This is fairly common in software.
Tools of the trade First steps in the reversing are devoted to find all the interesting functions to analyze. To do this I used with Hex-Rays decompiler, and the debugger. For the last part I used symbolic virtual machine under Linux, and some bash scripting. The actual key generator was a simple application. Let me skip the first part, since it is not very interesting. You can find many other articles on the web that can guide you through basic reversing techniques with IDA Pro.
I only kept in mind some simple rules, while going forward: • always rename functions that uses interesting data, even if you don’t know precisely what they do. A name like license_validation_unknown_8 is always better than a default like sub_46fa39; • similarly, rename data whenever you find it interesting; • change data types when you are sure they are wrong: use structs and arrays in case of aggregates; • follow cross references of data and functions to expand your collection; • validate your beliefs with the debugger if possible.
For example, if you think a variable contains the serial, break with the debugger and see if it is the case. Big picture When I collected the most interesting functions, I tried to understand the high level flow and the simpler functions.
FL Studio Producer Edition 12.3 Keygen Crack is a complete software music production environment, representing more than 14 years of advanced developments. I just renewed our Team Brain x 2, at considerable cost, and yet can't get any information on when a significant bug introduced in 8.0.0.7 will be. We just released TheBrain 8 in November with over 50 new features with much industry acclaim.
Here are the main variables and types used in the validation process. As a note for the reader: most of them have been purged of uninteresting details, for the sake of simplicity.
1 2 3 4 5 int v1 = SymVar_1, v2 = SymVar_2; // symbolic variables if ( v1 >0 ) v2 = 0; if ( v2 == 0 && v1 0 and so, the symbolic engine adds a constraint SymVar_1 >0 for the true branch or conversely SymVar_1 0 && (SymVar_2 == 0 && SymVar_1 0). For example SymVar_1 = 1 and some random value for SymVar_2. The engine then backtrack to the previous branch and uses the negation of the constraint, that is SymVar_1 0). This is satisfiable with SymVar_1 = -1 and SymVar_2 = 0. This concludes the analysis of the program paths, and our symbolic execution engine can output the following test cases: • v1 = 1; • v1 = -1, v2 = 0. Those test cases are enough to cover all the paths of the program.
This approach is useful for testing because it helps generating test cases. It is often effective, and it does not waste computational power of your brain. You know tests are very difficult to do effectively, and brain power is such a scarce resource! I don’t want to elaborate too much on this topic because it is way too big to fit in this post. Moreover, we are not going to use symbolic execution engines for testing purpose. This is just because we don’t like to use things in the way they are intended:) However, I will point you to some good references in the last section. 1 2 3 4 5 KLEE: output directory is '/work/klee-out-0' KLEE: done: total instructions = 26 KLEE: done: completed paths = 2 KLEE: done: generated tests = 2 KLEE will generate test cases for the input variable, trying to cover all the possible execution paths and to make the provided assertions to fail (if any given).
In this case we have two execution paths and two generated test cases, covering them. Test cases are in the output directory (in this case /work/klee-out-0).
The soft link klee-last is also provided for convenience, pointing to the last output directory. Inside that directory a bunch of files were created, including the two test cases named test000001.ktest and test000002.ktest. These are binary files, which can be examined with the ktest-tool utility. Let’s try it. 1 2 3 $ ktest - tool -- write - ints klee - last / test000002.
Object 0: data: 0 In these test files, KLEE reports the command line arguments, the symbolic objects along with their size and the value provided for the test. To cover the whole program, we need input variable to get a value greater than 10 and one below or equal. You can see that this is the case: in the first test case the value is used, covering the first branch, while 0 is provided for the second, covering the other branch. So far, so good. But what if we change the function in this way? 1 2 3 4 $ ktest - tool -- write - ints klee - last / test000002.
Ktest ktest file: ' klee - last / test000002. Object 0: data: 10 As we had expected, the assertion fails when input value is 10. So, as we now have three execution paths, we also have three test cases, and the whole program gets covered. KLEE provides also the possibility to replay the tests with the real program, but we are not interested in it now.
You can see a usage example in this. KLEE’s abilities to find execution paths of an application are very good. According to the, KLEE has been successfully used to test all 89 stand-alone programs in GNU COREUTILS and the equivalent busybox port, finding previously undiscovered bugs, errors and inconsistencies. The achieved code coverage were more than 90% per tool. Pretty awesome! But, you may ask:.
You will see it in a moment. KLEE to reverse a function As we have a powerful tool to find execution paths, we can use it to find the path we are interested in. As showed by the nice post of Feliam, we can use KLEE to solve a maze.
The idea is simple but very powerful: flag the portion of code you interested in with a klee_assert(0) call, causing KLEE to highlight the test case able to reach that point. In the maze example, this is as simple as changing a read call with a klee_make_symbolic and the prinft('You win! N') with the already mentioned klee_assert(0).
Test cases triggering this assertion are the one solving the maze! For a concrete example, let’s suppose we have this function. 1 2 3 4 5 6 7 8 $ clang - emit - llvm - g - o atoi. C $ klee atoi.
Ll KLEE: output directory is '/work/klee-out-4' KLEE: WARNING: undefined reference to function: atoi KLEE: WARNING ONCE: calling external: atoi ( 0 ) KLEE: ERROR: / work / atoi. C: 5: failed external call: atoi KLEE: NOTE: now ignoring this error at this location. To fix this we can use the KLEE uClibc and POSIX runtime. Dyno Bmx Bike Serial Numbers on this page. Taken from the help: “If we were running a normal native application, it would have been linked with the C library, but in this case KLEE is running the LLVM bitcode file directly. In order for KLEE to work effectively, it needs to have definitions for all the external functions the program may call. Similarly, a native application would be running on top of an operating system that provides lower level facilities like write(), which the C library uses in its implementation.
As before, KLEE needs definitions for these functions in order to fully understand the program. We provide a POSIX runtime which is designed to work with KLEE and the uClibc library to provide the majority of operating system facilities used by command line applications”. Let’s try to use these facilities to test our atoi function. 1 2 3 4 5 6 7 8 9 10 11 $ klee -- optimize -- libc = uclibc -- posix - runtime atoi.
Ll -- sym - args 0 1 3 KLEE: NOTE: Using klee - uclibc: / usr / local / lib / klee / runtime / klee - uclibc. Bca KLEE: NOTE: Using model: / usr / local / lib / klee / runtime / libkleeRuntimePOSIX. Bca KLEE: output directory is '/work/klee-out-5' KLEE: WARNING ONCE: calling external: syscall ( 16, 0, 21505, 70495424 ) KLEE: ERROR: / tmp / klee - uclibc / libc / stdlib / stdlib. C: 526: memory error: out of bound pointer KLEE: NOTE: now ignoring this error at this location KLEE: done: total instructions = 5756 KLEE: done: completed paths = 68 KLEE: done: generated tests = 68 And KLEE founds the possible out of bound access in our program. Because you know, our program is bugged:) Before to jump and fix our code, let me briefly explain what these new flags did: • --optimize: this is for dead code elimination. It is actually a good idea to use this flag when working with non-trivial applications, since it speeds things up; • --libc=uclibc and --posix-runtime: these are the aforementioned options for uClibc and POSIX runtime; • --sym-args 0 1 3: this flag tells KLEE to run the program with minimum 0 and maximum 1 argument of length 3, and make the arguments symbolic.
Note that adding atoi function to our code, adds 68 execution paths to the program. Using many libc functions in our code adds complexity, so we have to use them carefully when we want to reverse complex functions. Let now make the program safe by adding a check to the command line argument length. Let’s also add an assertion, because it is fun:).
1 2 3 4 5 6 7 8 9 10 11 12 $ clang - emit - llvm - g - o atoi2. Ll - c atoi2. C $ klee -- optimize -- libc = uclibc -- posix - runtime atoi2. Ll -- sym - args 0 1 3 KLEE: NOTE: Using klee - uclibc: / usr / local / lib / klee / runtime / klee - uclibc. Bca KLEE: NOTE: Using model: / usr / local / lib / klee / runtime / libkleeRuntimePOSIX. Bca KLEE: output directory is '/work/klee-out-6' KLEE: WARNING ONCE: calling external: syscall ( 16, 0, 21505, 53243904 ) KLEE: ERROR: / work / atoi2.
C: 8: ASSERTION FAIL: 0 KLEE: NOTE: now ignoring this error at this location KLEE: done: total instructions = 5962 KLEE: done: completed paths = 73 KLEE: done: generated tests = 69 Here we go! We have fixed our bug. KLEE is also able to find an input to make the assertion fail. 1 2 3 4 5 6 7 8 9 10 11 $ ls klee - last / grep err test000016. Err $ ktest - tool klee - last / test000016. Ktest ktest file: ' klee - last / test000016.
Ktest ' args: [ ' atoi. Ll ', ' -- sym - args ', '0', '1', '3' ] num objects: 3. Object 1: name: ' arg0 ' object 1: size: 4 object 1: data: ' + 42 x00 '. And the answer is the string “+42” as we know. There are many other KLEE options and functionalities, but let’s move on and try to solve our original problem. Interested readers can find a good tutorial, for example, in.
KLEE keygen Now that we know basic KLEE commands, we can try to apply them to our particular case. We have understood some of the validation algorithm, but we don’t know the computation details. They are just a mess of arithmetical and logical operations that we are tired to analyze. Here is our plan: • we need at least a valid customer number, a serial number and a mail address; • more ambitiously we want a list of them, to make a key generator. This is a possibility.
1 2 $ clang - emit - llvm - g - o attempt1. Ll - c attempt1.
C $ klee -- optimize -- libc = uclibc -- posix - runtime attempt1. Ll And then wait for an answer. And wait for another while. Make some coffee, drink it, come back and watch the PC heating up.
Go out, walk around, come back, have a shower, and. It’s still running! OK, that’s enough!
Porcelain Patch Kit on this page. Let’s kill it. Deconstruction approach We have assumed too much from the tool.
It’s time to use the brain and ease its work a little bit. Let’s decompose the big picture of the registration check presented before piece by piece. We will try to solve it bit by bit, to reduce the solution space and so, the complexity.
Recall that the algorithm is composed by three main conditions: • serial number must be valid by itself; • serial number, combined with mail address have to correspond to the actual customer number; • there has to be a correspondence between serial number and mail address, stored in a static table in the binary. Can we split them in different KLEE runs? Clearly the first one can be written as. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ clang - emit - llvm - g - o serial_type. Ll - c serial_type. C $ klee -- optimize -- libc = uclibc -- posix - runtime serial_type. KLEE: ERROR: / work / symbolic / serial_type.
C: 17: ASSERTION FAIL:! $ ls klee - last / grep err test000019. Err $ ktest - tool -- write - ints klee - last / test000019. Ktest ktest file: ' klee - last / test000019. Ktest ' args: [ ' serial_type. Ll ' ] num objects: 2 object 0: name: ' model_version ' object 0: size: 4 object 0: data: 1 object 1: name: ' serial ' object 1: size: 4 object 1: data: 102690141 Yes! We now have a serial number that is considered PRO by our target application.
The third condition is less simple: we have a table in which are stored values matching mail addresses with serial numbers. The high level check is this. 1 2 3 4 5 6 7 8 9 10 11 12 13 $ clang - emit - llvm - g - o serial. Ll - c serial. C $ klee -- optimize -- libc = uclibc -- posix - runtime serial. KLEE: ERROR: / work / symbolic / serial.
C: 21: ASSERTION FAIL:! $ ls klee - last / grep err test000032. Err $ ktest - tool -- write - ints klee - last / test000019.
Object 1: name: ' serial ' object 1: data: 120300641. For those who are wondering if get_index_in_mail_table could return a negative index, and so possibly crash the program I can answer that they are not alone. Asked me the same question, and unfortunately I have to answer a no. I tried, because I am a lazy ass, by changing the assertion above to klee_assert(index.