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/
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
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
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.check_root
Pour cette fonction, elle est énorme et contient une multitude de check.
On en conclut donc que :
sym.check_flag-> Fonction qui retourne le flagsym.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 !
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 :
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 :
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
0x00000bc0et0x00000bf0
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.





