DEF CON Quals 2019 : VERYANDROIDOSO

Here is my writeup for VERYANDROIDOSO task. Ofcourse with frida :D

App takes input from us and checks if it is correct flag. Length of flag should be 23 enclosed with OOO{..}. Also inside of brackets only hex [0-9a-f].
18(23-5) divided into 9 part with each part length of 2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Button button = (Button) findViewById(R.id.button);
final EditText editText = (EditText) findViewById(R.id.editText);
button.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
view = MainActivity.this.parse(editText.getText().toString());
if (view != null) {
if (Solver.solve(view[0], view[1], view[2], view[3], view[4], view[5], view[6], view[7], view[8]) != null) {
MainActivity.this.win();
} else {
MainActivity.this.fail();
}
MainActivity.this.finish();
return;
}
MainActivity.this.fail();
MainActivity.this.finish();
}
});

Solverrr

In solver class there are solver, getSecretNumber, scramble(not really), sleep (!):
and exported functions from native-lib:

m0, m1, m2, m3, m4, m6, m7, m8, m9

  • getSecretNumber gets one byte from hash of certificate of application. So its always returns same value to same input.

  • sleep function takes input, and sleep nanosecond*input and returns how much nanosecond is passed (which can be vary).

  • scramble does following :

1
2
3
4
5

public static int scramble(int i) {
int sleep = ((int) sleep(500)) - 499;
return ((i + ((int) Math.round(Math.sqrt((double) ((sleep * 4) * sleep)) / ((double) sleep)))) + 321) % 256;
}

Looks like it depends on output of sleep function ?

Nope :D because sqrt(4sleep^2)/sleep always returns 2. So overall scramble is same as return (i+2+321)%256
Same as getSecretNumber this function is always returns same value to same input.

  • If you open up ghidra and decompile you can see that m0-m1-m2-m3-m4-m6 doesn’t depend on any value other than its inputs. But
    m7 depends on s1 and s2. m8 depends on s3 and s4. If you look at offset of s4, you can see it is not defined. m9 takes one input and CREATES s4 data.
    So m9 is very important to m8’s operations and output. s1, s2 and s3 is already defined in binary and never changed with any other function.

Now comes fun part.

Fridaa

in solve function there are huge if blocks for each input part.

For i1 code is as follows :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int scramble = scramble(13);
if (i1 == 0) {
m0 = m0(100, getSecretNumber(scramble));
i10 = 255;
} else if (i1 == 1) {
m0 = m0(190, getSecretNumber(scramble));
i10 = 255;
} else if (i1 == 2) {
m0 = m0(88, getSecretNumber(scramble));
i10 = 255;
} else if (i1 == 3) {
.
.
.

if ((m0 & i10) != 172) {
return false;
}

To not return false, we need to find correct m0 value . which is m0 & 255 == 172 . iff m0 = 172 . As you can see m0 set with output of m0 function.
So we need to find correct first parameter to m0. How ? frida :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Java.perform(function(){

b = Java.use("ooo.defcon2019.quals.veryandroidoso.Solver")

b.sleep.implementation = function(a){
return 501
}

b.getSecretNumber.implementation = function(a){
r = this.getSecretNumber(a)
console.log("Secret " + r + " " + a)
return r
}
b.scramble.implementation = function(a){
r = this.scramble(a)
console.log("Scramble " + r + " " + a)
return r
}
for(i=0;i < 255;i++){
jj = parseInt(b.m0(i,113))&255
if(jj == 172){
console.log("m0 = " + i)
}
}
})

How did I know second parameter of m0 ?. Lets just give random input : OOO{123456789012345678}

Attach with frida -U --no-pause -f ooo.defcon2019.quals.veryandroidoso -l 1.txt

Then frida outputs :

Scramble 80 13 Secret 113 80

secret function output is 113.

Then I bruteforced all values for m0 in for loop. Frida says m0 should be 53. 53 is input to m0. And we have just 1 option since we ANDed with 255.
If you search m0(53, in jadx you can find i1 == 250. Our first byte is 0xfa . If it is correct then We must see second scramble output in frida.

Try OOO{fa3456789012345678}

Same script gives:

Scramble 80 13 Secret 113 80 Scramble 147 80 Secret 201 147

So it is correct !

For i2 AND

1
2
3
4
5
6
7
8
9
10
11
//...
else if (i2 == 254) {
m0 = m1(198, getSecretNumber(scramble));
i10 = 255;
} else {
i10 = 255;
m0 = i2 == 255 ? m1(220, getSecretNumber(scramble)) : 0;
}
if ((m0 & i10) != 6) {
return false;
}

Our condition is m0 & 255 == 6, m0 should be 6. You can find correct secret value same as in step 1. So our for new for loop is :

1
2
3
4
5
6
for(i=0;i < 255;i++){
jj = parseInt(b.m1(i,201))&255
if(jj == 6){
console.log("m1 = " + i)
}
}

Add it to frida script. Then output is :
m0 = 53,m1 = 35 => i2 = 180

[250,180]

So far so good.

For i3 :

1
2
3
4
5
6
7
8
9
10
 else if (i3 == 254) {
i10 = m2(7, getSecretNumber(scramble));
m0 = 251;
} else {
i10 = i3 == 255 ? m2(161, getSecretNumber(scramble)) : 0;
m0 = 251;
}
if ((i10 & m0) != 146) {
return false;
}

This time output is ANDed with 251. i10 & 251 == 146 gives two option.

1
2
3
4
5
6
7
    for(i=0;i < 255;i++){
jj = parseInt(b.m2(i,132))&251
if(jj == 146){
console.log("m2 = " + i)
}

}

m2 = 7 and m2 = 11 ==> 254 and 52

.
.
.

Overall dc.txt gives :

1
2
3
4
5
6
7
8
m0 = 53
m1 = 35
m2 = 7,m2 = 11
m3 = 132,m3 = 140
m4 = 13,m4 = 14,m4 = 17,m4 = 18,m4 = 29,m4 = 30,m4 = 33,m4 = 34,m4 = 45,m4 = 46,m4 = 49,m4 = 50,m4 = 61,m4 = 62,m4 = 65,m4 = 66
m5 = 65,m5 = 67,m5 = 69,m5 = 71,m5 = 73,m5 = 75,m5 = 77,m5 = 79,m5 = 81,m5 = 83,m5 = 85,m5 = 87,m5 = 89,m5 = 91,m5 = 93,m5 = 95,m5 = 97,m5 = 99,m5 = 101,m5 = 103,m5 = 105,m5 = 107,m5 = 109,m5 = 111,m5 = 113,m5 = 115,m5 = 117,m5 = 119,m5 = 121,m5 = 123,m5 = 125,m5 = 127,m5 = 193,m5 = 195,m5 = 197,m5 = 199,m5 = 201,m5 = 203,m5 = 205,m5 = 207,m5 = 209,m5 = 211,m5 = 213,m5 = 215,m5 = 217,m5 = 219,m5 = 221,m5 = 223,m5 = 225,m5 = 227,m5 = 229,m5 = 231,m5 = 233,m5 = 235,m5 = 237,m5 = 239,m5 = 241,m5 = 243,m5 = 245,m5 = 247,m5 = 249,m5 = 251,m5 = 253
m6 = 205
m7 = 129

(Careful here, you should find correct i1 for m0) But since there is no m5 and it just checks over itself you can assign it as correct input.

1
2
3
4
5
6
7
8
i1 = 250
i2 = 180
i3 = [254,52]
i4 = [176,22]
i5 = [254, 145, 136, 72, 203, 118, 74, 120 ,244 ,221, 102, 147, 63, 238, 49, 247]
i6 = [65, 67, 69, 71, 73,75, 77, 79, 81, 83,85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253]
i7 = 68
i8 = 190

We have 2*2*16*64 different combination for now.

Bigger plan

After m7 if block, m9(i1+..+i7*i8) is called. This will change s4. Then ONE more if block comes for last hex. Since m8 is dependent on s4 we CANNOT bruteforce like before.

If block :

1
2
3
if ((i10 & 255) != 103) {
return false;
}

i10 must be 103.

To find last hex : frida script:

Iterate over possible inputs
Clear s4.
Calculate input for m9
Call m9
Find correct value for m8
Store solution

Frida can easily handle memory. Calculate all possible values : calc.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
nulls = []
for(i=0;i<0x100;i++){
nulls.push(0x0)
}
for(i=0; i < t.length;i++){

i9 = t[i][0] + t[i][1] + t[i][2] + t[i][3] + t[i][4] + t[i][5] + t[i][6] * t[i][7]
//call m9
b.m9(i9)
for(j=0;j<255;j++){

aa = b.m8(j,23)
res = parseInt(aa)&255
if( res== 103){
//console.log("Found last digit for : " + i9+ " "+ j)
console.log([t[i][0],t[i][1],t[i][2],t[i][3],t[i][4],t[i][5],t[i][6],t[i][7],j])
//clear s4 maybe one more digit will be found
Memory.writeByteArray(ptr(s4),nulls)

}
}
Memory.writeByteArray(ptr(s4),nulls)
}

Here t is possible values. Script will find correct input to m8 function. Then you will need to find correct i8. I found older inputs with manual search, but for this case I used python

Now there is just last check !

This time we have all inputs lets just give it to solve function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
b.solve.implementation = function(a,o,c,d,e,f,g,h,i){
solutions = []
nulls = []
for(i=0;i<0x1000;i++){
nulls.push(0x0)
}
for(i=0; i < t.length;i++){
t2 = this.solve(t[i][0],t[i][1],t[i][2],t[i][3],t[i][4],t[i][5],t[i][6],t[i][7],inputs[i])
console.log(i + " " + t2)
if(t2){
console.log(t[i][0],t[i][1],t[i][2],t[i][3],t[i][4],t[i][5],t[i][6],t[i][7],inputs[i])
return true
}
else{
Memory.writeByteArray(ptr(s4),nulls)

}
}
return true

}
b.sleep.implementation = function(a){
return 502
}

Last script

Bypass sleep function so we wont wait.

Fl4g

Now enter something like OOO{123456789012345678} and wait until frida finds correct value ! :D

Flag is hex values of those integers:
OOO{fab43416484944beba}