PicoCTF 2019

Déjà l'ECSC vient aussi de se terminer le même jour, l'équipe France s'est donné à fond pendant ces 2 jours, bien joué à eux !

J'ai participé au PicoCTF avec l'équipe saladetomate.onion, l'équipe à acceuillit 2 guests qui ont bien géré (0p62No2 et Maltemo).

L'équipe termine 112ème au scoreboard global avec 105 challenges résolus.

Et petite victoire de notre côté : nous sommes arrivés 1ère équipe de l'ENSIBS

Malgré beaucoup de difficultés rencontrées j'ai trouvé quelques challenges cools et décide d'en faire un writeup.

Reverse engineering

asm2

asm2:
	<+0>:	push   ebp
	<+1>:	mov    ebp,esp
	<+3>:	sub    esp,0x10
	<+6>:	mov    eax,DWORD PTR [ebp+0xc]
	<+9>:	mov    DWORD PTR [ebp-0x4],eax
	<+12>:	mov    eax,DWORD PTR [ebp+0x8]
	<+15>:	mov    DWORD PTR [ebp-0x8],eax
	<+18>:	jmp    0x50c 
	<+20>:	add    DWORD PTR [ebp-0x4],0x1
	<+24>:	add    DWORD PTR [ebp-0x8],0xd1
	<+31>:	cmp    DWORD PTR [ebp-0x8],0x9087
	<+38>:	jle    0x501 
	<+40>:	mov    eax,DWORD PTR [ebp-0x4]
	<+43>:	leave  
	<+44>:	ret

Bon alors l'ASM c'est quelquechose quand on en a jamais vu. En gros esp c'est le registre qui ponte en haut de la stack, en le prennant avec un offset on tombe sur les arguments donnés à la fonction.. Ici, ebp+0xc correspond à arg2 (0x22), et ebp+0x8 c'est arg1 (0xe), car on a l'instruction mov ebp,esp qui nous dit de "mettre" le contenu de esp dans ebp.

Ensuite, on peut voir un jump à <+18> vers <+31> (une comparaison), tant que la comparaison n'est pas vérifiée on jump vers <+20>. On peut alors faire le rapprochement avec une boucle while ; dans cette boucle il se passe quelques opérations, c'est pas bien méchant. Pour résoudre ce challenge j'ai recodé cette fonction en Python :

#les arguments donnés dans l'énonncé :
a=0xe
b=0x22

#la boucle while à <+18>
while a <= 0x9087:
    b=b+0x1
    a=a+0xd1
print(hex(b))
#output : 0xd3

On aurrait très bien pu faire l'exécution du programme ASM à la main, mais j'ai décidé de prendre la voie de la facilité.

flag : 0xd3

droids0

L'énnoncé nous dit de regarder dans les logs android, je lance mon émulateur android :

$ ~/Andoid/Sdk/emulator/emulator -list-avds
Pixel_2_API_26
$ ~/Android/Sdk/emulator/emulator -avd Pixel_2_API_26 & #émulateur lançé
$ adb devices
List of devices attached
emulator-5554    device
$ adb install zero.apk
Success
$ adb logcat | grep picoCTF
10-08 19:40:47.998  4201  4201 I PICO    : picoCTF{a.moose.once.bit.my.sister}
flag : picoCTF{a.moose.once.bit.my.sister}

droids1

Pas trop d'indice dans l'énnoncé, rien non plus dans les logs, j'ai décompilé l'apk avec apktool :

$ apktool d one.apk
...
$ vim one/smali/com/hellocmu/picoctf/MainActivity.smali

Dans l'activité principale de l'application, on voit que la fonction getFlag() de l'activité FlagstaffHill, regardons ce qu'il y a dedans :

#fonction getFlag()
.method public static getFlag(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/String;
    .locals 2
    .param p0, "input"    # Ljava/lang/String;
    .param p1, "ctx"    # Landroid/content/Context;

    .line 11
    const v0, 0x7f0b002f

    invoke-virtual {p1, v0}, Landroid/content/Context;->getString(I)Ljava/lang/String;

    move-result-object v0

    .line 12
    .local v0, "password":Ljava/lang/String;
    #comparaison entre l'input et le string "password" du fichier strings.xml
    invoke-virtual {p0, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v1

    if-eqz v1, :cond_0

    invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->fenugreek(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    return-object v1

    .line 13
    :cond_0
    const-string v1, "NOPE"

    return-object v1
.end method

En mettant le contenu du string "password" du fichier one/res/values/strings.xml dans l'input de l'application on obtient le flag :

flag : picoCTF{pining.for.the.fjords}

droids2

Comme pour le challenge précédent, on va devoir décompiler l'apk pour trouver le flag, l'activité FlagstaffHill nous permet de retrouver le mot de passe pour avoir le flag :

.method public static getFlag(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/String;
    .locals 10
    .param p0, "input"    # Ljava/lang/String;
    .param p1, "ctx"    # Landroid/content/Context;

    .line 11
    const/4 v0, 0x6

    new-array v0, v0, [Ljava/lang/String;

    .line 12
    .local v0, "witches":[Ljava/lang/String;
    const/4 v1, 0x0

    const-string v2, "weatherwax"

    aput-object v2, v0, v1

    .line 13
    const/4 v1, 0x1

    const-string v2, "ogg"

    aput-object v2, v0, v1

    .line 14
    const/4 v1, 0x2

    const-string v2, "garlick"

    aput-object v2, v0, v1

    .line 15
    const/4 v1, 0x3

    const-string v2, "nitt"

    aput-object v2, v0, v1

    .line 16
    const/4 v1, 0x4

    const-string v2, "aching"

    aput-object v2, v0, v1

    .line 17
    const/4 v1, 0x5

    const-string v2, "dismass"

    aput-object v2, v0, v1

    .line 19
    const/4 v1, 0x3

    .line 20
    .local v1, "first":I
    sub-int v2, v1, v1

    .line 21
    .local v2, "second":I
    div-int v3, v1, v1

    add-int/2addr v3, v2

    .line 22
    .local v3, "third":I
    add-int v4, v3, v3

    sub-int/2addr v4, v2

    .line 23
    .local v4, "fourth":I
    add-int v5, v1, v4

    .line 24
    .local v5, "fifth":I
    add-int v6, v5, v2

    sub-int/2addr v6, v3

    .line 26
    .local v6, "sixth":I
    aget-object v7, v0, v5

    .line 27
    const-string v8, ""

    invoke-virtual {v8, v7}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    const-string v8, "."

    invoke-virtual {v7, v8}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    aget-object v9, v0, v3

    invoke-virtual {v7, v9}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    invoke-virtual {v7, v8}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    aget-object v9, v0, v2

    .line 28
    invoke-virtual {v7, v9}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    invoke-virtual {v7, v8}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    aget-object v9, v0, v6

    invoke-virtual {v7, v9}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    invoke-virtual {v7, v8}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    aget-object v9, v0, v1

    .line 29
    invoke-virtual {v7, v9}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    invoke-virtual {v7, v8}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    aget-object v8, v0, v4

    invoke-virtual {v7, v8}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v7

    .line 32
    .local v7, "password":Ljava/lang/String;
    invoke-virtual {p0, v7}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v8

    if-eqz v8, :cond_0

    invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->sesame(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v8

    return-object v8

    .line 33
    :cond_0
    const-string v8, "NOPE"

    return-object v8
.end method

Bon alors ça fait un peu de code à reverse mais on s'en sort facilement. J'ai flag à la main, mais maintenant que j'ai le temps j'ai réécris le code en Python, le code est plus bas, je vais d'abord expliquer ce qu'il se passe.
Pour simplifier un peu on comprend qu'une array est initialisée avec des strings à l'intérieur, ensuite des opérations se passent afin d'afficher certains éléments de l'array séparés par des points.
Mon script de résolution :

#array de strings
whitches = ["weatherwax", "ogg", "garlick", "nitt", "aching", "dismass"]

flag = ""

v1 = 3
v2 = 0
v3 = 1
v3 = v3 + v2 #opération inutile
v4 = 2
v4 = v4 - v2 #opération inutile
v5 = 5
v6 = 5
v6 = v6 - v3
v7 = whitches[v5]
v8 = "."
flag = v7 + v8
v9 = whitches[v3]
flag = flag + v9 + v8
v9 = whitches[v2]
flag = flag + v9 + v8
v9 = whitches[v6]
flag = flag + v9 + v8
v9 = whitches[v1]
flag = flag + v9 + v8
v8 = whitches[v4]
flag = flag + v8
print(flag)
#output : dismass.ogg.weatherwax.aching.nitt.garlick

Ce mot de passe permet d'obtenir le flag :

flag : picoCTF{what.is.your.favourite.colour}

droids3

Pour ce challenge il va falloir innover un peu, après analyse du code de FlagstaffHill.smali on comprend que la fonction qui donne le flag n'est jamais appellée dans le code. 2 solutions s'ouvrent à nous : modifier le code smali, recompiller l'apk et la signer pour la lancer sur le terminal ; ou utiliser un hooker comme frida. Je vais ici utiliser frida sur un terminal rooté.
La fonction nope() est toujours appellée, nous voullons appeler yep() pour avoir le flag. Voici mon script pour frida :

Java.perform(function(){
    const FlagHill = Java.use("com.hellocmu.picoctf.FlagstafHill")

    //on modifie le comportement de getFlag pour appeler yep()
    FlagHill.getFlag.implementation = function(){
        return FlagHill.yep()
    }
})

Setup du terminal :

#on installe le serveur frida sur le terminal
$ frida-push
...
#on lance le script de hook sur l'application
$ frida -U -l script.js -f com.hellocmu.picoctf
flag : picoCTF{tis.but.a.scratch}

droids4

Comme juste avant, j'ai utilisé frida pour appeler la fonction cardamom de FlagstaffHill, qui nous donne le flag :

Java.perform(function(){
    const FlagHill = Java.use("com.hellocmu.picoctf.FlagstafHill")

    //on modifie le comportement de getFlag pour appeler yep()
    FlagHill.getFlag.implementation = function(){
        return FlagHill.cardamom()
    }
})
flag : picoCTF{not.particularly.silly}

Web

Open to admins

Sur ce site web on doit trouver le moyen de nous faire passer pour l'admin et on a une variable de temps à gérer. Après plusieurs minutes à essayer des choses un peu à tatons à vouloir modifier la date de ma requête, il suffit simplement d'utiliser un cookie (pas trop guessing puisque dans l'énnoncé admin et time sont tous les deux en gras, ce qui nous met sur la piste d'utilser la même technique pour les 2).
En javascript : document.cookie="admin=true;time=1400". On clique sur le bouton flag.

flag : picoCTF{0p3n_t0_adm1n5_dcb566bb}

picobrowser

L'énnoncé nous met sur la piste de l'user-agent. Sur le site en cliquant sur le bouton flag, on a un message d'erreur : " You're not picobrowser! [notre user-agent]".

On modifie alors l'user-agent pour picobrowser :

flag : picoCTF{p1c0_s3cr3t_ag3nt_55e7edbc}

Empire1

L'injection SQLite3 qui m'a prit le plus la tête. Il fallait d'abord trouver un moyen d'injecter du code (ici, avec une simple quote ça passe car on a une 500 du serveur avec), il faut ensuite trouver le type de base de données, en testant quelques payloads de chaque type de base de données, on trouve '|(select 69)|' qui affiche bien le fameux chiffre dans la liste des todos. Il s'agit d'une base SQLite.
Une fois que le type est trouvé, j'ai cherché le nom des tables, des colonnes, j'ai trouvé la table user avec les colonnes id, name, secret.
Exploitation : il faut trouver le secret de notre utilisateur. si mon id est 3, la payload suivante m'a donné mon secret : '|(select hex(secret) from user where id=3)|' -> 7069636F4354467B77683030745F69745F615F73716C5F696E6A65637464373565626666347D -> picoCTF{wh00t_it_a_sql_injectd75ebff4}

flag : picoCTF{wh00t_it_a_sql_injectd75ebff4}

Empire2

On doit trouver un secret qui nous est attribué lors de notre connexion. Je suis longtemps resté sur une injection SQLite, après m'être souvenu d'avoir tenté en premier lieu une SSTI sur le challenge précédent, j'essaie ici : {{config}}, bingo. Il s'agit ici d'un flag troll, en reprenant l'énnoncé, j'ai tenté d'utilisé cette payload : {{session}}.

flag : picoCTF{its_a_me_your_flag0994faa6}

Empire3

Empire3 est toujours une SSTI, un peu plus subtile que la précédente mais tout aussi cool à exploiter. Avec la payload {{url_for.__globals__}} on obtient des infos sur notre session, mais pas de flag en vue.
Le problème avec ce chall c'est que si la payload donne une erreur on doit changer de compte, c'est un peu dommage mais bon.

Exécuter du code sur le serveur

Il y a plein de payloads qui permettent de faire ça sur internet, j'ai voulu utiliser la classe popen au lieu de warningMessages car elle est plus facile d'utilisation dans une payload.{{''.__class__.__mro()[1].__subclasses__()[287]('ls', shell=True, stdout=-1).communicate()[0].strip()}} permet de lister les fichiers sur le serveur. Pour gagner du temps j'ai grep en cherchant le flag dans les fichiers : {{''.__class__.__mro()[1].__subclasses__()[287]('grep -ir "pico"', shell=True, stdout=-1).communicate()[0].strip()}}

flag : picoCTF{cookies_are_a_sometimes_food_49392002}

JaWT Scratchpad

Comme son nom l'indique on va jouer avec les tokens JWT. Sur le site on se connecte avec un nom. On obtient alors un cookie "jwt" qui contient un token JWT.

Sur le site on voit une référence à Jonh the ripper pour trouver des mots de passe, je décide d'utiliser le très bon outil jwt_tool avec rockyou :

$ python2 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoibmluamEifQ.Sbj-RAjlnH6iEAvOm0MEvLtC3ee5NrTSKlUKyIo_oT8 rockyou.txt

Token header values:
[+] typ = JWT
[+] alg = HS256

Token payload values:
[+] user = ninja

######################################################
# Options: 
# 1: Check CVE-2015-2951 - alg=None vulnerability
# 2: Check for Public Key bypass in RSA mode
# 3: Check signature against a key
# 4: Check signature against a key file ("kid")
# 5: Crack signature with supplied dictionary file 
# 6: Tamper with payload data (key required to sign) 
# 0: Quit
######################################################

Please make a selection (1-6)
> 5

Loading key dictionary...
File loaded: /home/ninja/ctf/rockyou.txt
Testing 14344380 passwords...
[+] ilovepico is the CORRECT key!

On a trouvé la clé pour la signature du token. Sur jwt.io on reprend notre token, en modifiant la valeur de user par "admin", on entre la clé pour la signature, on obtient le token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.gtqDl4jVDvNbEe_JYEZTN19Vx6X9NNZtRVbKPBkhO-s, on l'utilise dans le cookie, et on actualise la page. On est connecté en tant qu'admin et on a le flag.

flag : picoCTF{jawt_was_just_what_you_thought_be9ef99e529597da0f3543893357908b}

cereal hacker 1

Comme son nom l'indique, on va toucher à des objets sérialisés. Ce challenge m'a prit plusieurs heures avant de le flag.

On arrive sur le site, une page de connexion est là. J'ai bloqué quelques temps avant d'avancer, j'ai d'abord essayé d'aller sur la page /admin.php, on a un message qui nous dit que nous ne sommes pas admin (normal) et un bouton pour retourner à l'index. Ce bouton reset un cookie "user_info" au clique. Une piste que j'ai tout de suite prise.
Sans avoir plus d'infos j'ai tenté de trouver ce qu'il fallait mettre dans ce cookie, sans aucune piste correcte. [ellipse de 2 jours] Sur la page de login, on teste des identifiants à la con : admin:admin, user:password, test:test ..., sans succès, sauf pour le couple guest:guest qui nous connecte ! Sursaut de joie on a maintenant un cookie valide, de la base64 contenant un objet PHP sérialisé : O:11:"permissions":2:{s:8:"username";s:5:"guest";s:8:"password";s:5:"guest";}, avec un objet comme ça, normal qu'on ne pouvait pas le guess.

Après avoir cherché longtemps avant de comprendre comment en terminer, j'ai tenté pleins de choses avec cet objet (mais c'est un secret entre lui et moi). J'ai tenté une injection SQL dans ce dernier : O:11:"permissions":2:{s:8:"username";s:33:"admin' and password like '%';-- -";s:8:"password";s:5:"guest";}, qui me connecte en tant qu'admin.

flag : picoCTF{41f9ada4385bd93a3b15eead30841230}

Irish-Name-Repo 1

Sur le site, on trouve une page de login, premier réflexe : injection SQL avec admin' and 1=1;-- -.

flag : picoCTF{s0m3_SQL_6b96db35}