Post

Write-Up Android - Freeda Native Hook (Wrong Way)

Salut ! Ce week-end, le 29 novembre, avait lieu le HeroCTF, un CTF créé et géré par des (anciens) étudiants des écoles ENSIBS, ESNA et ESIEA.

Pour cette édition, j’ai pu participer avec l’équipe d’Oterihack, où peu de joueurs ont pu vraiment le tryhard.

Personnellement, j’ai pu le faire une petite partie de la journée, le samedi matin et le soir, sans y aller le dimanche, mais cela ne m’a pas empêché de flag tous les challenges Android, un challenge LSB, ainsi qu’un magnifique MISC de bootloader.

On se retrouve donc aujourd’hui pour le write-up du dernier challenge Android, Freeda Native Hook, de la mauvaise manière ! L’idée était d’utiliser Freeda, mais comme vous allez le voir, du fait que ça ne fonctionnait pas sur mon ordinateur, j’ai dû le reverse afin de trouver le flag !

Lors de l’arrivée sur ce challenge, on a un fichier apk nommé app-ctf.apk. Les fichiers .apk sont les fichiers utilisés pour distribuer et installer des applications android.

La première étape était de décompiler le fichier pour avoir une idée du challenge à faire. Pour cela, j’ai utilisé JADX qui est un outil de décompilation d’applications Android.
Il sert à analyser le contenu d’un fichier .apk, notamment pour lire le code source.

1
jadx app-ctf.apk -d ./output/ 

Une fois cette commande exécutée, on va avoir tous les fichiers du projet apk dans le répertoire /output/

Image

Comme on le voit, on a 2 grands répertoires resources et sources. Et dans le répertoire sources, on trouve les fichiers liés au CTF dans le répertoire HeroCTF et qui se nomment CheckFlag.java et NativeGate.java.

En regardant de plus près, voici le contenu des deux fichiers :

CheckFlag.java

1
2
3
4
5
6
7
8
9
10
11
package com.heroctf.freeda3.utils;
  
/* loaded from: classes.dex */
public abstract class CheckFlag {
    public static boolean checkFlag(String input) {
        if (input == null) {
            return false;
        }
        return NativeGate.nCheck(input);
    }
}

NativeGate.java

1
2
3
4
5
6
7
8
9
10
package com.heroctf.freeda3.utils;
  
/* loaded from: classes.dex */
public abstract class NativeGate {
    public static native boolean nCheck(String str);
  
    static {
        System.loadLibrary("v3");
    }
}

En analysant le premier fichier CheckFlag.java, on voit une première vérification de l’input avec null qui va faire avorter la fonction. Dans l’autre cas, il va appeler la fonction ncheckde la classe NativeGate qui doit être dans le répertoire com.heroctf.freeda3.utils du fait du seul import.

En regardant le deuxième fichier NativeGate.java, on voit ici la fonction ncheck prenant une string en entrée, et que la classe va simplement appeler la Java Native Interface (JNI) v3. C’est donc à l’intérieur de celle-ci qu’on va avoir toute la logique pour retrouver le flag !

Les JNI sont des librairies compilées en C ou C++, qui sont utilisées sur une application Android. Elles permettent d’exécuter du code de haut niveau et en interagissant directement avec l’appareil physique. Ces librairies sont reconnaissables, puisqu’elles comportent l’extension .so (Shared Objects). Un très bon article qui explique cela est disponible ici :

  • https://redfoxsec.com/blog/exploring-native-modules-in-android-with-frida/

On voit aussi que ces librairies sont disponibles dans le répertoire /lib donc autant aller voir !

En inspectant l’arbre à l’intérieur du répertoire /resources/lib, on voit 3 fichiers :

  • arm64-v8a
  • x86
  • x86_64

Image

Ayant l’air plutôt similaire, je décide de m’attaquer au fichier x86_64

La première étape que j’ai faite était de regarder s’il y avait pas des informations dans le strings, mais rien n’y fait :

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
strings libv3.so
Android
12077973
__cxa_finalize
__cxa_atexit
__register_atfork
check_root
access
stat
__open_2
read
close
__system_property_get
strstr
__errno
ptrace
__strlen_chk
memcmp
strchr
__stack_chk_fail
get_flag
Java_com_heroctf_freeda3_utils_NativeGate_nCheck
libc.so
LIBC
liblog.so
libm.so
libdl.so
libv3.so
Root detected - flag unavailable
/system/bin/busybox
ro.debuggable
userdebug
/proc/self/status
/proc/self/maps
/system/bin/su
test-keys
ro.build.type
/sbin/su
/system/xbin/su
/system/bin/.ext/.su
/system/xbin/busybox
/sbin/.magisk
ro.build.tags
ro.secure
/proc/self/mounts
/vendor/bin/su
/system/xbin/daemonsu
/sys/fs/selinux/enforce
/data/adb/magisk
/data/adb/modules
/data/adb/ksu
/su/bin/su
8|kN"u
ffffff.
UAWAVAUATSH
)D$p
)D$`
)D$P
)D$@
)D$0
)D$
 /systemff.
)D$
)D$P
)D$@
)D$0
)D$
frid
$s4H
gum-f
$wCI
$s7H
libfridaH
-gadf
$wII
$s=H
fff.
re.fridaH
$wFI
$s:H
zygif
$wCI
$s7H
magif
$wCI
$s7H
edxp
$w=I
$s1H
substratH
$w>I
$s2H
xposf
)D$P
)D$@
)D$0
)D$
)D$P
)D$@
)D$0
)D$
)D$P
)D$@
)D$0
)D$
)D$P
)D$@
)D$0
)D$
TracerPiH1
TracerPiH1
|JD9
> rw,t
ffffff.
 /vendorH1
9 rw,t
[A\A]A^A_]
@2t<
@2t<
@2t<
@2t<
@2t<
@2t<
@2t<
<:Hc
00L$
H;D$
AWAVATSPH
0uFI
[A\A^A_
Android (12027248, +pgo, +bolt, +lto, +mlgo, based on r522817) clang version 18.0.1 (https://android.googlesource.com/toolchain/llvm-project d8003a456d14a3deb8054cdaa529ffbf02d9b262)
Linker: LLD 18.0.1
.fini_array
.text
.got
.comment
.note.android.ident
.got.plt
.rela.plt
.bss
.dynstr
.eh_frame_hdr
.gnu.version_r
.data.rel.ro
.rela.dyn
.gnu.version
.dynsym
.gnu.hash
.relro_padding
.eh_frame
.note.gnu.build-id
.dynamic
.shstrtab
.rodata

On n’a pas plus d’informations sur le flag mais juste sur des librairies importées.

En cherchant un peu plus loin, j’ai vu qu’on pouvait utiliser un outil pour décompiler ces fichiers .so qui s’appelle Radare2. Celui-ci permet d’interagir avec le fichier, trouver le code assembleur, faire des sauts …

C’est donc cet outil que je vais utiliser grâce à la commande suivante :

1
r2 -A ./libv3.so

L’option -A étant faite pour faire une première analyse

Une fois dans l’outil, on peut lister les fonctions grâce à la commande :

1
afl

Image Nous voyons 2 fonctions intéressantes qui sont sym.check_rootet sym.get_flag. On peut maintenant voir le contenu des deux fonctions grâce a Radare2 et la suite d’instruction :

1
2
s {FONCTION}
pdf

s {FONCTION} étant pour se positionner au niveau de la fonction pdf étant pour afficher la fonction désassemblé

sym.get_flag Image

sym.check_root Image Image Pour cette fonction, elle est énorme et contient une multitude de check.

On en conclut donc que :

  • sym.check_flag-> Fonction qui retourne le flag
  • sym.check_root -> Fonction de détection de root

Lorsqu’on analyse sym.get_flag, on voit le call vers check_root grâce à fnc.00003110 (adresse de la fonction).

1
│ 0x00002845 e8c6080000 call fcn.00003110 ; Appel check_root

Ensuite, la fonction va vérifier le retour :

1
2
│ 0x0000284a 85c0 test eax, eax
│ ┌─< 0x0000284c 7414 je 0x2862

en x86_64, la valeur de retour d’une fonction est placée dans le registre eax (ou rax pour les 64 bits)

Puis si eax = 0, on va à l’instruction 2862 qui correspond a la réponse “Root detected - flag unavailable”. Sinon, on continue dans le bloc qui suit le je (jump equal) Tout est résumé de manière plus claire dans ce schéma !

Image On voit donc que le flag est construit via la fonction fcn.00002870

Maintenant, il va falloir bypass le check pour toujours afficher avoir 1 !

Pour ce faire, il va falloir modifier la valeur de retour de la fonction fnc.00003110 afin de toujours retourner 1. Pour ce faire, on va se déplacer dans la fonction, mettre 1 dans eax puis retourner le résultat grâce a :

1
2
3
4
5
6
7
8
# On se place 
s fnc.00003110

# On met 1 dans EAX 
wa mov eax, 1

# On appelle le return
wa ret

Ici, on indique juste que la fonction fnc.00003110 (check_root) va directement renvoyer 1

C’est la manière la plus simple car sinon, il fallait trouver l’endroit dans la fonction check_rootde plus de 800 lignes :)

A ce moment, on peut afficher des instructions pour savoir ou on est dans le programme et on peut le faire grâce a la commande suivante :

1
pd {nombre}

On a donc ce résultat :

Image

Il s’agit là de l’algorithme du flag. Avec l’aide d’un peu d’IA, on trouve que les valeurs référencées sont dans

1
2
0x00000bc0
0x00000fb0 

Il s’agit là des segments read-only data (RODATA) dans le fichier .so. On va donc pouvoir les afficher grâce à :

1
2
px 100 @ 0x00000bc0
px 100 @ 0x00000bf0

Ce qui nous donne donc le flag ! Sans être passé dans la moulinette de l’algorithme :

Image

A ce stade, j’ai extrait les instructions dans un fichier texte pour que ce soit plus simple grâce à :

1
2
# vérifiez à bien encore dans @0x00002870 !
pd 200 > ./result.txt

En analysant l’algorithme dans ./result.txt :

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
alop@MSI ~/cybersecurite/ctf/output/resources/lib/x86_64 $ head -150 /tmp/fcn_full.txt
            0x00002870      4883ec18       sub rsp, 0x18
            0x00002874      64488b042528.  mov rax, qword fs:[0x28]
            0x0000287d      4889442410     mov qword [rsp + 0x10], rax
            0x00002882      8b05702c0000   mov eax, dword [0x000054f8] ; [0x54f8:4]=0
            0x00002888      89442408       mov dword [rsp + 8], eax
            0x0000288c      8b442408       mov eax, dword [rsp + 8]
            0x00002890      31c9           xor ecx, ecx
            0x00002892      baffffffff     mov edx, 0xffffffff         ; -1
            0x00002897      89ce           mov esi, ecx
            0x00002899      488d054ce1ff.  lea rax, [0x000009ec]
            0x000028a0      486334b0       movsxd rsi, dword [rax + rsi*4]
            0x000028a4      4801c6         add rsi, rax
            0x000028a7      ffe6           jmp rsi
            0x000028a9      0f1f80000000.  nop dword [rax]
            0x000028b0      83fa2e         cmp edx, 0x2e
        ┌─< 0x000028b3      0f8f31070000   jg 0x2fea
        │   0x000028b9      4863d2         movsxd rdx, edx
        │   0x000028bc      48ffc2         inc rdx
        │   0x000028bf      89d1           mov ecx, edx
        │   0x000028c1      83e107         and ecx, 7
        │   0x000028c4      83f906         cmp ecx, 6
       ┌──< 0x000028c7      0f8790030000   ja 0x2c5d
       ││   0x000028cd      488d3528e1ff.  lea rsi, [0x000009fc]
       ││   0x000028d4      48630c8e       movsxd rcx, dword [rsi + rcx*4]
       ││   0x000028d8      4801f1         add rcx, rsi
       ││   0x000028db      ffe1           jmp rcx
       ││   0x000028dd      c744240c0000.  mov dword [rsp + 0xc], 0
       ││   0x000028e5      3154240c       xor dword [rsp + 0xc], edx
       ││   0x000028e9      8b4c240c       mov ecx, dword [rsp + 0xc]
       ││   0x000028ed      8d0cd2         lea ecx, [rdx + rdx*8]
       ││   0x000028f0      8d0c49         lea ecx, [rcx + rcx*2]
       ││   0x000028f3      01d1           add ecx, edx
       ││   0x000028f5      01d1           add ecx, edx
       ││   0x000028f7      83c107         add ecx, 7
       ││   0x000028fa      4863c9         movsxd rcx, ecx
       ││   0x000028fd      4869f1abaaaa.  imul rsi, rcx, 0x2aaaaaab
       ││   0x00002904      4889f7         mov rdi, rsi
       ││   0x00002907      48c1ef3f       shr rdi, 0x3f
       ││   0x0000290b      48c1ee23       shr rsi, 0x23
       ││   0x0000290f      01fe           add esi, edi
       ││   0x00002911      c1e604         shl esi, 4
       ││   0x00002914      8d3476         lea esi, [rsi + rsi*2]
       ││   0x00002917      29f1           sub ecx, esi
       ││   0x00002919      4863c9         movsxd rcx, ecx
       ││   0x0000291c      488d359de2ff.  lea rsi, [0x00000bc0]
       ││   0x00002923      0fb63431       movzx esi, byte [rcx + rsi]
       ││   0x00002927      488d3dc2e2ff.  lea rdi, [0x00000bf0]
       ││   0x0000292e      0fb60c39       movzx ecx, byte [rcx + rdi]
       ││   0x00002932      4863fa         movsxd rdi, edx
       ││   0x00002935      4869ff932449.  imul rdi, rdi, 0xffffffff92492493
       ││   0x0000293c      48c1ef20       shr rdi, 0x20
       ││   0x00002940      01d7           add edi, edx
       ││   0x00002942      4189f8         mov r8d, edi
       ││   0x00002945      41c1e81f       shr r8d, 0x1f
       ││   0x00002949      c1ff02         sar edi, 2
       ││   0x0000294c      4401c7         add edi, r8d
       ││   0x0000294f      448d04fd0000.  lea r8d, [rdi*8]
       ││   0x00002957      4429c7         sub edi, r8d
       ││   0x0000295a      01d7           add edi, edx
       ││   0x0000295c      40fec7         inc dil
       ││   0x0000295f      40887c2407     mov byte [rsp + 7], dil
       ││   0x00002964      bfc37b9d5f     mov edi, 0x5f9d7bc3
       ││   0x00002969      803d8c2b0000.  cmp byte [0x000054fc], 0    ; [0x54fc:1]=0
       ││   0x00002970      7406           je 0x2978
       ││   0x00002972      8b3d802b0000   mov edi, dword [0x000054f8] ; [0x54f8:4]=0
       ││   0x00002978      897c240c       mov dword [rsp + 0xc], edi
       ││   0x0000297c      c644240600     mov byte [rsp + 6], 0
       ││   0x00002981      30542406       xor byte [rsp + 6], dl
       ││   0x00002985      0fb67c2406     movzx edi, byte [rsp + 6]
       ││   0x0000298a      89d7           mov edi, edx
       ││   0x0000298c      83e703         and edi, 3
       ││   0x0000298f      4030ce         xor sil, cl
       ││   0x00002992      4032743c0c     xor sil, byte [rsp + rdi + 0xc]
       ││   0x00002997      40f6d6         not sil
       ││   0x0000299a      0fb64c2407     movzx ecx, byte [rsp + 7]
       ││   0x0000299f      40d2ce         ror sil, cl
       ││   0x000029a2      488d0d172b00.  lea rcx, segment.LOAD3      ; 0x54c0
       ││   0x000029a9      4088340a       mov byte [rdx + rcx], sil
       ││   0x000029ad      b901000000     mov ecx, 1
       ││   0x000029b2      89ce           mov esi, ecx
       ││   0x000029b4      486334b0       movsxd rsi, dword [rax + rsi*4]
       ││   0x000029b8      4801c6         add rsi, rax
       ││   0x000029bb      ffe6           jmp rsi
       ││   0x000029bd      c744240c0000.  mov dword [rsp + 0xc], 0
       ││   0x000029c5      3154240c       xor dword [rsp + 0xc], edx
       ││   0x000029c9      8b4c240c       mov ecx, dword [rsp + 0xc]
       ││   0x000029cd      8d0cd2         lea ecx, [rdx + rdx*8]
       ││   0x000029d0      8d0c49         lea ecx, [rcx + rcx*2]
       ││   0x000029d3      01d1           add ecx, edx
       ││   0x000029d5      01d1           add ecx, edx
       ││   0x000029d7      83c107         add ecx, 7
       ││   0x000029da      4863c9         movsxd rcx, ecx
       ││   0x000029dd      4869f1abaaaa.  imul rsi, rcx, 0x2aaaaaab
       ││   0x000029e4      4889f7         mov rdi, rsi
       ││   0x000029e7      48c1ef3f       shr rdi, 0x3f
       ││   0x000029eb      48c1ee23       shr rsi, 0x23
       ││   0x000029ef      01fe           add esi, edi
       ││   0x000029f1      c1e604         shl esi, 4
       ││   0x000029f4      8d3476         lea esi, [rsi + rsi*2]
       ││   0x000029f7      29f1           sub ecx, esi
       ││   0x000029f9      4863c9         movsxd rcx, ecx
       ││   0x000029fc      488d35bde1ff.  lea rsi, [0x00000bc0]
       ││   0x00002a03      0fb63431       movzx esi, byte [rcx + rsi]
       ││   0x00002a07      488d3de2e1ff.  lea rdi, [0x00000bf0]
       ││   0x00002a0e      0fb60c39       movzx ecx, byte [rcx + rdi]
       ││   0x00002a12      4863fa         movsxd rdi, edx
       ││   0x00002a15      4869ff932449.  imul rdi, rdi, 0xffffffff92492493
       ││   0x00002a1c      48c1ef20       shr rdi, 0x20
       ││   0x00002a20      01d7           add edi, edx
       ││   0x00002a22      4189f8         mov r8d, edi
       ││   0x00002a25      41c1e81f       shr r8d, 0x1f
       ││   0x00002a29      c1ff02         sar edi, 2
       ││   0x00002a2c      4401c7         add edi, r8d
       ││   0x00002a2f      448d04fd0000.  lea r8d, [rdi*8]
       ││   0x00002a37      4429c7         sub edi, r8d
       ││   0x00002a3a      01d7           add edi, edx
       ││   0x00002a3c      40fec7         inc dil
       ││   0x00002a3f      40887c2407     mov byte [rsp + 7], dil
       ││   0x00002a44      bfc37b9d5f     mov edi, 0x5f9d7bc3
       ││   0x00002a49      803dac2a0000.  cmp byte [0x000054fc], 0    ; [0x54fc:1]=0
       ││   0x00002a50      7406           je 0x2a58
       ││   0x00002a52      8b3da02a0000   mov edi, dword [0x000054f8] ; [0x54f8:4]=0
       ││   0x00002a58      897c240c       mov dword [rsp + 0xc], edi
       ││   0x00002a5c      c644240600     mov byte [rsp + 6], 0
       ││   0x00002a61      30542406       xor byte [rsp + 6], dl
       ││   0x00002a65      0fb67c2406     movzx edi, byte [rsp + 6]
       ││   0x00002a6a      89d7           mov edi, edx
       ││   0x00002a6c      83e703         and edi, 3
       ││   0x00002a6f      4030ce         xor sil, cl
       ││   0x00002a72      4032743c0c     xor sil, byte [rsp + rdi + 0xc]
       ││   0x00002a77      40f6d6         not sil
       ││   0x00002a7a      0fb64c2407     movzx ecx, byte [rsp + 7]
       ││   0x00002a7f      40d2ce         ror sil, cl
       ││   0x00002a82      488d0d372a00.  lea rcx, segment.LOAD3      ; 0x54c0
       ││   0x00002a89      4088340a       mov byte [rdx + rcx], sil
       ││   0x00002a8d      b901000000     mov ecx, 1
       ││   0x00002a92      89ce           mov esi, ecx
       ││   0x00002a94      486334b0       movsxd rsi, dword [rax + rsi*4]
       ││   0x00002a98      4801c6         add rsi, rax
       ││   0x00002a9b      ffe6           jmp rsi
       ││   0x00002a9d      c744240c0000.  mov dword [rsp + 0xc], 0
       ││   0x00002aa5      3154240c       xor dword [rsp + 0xc], edx
       ││   0x00002aa9      8b4c240c       mov ecx, dword [rsp + 0xc]
       ││   0x00002aad      8d0cd2         lea ecx, [rdx + rdx*8]
       ││   0x00002ab0      8d0c49         lea ecx, [rcx + rcx*2]
       ││   0x00002ab3      01d1           add ecx, edx
       ││   0x00002ab5      01d1           add ecx, edx
       ││   0x00002ab7      83c107         add ecx, 7
       ││   0x00002aba      4863c9         movsxd rcx, ecx
       ││   0x00002abd      4869f1abaaaa.  imul rsi, rcx, 0x2aaaaaab
alop@MSI ~/cybersecurite/ctf/output/resources/lib/x86_64 $

on trouve 4 grands blocs :

1
2
3
4
5
6
7
# Pour chaque position edx de 0 à 47 
0x000028ed 8d0cd2 lea ecx, [rdx + rdx*8] # ecx = edx * 9 
0x000028f0 8d0c49 lea ecx, [rcx + rcx*2] # ecx = ecx * 3 = edx * 27 
0x000028f3 01d1 add ecx, edx # ecx += edx 
0x000028f5 01d1 add ecx, edx # ecx += edx 
0x000028f7 83c107 add ecx, 7 # ecx += 7 
# Donc: idx = (edx * 29 + 7) % 48
1
2
3
4
5
# Lecture des tables 
0x0000291c 488d359de2ff. lea rsi, [0x00000bc0] # table1 
0x00002923 0fb63431 movzx esi, byte [rcx + rsi] 
0x00002927 488d3dc2e2ff. lea rdi, [0x00000bf0] # table2 
0x0000292e 0fb60c39 movzx ecx, byte [rcx + rdi]
1
2
3
4
5
6
# Décodage avec XOR, NOT, ROR 
0x0000298f 4030ce xor sil, cl # XOR des deux tables 
0x00002992 4032743c0c xor sil, byte [rsp + rdi + 0xc] # XOR avec clé 
0x00002997 40f6d6 not sil # NOT 
0x0000299f 40d2ce ror sil, cl 
# Rotation droite
1
2
3
# Écriture dans le buffer
0x000029a2      488d0d172b00.  lea rcx, segment.LOAD3    ; 0x54c0
0x000029a9      4088340a       mov byte [rdx + rcx], sil

De plus, une information importante qu’on va avoir est la clé utilisée pour faire le XOR :

1
0x00002964      bfc37b9d5f     mov edi, 0x5f9d7bc3

On peut la deviner car la clé est mise dans le registre edi

Avec ça, on peut maintenant traduire tout ça en python afin de trouver le flag !

1
2
table1 = bytes.fromhex("171105b3c142d01e387c6b4e227590a3380fcd84acf287adc178f91d926b994f81c105b2f16d693adbc3be340fc8611f")
table2 = bytes.fromhex("31d91f31891339b22debbed7af6d5692affef1752b508cdbec92064e74a9ee865488 4e0fe0976fccc3adeb41ac223726".replace(" ", ""))

Retranscription en bytes des 2 valeurs dans 0x00000bc0 et 0x00000bf0

1
2
3
4
5
    # Calcul de l'index dans les tables
    ecx = edx + (edx * 8)      # edx * 9
    ecx = ecx + (ecx * 2)       # ecx * 3 = edx * 27
    ecx = ecx + edx + edx + 7   # + 2*edx + 7
    idx = ecx % 48

Retranscription du premier bloc

1
2
3
xor_key = 0x5f9d7bc3  # Clé trouvée à l'adresse 0x54f8
	# Dans la boucle 
    xor_byte = (xor_key >> ((edx % 4) * 8)) & 0xFF

Retranscription de la partie XOR

1
2
3
4
5
    # Décodage
    result = esi ^ ecx_val      # XOR des deux tables
    result ^= xor_byte           # XOR avec la clé
    result = (~result) & 0xFF    # NOT
    result = ((result >> shift) | (result << (8 - shift))) & 0xFF  # ROR

Retranscription du 4ème bloc

On peaufine un peu tout ça et ça nous donne le script suivant :

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
#!/usr/bin/env python3

table1 = bytes.fromhex("171105b3c142d01e387c6b4e227590a3380fcd84acf287adc178f91d926b994f81c105b2f16d693adbc3be340fc8611f")
table2 = bytes.fromhex("31d91f31891339b22debbed7af6d5692affef1752b508cdbec92064e74a9ee865488 4e0fe0976fccc3adeb41ac223726".replace(" ", ""))

flag = bytearray(48)
xor_key = 0x5f9d7bc3  # Clé trouvée à l'adresse 0x54f8

for edx in range(48):
    # Calcul de l'index dans les tables
    ecx = edx + (edx * 8)      # edx * 9
    ecx = ecx + (ecx * 2)       # ecx * 3 = edx * 27
    ecx = ecx + edx + edx + 7   # + 2*edx + 7
    idx = ecx % 48
    
    # Lecture des bytes des tables
    esi = table1[idx]
    ecx_val = table2[idx]
    
    # Calcul du shift (rotation)
    shift = ((edx % 7) + 1)
    
    # Extraction du byte de la clé XOR
    xor_byte = (xor_key >> ((edx % 4) * 8)) & 0xFF
    
    # Décodage
    result = esi ^ ecx_val      # XOR des deux tables
    result ^= xor_byte           # XOR avec la clé
    result = (~result) & 0xFF    # NOT
    result = ((result >> shift) | (result << (8 - shift))) & 0xFF  # ROR
    
    flag[edx] = result

print("Flag:", ''.join(chr(c) if 32 <= c <= 126 else '?' for c in flag))

Et maintenant, on peut profiter du challenge en soumettant le flag :

1
Hero{F1NAL_57EP_Y0U_KN0W_H0W_TO_R3V3R53_4NDR01D}

Pour le crochet apres FINAL_57EP, je sais pas pourquoi il apparait comme ça mais c’est un tiret du 8

J’ai trouvé le challenge vachement cool ! De cette manière, ça mélange de l’Android et du reverse, et le moyen d’y arriver est vraiment pas évident et demande de se creuser les méninges.

This post is licensed under CC BY 4.0 by the author.