15 August 2019

After downloading the APK from the github repository we fire up android studio and the emulator. We install the APK by dragging it into the emulator interface and launch the app.

The application consists of a text field where we can enter a serial and a button "Activate Software" to supposedly activate the software from the given serial.

If we try a random serial a pop up appears asking us to enter a valid serial.

We start our reverse engineering with a static analysis of the application. We use JADX to do this.

We can see that there aren't a lot of classes :

- BuildConfig
- MainActivity
- PremiumActivity

From the manifest, we can see that the class MainActivity is started from the launcher :

```
<activity android:name="com.thc.bestpig.serial.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
```

The MainActivity contains a method called **validateSerial** :

```
public boolean validateSerial(String serial) {
if (serial.length() == 19
&& serial.charAt(4) == '-'
&& serial.charAt(9) == '-'
&& serial.charAt(14) == '-'
&& serial.charAt(5) == serial.charAt(6) + 1
&& serial.charAt(5) == serial.charAt(18)
&& serial.charAt(1) == (serial.charAt(18) % 4) * 22
&& ((serial.charAt(3) * serial.charAt(15)) / serial.charAt(17)) - 1 == serial.charAt(10)
&& serial.charAt(10) == serial.charAt(1)
&& serial.charAt(13) == serial.charAt(10) + 5
&& serial.charAt(10) == serial.charAt(5) - 9
&& (serial.charAt(0) % serial.charAt(7)) * serial.charAt(11) == 1440
&& (serial.charAt(2) - serial.charAt(8)) + serial.charAt(12) == serial.charAt(10) - 9
&& (serial.charAt(3) + serial.charAt(12)) / 2 == serial.charAt(16)
&& (serial.charAt(0) - serial.charAt(2)) + serial.charAt(3) == serial.charAt(12) + 15
&& serial.charAt(3) == serial.charAt(13)
&& serial.charAt(16) == serial.charAt(0)
&& serial.charAt(7) + 1 == serial.charAt(2)
&& serial.charAt(15) + 1 == serial.charAt(11)
&& serial.charAt(11) + 3 == serial.charAt(17)
&& serial.charAt(7) + 20 == serial.charAt(6)) {
return true;
}
return false;
}
```

From the `validateSerial`

function we have the following system of equations where x0 is serial.charAt(0) and so on :

```
A) x5 = x6 + 1
B) x5 = x18
C) x1 = (x18 % 4) * 22
D) ((x3 * x15) / x17) - 1 = x10
E) x10 = x1
F) x13 = x10 + 5
G) x10 = x5 - 9
H) (x0 % x7) * x11 = 1440
I) (x2 - x8) + x12 = x10 - 9
J) (x3 + x12) / 2 = x16
K) (x0 - x2) + x3 = x12 + 15
L) x3 = x13
M) x16 = x0
N) x7 + 1 = x2
O) x15 + 1 = x11
P) x11 + 3 = x17
Q) x7 + 20 = x6
```

After some substitions we get the following inequality

`x5 - 9 = (x5 mod 4) * 22`

Since `x5 - 9 = (x5 mod 4) * 22`

, we know that there is an integer `k`

such that :

`x5 - 9 = (4k + x5) * 22`

From there we now have an equation for `x5`

:

`x5 = - (88k + 9)/21`

Since `x5`

must be an integer we create a small kotlin script to test multiple possibilities for `k`

and return the ones where `x5`

is an integer :

```
fun main() {
for (k in 0..100) {
var res = (88.0*k+9.0)/21.0
if (res.roundToInt().toDouble() == res) {
println("k = $k => x5 = " + (88.0 * k + 9.0) / 21.0)
}
}
}
```

We get the following possibilities, so we assume we know `x5`

for now :

```
k = 3 => x5 = 13.0
k = 24 => x5 = 101.0
k = 45 => x5 = 189.0
k = 66 => x5 = 277.0
k = 87 => x5 = 365.0
```

We have now use this knowledge to solve the system of equations and express every variable with `x5`

:

```
x0 = x5 - 3
x1 = x5 - 9
x2 = x5 - 20
x3 = x5 - 4
x6 = x5 - 1
x7 = x5 - 21
x8 = x5 - 4
x10 = x5 - 9
x12 = x5 - 2
x13 = x5 - 4
x16 = x5 - 3
```

You might have noticed that `x11`

, `x15`

and `x17`

are missing.
If you tried to solve the system of equations without taking into account equation H) you might get something like:

```
x11 = x5 - 7
x15 = x5 - 8
x17 = x5 - 4
```

The problem is that these value are incorrect and do not work with the equation D), you will get something like `16 = 64`

which is obviously false.

The reason for that is that to find `x11`

, `x15`

and `x17`

you will use the following set of equations :

```
D) ((x3 * x15) / x17) - 1 = x10
O) x15 + 1 = x11
P) x11 + 3 = x17
```

When you get to this point, you already know how to express `x3`

and `x10`

from `x5`

so it should look like this :

```
D) ((x5 - 4) * x15) / x17 = x5 - 8
O) x15 + 1 = x11
P) x11 + 3 = x17
```

When you try to solve this set of equations the usual way, you might fail to take into account that you are working with **integers** and **integer division**.
This means that when you perform a division, you are rounding up the result to get an integer.

Now if we perform some substitutions we can get this equation:

`D) (x17 - 4) / x17 = (x5 - 8) / (x5 - 4)`

Since we are performing **integer division** we have :

```
(x17 - 4) / x17 = 0
(x5 - 8) / (x5 - 4) = 0
```

So this equation is really saying `0 = 0`

if we try to apply divisions.

To find the remaining letters we have to use equation H)

`H) (x0 % x7) * x11 = 1440`

From this equation we know that there is a `k`

(different from the previous one) such that:

`1440 = (x0 + k*x7) * x11`

If I replace `x0`

and `x7`

by their values expressed from `x5`

, and if I prettify the equation I can get `x11`

from `x5`

and `k`

:

`x11 = 1440 / (x5*(1+k) - 21*k - 3)`

Now I can create a function to compute the different possibilities for `x11`

from `x5`

:

```
fun findx11(x5 : Int) {
for (k in -100..100) {
val x11 = 1440.0 / (x5*(1+k) - 21*k - 3.0 )
if (x11 == x11.toInt().toDouble()) {
println("k = $k ; x11 = $x11")
}
}
}
```

When I have found the correct `x11`

I will be able to deduce `x15`

and `x17`

.

I am not finished yet here. I could finish manually by a process of trial and error but I still have equation D) that I
did not apply as a constraint. I will use it to help me find the proper `x5`

and the proper `x11`

.

`D) ((x3 * x15) / x17) - 1 = x10`

If I remodel the equation to express it from `x11`

and `x5`

here is what I get:

`(x5 - 4) * (x11 - 1) / (x11 + 3) = (x5 - 8)`

Since I have only guesses for `x5`

and `x11`

, I have to use this equation to validate the pair that satisfies all the constraints.

Here is the final code used to crack the serial:

```
import kotlin.math.roundToInt
fun main() {
guessX5().forEach {x5 ->
guessX11(x5).forEach {x11 ->
if((x5 - 4) * (x11 - 1) / (x11 + 3) == (x5 - 8)) {
println(computeSerial(x5, x11))
}
}
}
}
fun guessX5() : List<Int> {
val list = mutableListOf<Int>()
for (k in -100..100) {
var guess = -(88.0*k+9.0)/21.0
if (guess.isInt()) {
list.add(guess.toInt())
}
}
return list
}
fun guessX11(x5 : Int) : List<Int> {
val list = mutableListOf<Int>()
for (k in -100..100) {
val guess = 1440.0 / (x5*(1+k) - 21*k - 3.0 )
if (guess.isInt()) {
list.add(guess.toInt())
}
}
return list
}
fun Double.isInt() : Boolean {
return this.roundToInt().toDouble() == this
}
fun computeSerial(x5: Int, x11: Int) : String {
val x = Array(19, init = {0})
x[0] = x5 - 3
x[1] = x5 - 9
x[2] = x5 - 20
x[3] = x5 - 4
x[4] = 45
x[5] = x5
x[6] = x5 - 1
x[7] = x5 - 21
x[8] = x5 - 5
x[9] = 45
x[10] = x5 - 9
x[11] = x11
x[12] = x5 - 2
x[13] = x5 - 4
x[14] = 45
x[15] = x11 - 1
x[16] = x5 - 3
x[17] = x11 + 3
x[18] = x5
return x.joinToString("") { i -> i.toChar().toString() }
}
```

The serial is `HB7G-KJ6F-BPIG-OHSK`

.