欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DDCTF部分WP

程序员文章站 2022-05-12 13:40:01
...

这周去打了DDCTF,花了一周做出了九道题,完成了1000分的梦想。也算是菜鸡打比赛这么久拿到分数最多的一次了,现记录如下:

MISC

1、(╯°□°)╯︵ ┻━┻

(╯°□°)╯︵ ┻━┻
d4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9e1b2e2e5e2b5b4e4b8b7e6e1e1b6b9e4b5e3b8b1b1e3e5b5b6b4b1b0e4e6b2fd

刚开始拿到这道题的时候以为是base64,因为base64是可以解出来的,解出base64后以为是栅栏密码,解了半天无效,卡在这里。
之后观察字符特点发现字母最大没有超过f的,应该是16进制,可是转换成ascii还是乱码,又试过把整个文本倒序输出(因为题目颜文字名称为flipping table),依然无效。
之后尝试尝试各种方法,并在Wechall里面发现了类似的题目(Training: Crypto - Caesar II),还是做题少啊。。。。
其实题目就是一个16位表示的移位密码,写脚本跑flag:

s="d4 e8 e1 f4 a0 f7 e1 f3 a0 e6 e1 f3 f4 a1 a0 d4 e8 e5 a0 e6 ec e1 e7 a0 e9 f3 ba a0 c4 c4 c3 d4 c6 fb b7 b9 b8 e4 b5 b5 e4 e2 b7 b6 b5 b5 b2 e1 b9 b2 b2 e4 b0 b0 e4 b7 b7 b5 e5 b3 b3 b1 b1 b9 b0 b7 fd"
s=s.split()
for key in range(0,128+1,1):
    for i in s:
        i = int(i,16)
        print (chr((i + key + 256) % 256 ), end = '')
    print (key)

当跑到128的时候看到了flag

2、第四扩展FS

D公司正在调查一起内部数据泄露事件,锁定嫌疑人小明,取证人员从小明手机中获取了一张图片引起了怀疑。这是一道送分题,提示已经在题目里,日常违规审计中频次有时候非常重要。

题目附件是一个windows.jpg文件,binwalk发现内含有zip文件,使用foremost分离得到一个zip文件
zip文件经过加密,检查过发现并不是伪加密,于是有一次卡在了密码上。。。。。。
后来群里有人提示那张jpg文件里面有东西,查了半天隐写最后竟然发现密码是在属性->详细信息->备注里!!!
后来想想其实这样是比较符合实际情况的,很多人是会把不常用的密码写到文件的备注里,可以看出DD的这次赛题画风比较贴近实际了。
打开zip文件得到了一个file.txt,考虑到题目最后一句话中提到的频次,写个脚本来统计文件中字母出现的频次:

s=open('file.txt','r').read()
A="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789!{}"
for a in A:
    times=s.count(a)
    print (times,a)

然后很惊喜的发现有些字母是没有出现过的,而有些字母出现了一千多次,在这儿突然开个脑洞把出现的字母根据频次大小排序,于是很开心的得到了flag
(这道题属于想打死出题人系列,因为题目的解题思路和第四扩展FS没有任何一点关系,看到群里不少的人被题目名称给误导了,还好我机智!)

3、流量分析

提示一:若感觉在中间某个容易出错的步骤,若有需要检验是否正确时,可以比较MD5: 90c490781f9c320cd1ba671fcb112d1c
提示二:注意补齐私钥格式
—–BEGIN RSA PRIVATE KEY—–
XXXXXXX
—–END RSA PRIVATE KEY—–

这是我打比赛过程中第一次做流量分析题(以前遇到不是推给队友就是直接放弃)
首先打开协议分级发现有93.5%的流量都是FTPdata,通过ftp传送了文件
DDCTF部分WP

查看ftp流量,发现传送了Fl_g.zip和sqlmap.zip两个文件,但却伴随着大量的数据包丢失
DDCTF部分WP
DDCTF部分WP
考虑是否有办法恢复这些丢失的报文,但看了好久发现并没有重发的报文,因此这条路可以宣告gg了
再去看看协议分级发现里面还有smtp流量,该流量是收到的邮件信息,去看看这些报文,在一封邮件里发现了一个base64加密的图片,解码后根据题目的提示二猜到应该是一个RSA私钥文件,同时发现流量包里还有HTTPS流量,因此将私钥文件导入解密https流量的到flag.
(最后导入证书这一步卡了很久,最后比对分析是自己证书多了几个回车格式上的错误,可以说是很生气了)

4、安全通信

这道题目拿到题是想放弃的,因为对aes了解的比较少。
看完代码搞清了程序流程:首先输入你的mission key(系统根据mission key生成你的flag),然后输入agent id(随意输),程序会随机生成key并根据你的agent id 和flag对字符串Connection for mission: {agent id}, your mission’s flag is: {flag}进行加密并输出加密结果。然后程序允许用户自己定义字符串并用相同的key进行加密并输出,加密方式使用了ecb模式。
放弃之前顺手在google查了一下aes ecb attack,发现由于ecb模式是直接对明文进行分块加密,因此存在明文攻击的可能性,具体参考:Attacking ECB

ecb模式攻击原理:

DDCTF部分WP
ecb模式使用相同的key分块对明文分别进行加密,相同的明文获得相同的密文输出
根据这一特性,可以构造如下数据进行攻击:
首先输入blocksize-1的填充,这样未知字符串的第一个字符将落到填充块的最后一个字节:
DDCTF部分WP
这时会得到填充块的一个密文输出,**最后一个字节,直到产生与刚才相同的密文输出,就可以确定未知字符串的一个字节,重复这个过程就可以得到完整的未知字符串。

攻击条件

要能够像包含未知字符串的明文中插入数据(本题满足条件)
exp:

#!/usr/bin/env python
# coding=utf-8

from pwn import *
found=False
number=15
#count=1
flag=""
message="Connection for mission: , your mission's flag is: DDCTF{7ec4e929f4b93b6fbd3506dd"
while(number>=0):
    p=remote('116.85.48.103',5002)
    p.recvline()
    p.sendline("570ddb32ebcb382bdd0a62f437c4f769")
    p.recvline()
    p.sendline("1"*number)
    initialdata=p.recvline()
    blockdata=initialdata[144:175]
    #print(initialdata)
    #print("----------")
    array="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789{}"
    count=1
    for i in array:
        p.recvline()
        print count
        count=count+1
        message="Connection for mission: "+"1"*number+", your mission's flag is: DDCTF{7ec4e929f4b93b6fbd3506dd"+flag+i
        #print(message)
        p.sendline(message)
        data=p.recv()
        #print data
        if data[144:175]==blockdata:
            found = True
            print i
            flag=flag+i
            number=number-1
            if(i=="}"):
                number=-1
            #message=message+i
            break
            p.close()
print(flag)

每次只能爆16位,这是爆flag第三部分的代码,前面两部分需要稍微修改代码。

Android

1、RSA

先用jadx查看android的java代码,确定使用libhello-libs.so中的函数StringFromJNI判断输入是否正确。使用IDA加载此so文件,找到对应的函数Java_com_didictf_guesskey2018one_
MainActivity_stringFromJNI
sub_31364函数以输入为参数进行了某些处理,根据函数流程以及“basic_string::_S_construct null not valid”字符串基本确定次函数功能类似于stl string的初始化,将char*转化为string。string在内存中的结构为

DDCTF部分WP
其中ref表示指向当前string的指针个数,当ref<0时表示此string不允许使用指针,只允许复制。
Sub_31364函数功能为createstring,下面的sub_30A08函数为copy,j_j_j__Z20__aeabi_wind_cpp_prjSs基本可以确定为关键函数。下面分析此函数。
函数前半段为对输入进行处理,例如判断输入长度是否为43,并且将输入与byte_4DECB中的字符串进行亦或,得到的结果前十位转化为long作为除数。
后面是对两串固定字符进行处理,得到被除数。j_j_j__ZNSt3mapIciSt4lessIcESaISt4p
airIKciEEEixERS3_函数经过demangle得到

j_j_j_std::map <char, int, std::less, std::allocator<std::pair<char const, int> > >::operator[](char const&)

也就是相当于stl map的[]操作。所以使用python脚本可以得到被除数。

map1 = {}
str1 = 'deknmgqipbjthfasolrc'
for i in range(len(str1)):
    map1[str1[i]] = i/2
str2 = 'jlocpnmbmbhikcjgrla'
k = []
for i in range(len(str2)):
print map1[str2[i]],

最后是对结果的判断,首先判断余数是否为0,以及结果和被除数的大小。可以从0~√n 依次判断是否可以整除,最后得到一组数据1499419583,3927794789。根据判断条件得出被除数是1499419583,根据结果倒推可以得到flag。

2、Hello Baby Dex

Hook libdvm.so中的 openDexFileNative函数,打开apk可以得到patch_temp.jar。用jadx打开,根据流程可知是一个class patch,使用joseph算法获得最后的key,使用python脚本获得flag。

def joseph2(strt, dis):
    obj = [2131165184, 2131165213, 2131165215, 2131165216, 2131165222, 2131165221, 2131165233, 2131165311, 2131165185,
           2131165255, 2131165261, 2131165242, 2131165245]
    i2 = strt
    i = 0
    while 1:
        if len(obj) <= 1:
            break
        i = i2 + dis - 1
        i2 = i % len(obj)
        tmp = obj[i2]
        obj.remove(tmp)
    return str((obj[0] >> 5) & (obj[0] << 6))
print 'DDCTF{'+joseph2(5,6)+joseph2(7,8)+'}'

3、Diffie-Hellman

Apk的java部分与1相同,分析Java_com_didictf_guesskey2018two_MainActivity_
stringFromJNI函数的逻辑,可得逻辑为:
已知p和result,以及迭代函数a = f(a) % p,求经过多少次迭代后a等于result。所以代码为:

#include <stdio.h>
int main(int argc, char const *argv[]) {
    unsigned long p = 0xB49487B06AA40;
    unsigned long result = 0x1D026744B3680;
    unsigned long tmp = 0;
    unsigned long v12 = 2;
    unsigned long i = 0;
    for(i = 0;i < 0xffffffff; i ++)
    {
        //printf("i is %ld\n", i);
        *(((unsigned int *)&tmp) + 1) = v12 >> 31;
        *((unsigned int *)&tmp) = v12 * 2;
        v12 = tmp % p;
        if(v12 == result)
        {printf("has found, i is %ld\n", i);}
    }
    return 0;
}