Pedometer - Hack The Box

Lets start analzying the apk using JADX-GUI.

We have one activity, MainActivity.

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.rloura.pedometer;

import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;
import androidx.activity.result.C0041d;
import p004b.C0306c;
import p012d.AbstractActivityC0417l;
import p025g0.C0611a;
import p066t.AbstractC0955e;
import p070u1.C0974a;
import p070u1.C0976c;
import p073v1.C1030d;
import p079x1.AbstractC1073e;

public final class MainActivity extends AbstractActivityC0417l {

public static final int f1813w = 0;

public SensorManager f1814u;

public C0976c f1815v;

public final void m997n() {
Object systemService = getSystemService("sensor");
AbstractC1073e.m2489w(systemService, "null cannot be cast to non-null type android.hardware.SensorManager");
SensorManager sensorManager = (SensorManager) systemService;
this.f1814u = sensorManager;
Sensor defaultSensor = sensorManager.getDefaultSensor(1);
SensorManager sensorManager2 = this.f1814u;
if (sensorManager2 != null) {
sensorManager2.registerListener(new C0974a(this), defaultSensor, 3);
} else {
C1030d c1030d = new C1030d("lateinit property sensorManager has not been initialized");
AbstractC1073e.m2441c1(c1030d);
throw c1030d;
}
}

@Override
public final void onCreate(Bundle bundle) throws Exception {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
C0041d c0041dM63c = this.f73i.m63c("activity_rq#" + this.f72h.getAndIncrement(), this, new C0306c(0), new C0611a(this));
if (AbstractC0955e.m2234a(this, "android.permission.ACTIVITY_RECOGNITION") == 0) {
m997n();
} else {
c0041dM63c.m60w1();
}
this.f1815v = new C0976c(this);
}
}

Things to note from this activity:

Here is the content of the asset file a in hex:

1
2
3
4
5
6
7
8
9
10
❯ xxd a
00000000: 0101 0100 0101 0101 0101 0101 0101 0101 ................
00000010: f020 4000 f120 4000 f220 4000 f020 4000 . @.. @.. @.. @.
00000020: 012a f32b 602b 1d1b 7c56 7c22 4c75 3875 .*.+`+..|V|"Lu8u
00000030: 0845 3131 3141 0171 8971 fa41 7209 7256 .E111A.q.q.Ar.rV
00000040: 425e 965e de6e 494d 4928 7964 dd64 a954 B^.^.nIMI(yd.d.T
00000050: 7575 752a 455e ca5e b96e 72c8 7283 424a uuu*E^.^.nr.r.BJ
00000060: d54a a77a 7311 7325 4335 9135 fc05 6c0d .J.zs.s%C5.5..l.
00000070: 6c52 5c5e 615e 396e 5972 5909 697a 537a lR\^a^9nYrY.izSz
00000080: 114a 4359 430d 7355 bd55 f565 bcff 00 .JCYC.sU.U.e...

Lets take a look at C0974a class. (Will be showing the important code as some parts of code are not disassembled properly)

1
2
3
4
5
6
7
8
9
 InputStream inputStream = c0976c.f4097b;
if (inputStream.available() == 0) {
return;
}
int i4 = inputStream.read() ^ c0976c.f4099d;
for (EnumC0975b enumC0975b : EnumC0975b.values()) {
if (enumC0975b.f4095a == i4) {
...
}

Here is the structure of the enum:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0 - > 0x0 => STOP
1 - > 0x1 => PUSH
2 - > 0x2 => POP
3 - > 0x10 => ADD
4 - > 0x11 => SUB
5 - > 0x12 => MUL
6 - > 0x13 => DIV
7 - > 0x14 => MOD
8 - > 0x20 => EQ
9 - > 0x21 => LT
10 - > 0x22 => GT
11 - > 0x30 => NOT
12 - > 0x31 => XOR
13 - > 0x40 => IF
14 - > 0x41 => JMP
15 - > 0xf0 => CHRG
16 - > 0xf1 => AIRPLN
17 - > 0xf2 => INTRNT
18 - > 0xf3 => ENC
19 - > 0xf4 => DEC
20 - > 0xff => FLAG

Here is the solver script in Python:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
data_bytes = [
0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0xf0, 0x20, 0x40, 0x00, 0xf1, 0x20, 0x40, 0x00, 0xf2, 0x20, 0x40, 0x00, 0xf0, 0x20, 0x40, 0x00,
0x01, 0x2a, 0xf3, 0x2b, 0x60, 0x2b, 0x1d, 0x1b, 0x7c, 0x56, 0x7c, 0x22, 0x4c, 0x75, 0x38, 0x75,
0x08, 0x45, 0x31, 0x31, 0x31, 0x41, 0x01, 0x71, 0x89, 0x71, 0xfa, 0x41, 0x72, 0x09, 0x72, 0x56,
0x42, 0x5e, 0x96, 0x5e, 0xde, 0x6e, 0x49, 0x4d, 0x49, 0x28, 0x79, 0x64, 0xdd, 0x64, 0xa9, 0x54,
0x75, 0x75, 0x75, 0x2a, 0x45, 0x5e, 0xca, 0x5e, 0xb9, 0x6e, 0x72, 0xc8, 0x72, 0x83, 0x42, 0x4a,
0xd5, 0x4a, 0xa7, 0x7a, 0x73, 0x11, 0x73, 0x25, 0x43, 0x35, 0x91, 0x35, 0xfc, 0x05, 0x6c, 0x0d,
0x6c, 0x52, 0x5c, 0x5e, 0x61, 0x5e, 0x39, 0x6e, 0x59, 0x72, 0x59, 0x09, 0x69, 0x7a, 0x53, 0x7a,
0x11, 0x4a, 0x43, 0x59, 0x43, 0x0d, 0x73, 0x55, 0xbd, 0x55, 0xf5, 0x65, 0xbc, 0xff, 0x00
]

opcodes = {
0x0: "STOP", 0x1: "PUSH", 0x2: "POP",
0x10: "ADD", 0x11: "SUB", 0x12: "MUL", 0x13: "DIV", 0x14: "MOD",
0x20: "EQ", 0x21: "LT", 0x22: "GT",
0x30: "NOT", 0x31: "XOR",
0x40: "IF", 0x41: "JMP",
0xF0: "CHRG", 0xF1: "AIRPLN", 0xF2: "INTRNT",
0xF3: "ENC", 0xF4: "DEC",
0xFF: "FLAG"
}

class Pedometer_VM:
def __init__(self, data):
self.data = data
self.st = []
self.i = 0
self.d = 0
self.flag = ""

def read_byte(self):
if self.i < len(self.data):
b = self.data[self.i]
self.i += 1
return b
return None

def custom_push(self, x):
self.st.append(x & 0xFF)

def custom_pop(self):
return self.st.pop() if self.st else 0

def solve(self):
print("[+] Starting VM Execution")

while self.i < len(self.data):
dat = self.read_byte()
if dat is None:
break

op = dat ^ self.d
opcode = opcodes.get(op)

print(f"PC={self.i-1:03} | Opcode={opcode:<6} | Stack={self.st} | Key={self.d}")

if opcode == "PUSH":
val = self.read_byte()
if val is not None:
self.custom_push(val ^ self.d)

elif opcode == "ADD": self.custom_push(self.custom_pop() + self.custom_pop())

elif opcode == "SUB": self.custom_push(self.custom_pop() - self.custom_pop())

elif opcode == "MUL": self.custom_push(self.custom_pop() * self.custom_pop())

elif opcode == "DIV": self.custom_push(self.custom_pop() // self.custom_pop() if b else 0)

elif opcode == "MOD": self.custom_push(self.custom_pop() % self.custom_pop() if b else 0)

elif opcode == "EQ": self.custom_push(1 if self.custom_pop() == self.custom_pop() else 0)

elif opcode == "LT": self.custom_push(1 if self.custom_pop() < self.custom_pop() else 0)

elif opcode == "GT": self.custom_push(1 if self.custom_pop() > self.custom_pop() else 0)

elif opcode == "NOT": self.custom_push(1 if self.custom_pop() == 0 else 0)

elif opcode == "XOR":
res = self.custom_pop() ^ self.custom_pop()
self.custom_push(res)
self.d = res

elif opcode == "IF":
cond = self.custom_pop()
if cond == 0:
print("[-] IF failed")
return None
self.i += 1

elif opcode == "JMP": self.i += self.custom_pop()

elif opcode in ("CHRG", "AIRPLN", "INTRNT"):
self.custom_push(1)

elif opcode == "ENC":
self.d = self.custom_pop()

elif opcode == "DEC":
self.d = 0

elif opcode == "FLAG":
chars = []
for _ in range(21):
chars.append(chr(self.custom_pop() & 0xFF))
self.flag = "".join(chars)
print("[+] FLAG:", self.flag)
return self.flag

elif opcode == "STOP":
return None

return self.flag


vm = Pedometer_VM(data_bytes)
vm.solve()

Here is the output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
... (truncated)
PC=109 | Opcode=XOR | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 165, 200] | Key=52
PC=110 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109] | Key=109
PC=112 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 96] | Key=109
PC=114 | Opcode=XOR | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 96, 63] | Key=109
PC=115 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95] | Key=95
PC=117 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 62] | Key=95
PC=119 | Opcode=XOR | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 62, 102] | Key=95
PC=120 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88] | Key=88
PC=122 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 42] | Key=88
PC=124 | Opcode=XOR | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 42, 81] | Key=88
PC=125 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123] | Key=123
PC=127 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 40] | Key=123
PC=129 | Opcode=XOR | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 40, 106] | Key=123
PC=130 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66] | Key=66
PC=132 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66, 27] | Key=66
PC=134 | Opcode=XOR | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66, 27, 79] | Key=66
PC=135 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66, 84] | Key=84
PC=137 | Opcode=PUSH | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66, 84, 233] | Key=84
PC=139 | Opcode=XOR | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66, 84, 233, 161] | Key=84
PC=140 | Opcode=DEC | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66, 84, 72] | Key=72
PC=141 | Opcode=FLAG | Stack=[1, 0, 1, 1, 125, 116, 48, 112, 115, 95, 72, 101, 116, 95, 115, 75, 114, 52, 109, 95, 88, 123, 66, 84, 72] | Key=0
[+] FLAG: HTB{X_m4rKs_teH_sp0t}

Everything is working as expected and we got the flag.

Ending Thoughts

This was my first time solving a VM-based challenge and it took me a while to understand the opcodes and how the stack works.

Hope you find it useful :)