本文为之前参加过的ctf比赛的writeup,迁移到这个博客来吧,两道简答题,重在参与

RE

EXCELlent

  • 艹,这个z3有毒,怎么排列不按名字来的;
  • 下载下来是个excel,界面输入一个类似序列号的东西,后面第三行会动态地根据序列号进行变化,然后第二行看到了会根据输入的不同改变颜色,并且一开始什么都没有输的时候就有一格是绿色,如果不知道可能会以为这是装饰;
  • 第一感觉是用公式弄的,但是首先选中输入框,发现没有公式(也很正常,毕竟是输入),然后在变化的输出框发现有一个拼接字符串的公式CONCATENATE,发现后面应该是跟着另外的sheet的名字,然后跟着行和列信息,eg.Tablele4!D4;
  • 右键下面唯一的sheet,出现了取消隐藏,共有其他四个表,发现后三个表东西很多,搜了一下怎么跟踪公式:
操作 作用
公式->追踪引用单元格 可以把这个公式依赖的格子给显示一下,但是实际上还没有把光标放到公式去上色好用,不过在看对应哪个格子还是有点用的
公式->追踪从属单元格 看看这个格子被哪些其他格子使用
  • 对然后就是发现结果的字符串是由后面三个表生成的,根本搞不懂是什么加密;
  • 回想起那个绿条,发现这个绿条应该是类似提示的东西,可能全绿了表示输入正确,然后后面三个表相当于是一个解密器,解密器干了啥其实不重要,只要关心输入对不对就成;
  • 搜了下怎么自动变颜色,发现是开始->条件格式中可以设置规则,发现绿条的规则是和第一个隐藏表绑定的,回到第一个表发现是对输入进行了某种验证,那这样就开始了z3约束求解之旅;
  • 有一个要注意的地方是发现有一个格子有两个条件约束,但是约束不可能同时满足两个,所以有一个是错误的约束;
  • 约束应该没什么好说的,求到倒数第二个的时候就有答案了,可能以后应该从简单的约束开始写可以比较快一点出答案也说不定;
  • 哦还有一个排列不按顺序来的,反正按着数组的顺序输出就行了,至于为什么不按顺序来就真的搞不懂orz;
  • hxp{excellence_c0mes_fr0m_excel}

Hitcon 2020

RE

1101

  • 运行的时候报libcrypto.so.1.1库找不到,找了一些解决方法,终于先解决了一波;
  • ldd 文件名看链接,发现就是报错中的libcrypto.so.1.1找不到,搜了一下要直接装这个库的话要把一个库的信息rpm加到/etc/apt/sources.list里面,但是ubuntu16没有装rpm,所以用不了;
  • 于是看到有人是去升级openssl到1.1.1,所以先下载然后升级:
1
2
3
4
5
6
wget https://www.openssl.org/source/openssl-1.1.0g.tar.gz
tar xzvf openssl-1.1.0g.tar.gz
cd openssl-1.1.0g
./config -Wl,--enable-new-dtags,-rpath,'$(LIBRPATH)'
sudo make
sudo make install
  • 然后就是链接改一下,如果存在就rm掉:
1
2
sudo ln -s /usr/local/lib/libssl.so.1.1 /usr/lib/libssl.so.1.1
sudo ln -s /usr/local/lib/libcrypto.so.1.1 /usr/lib/libcrypto.so.1.1
  • 第一关是把输入&一个数,应该是用来限制输入的范围,后面爆破应该需要用到;

  • 看来以后这种题应该就是构造z3约束的题了,看了ctftime上的两个wp,perfectblue的有点看不懂,对比两个wp学一下z3怎么写的;

  • 首先就是我自己也能看出来的提供一个mask然后约束:
1
2
3
4
5
6
# wp1
inputBuffer = [BitVec("inp_"+str(i), 32) for i in range(20)]
for index in range(20):
s.add((compareData[2 * index] & inputBuffer[index]) == compareData[2 * index + 1])

# perfectblue,感觉他搞复杂了,不写他的了
  • 初始化学着点,然后这个比较也没什么好说,比较朴素;
  • 第二个是个移位,看起来也比较简单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for index in range(20):
for shift in range(18):
s.add(((inputBuffer[index] >> shift) & 7) != 7)
s.add(((inputBuffer[index] >> shift) & 7) != 0)



for shiftIndex in range(20):
orValue = 0
for index in range(20):
shiftedValue = inputBuffer[index] >> shiftIndex;
orValue = ((shiftedValue & 1) << index) | orValue;

for shift in range(18):
s.add(((orValue >> shift) & 7) != 7)
s.add(((orValue >> shift) & 7) != 0)
  • perfectblue的看不懂啊,好像是扩展成了bit的操作,跳过了跳过了;
  • 第三个也简单,合到上面去了;
  • 第四个看懂了,其实就是统计1的数量为10:
1
2
3
4
5
def popcount(bvec): 
return Sum([ ZeroExt(int(math.ceil(math.log(bvec.size(), 2.0))), Extract(i,i,bvec)) for i in range(bvec.size())])

for index in range(20):
s.add(popcount(inputBuffer[index]) == 10)
  • 解释下上面几个z3的api干了什么,Extract从目标bitvec里面拿出指定的bits,注意第一个参数是高位,并且应该是左右都是闭合区间;ZeroExt表示往高位补0,第一个参数就是补多少个0,那么为什么要这么写呢,因为log之后如果所有位都是1的话这样加起来也不会超过最大值;

  • 后面好像都差不多了,这一个是判断全不等的api:Distinct:

1
s.add(Distinct(inputBuffer))

上海大学生 2020

RE

真正的RE

  • 程序里有很多nop,有点奇怪,但是不影响IDA反编译;
  • 调整了最后的堆栈,让F5能用;
  • 这里貌似是反调试,检查这里的时钟周期是否在一个很短的时间区间内,有两个方法,要么每次运行到这里直接跳到下一句,要么就patch掉;
    1
    2
    3
    4
    5
    6
    7
    BYTE1(v15) &= v15;
    v4 = __rdtsc();
    v12 = v4;
    v5 = __rdtsc();
    v11 = v5;
    if ( (unsigned int)(v5 - v12) >= 0xFF )
    exit(0);
  • 跳到下一句是可以的;咦第二次来的时候就跳不住了,那还是先patch一下;
  • 看起来是对输入进行操作然后和一个固定的数组比较了;
  • 感觉是一个生成器把控制流给生成出来,然后对输入进行加密;
  • 有一个常数组的密钥;
  • 猜一下他是可逆的,输入结果试一下,并不是直接还原;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    unsigned char target[] =
    {
    0x1F, 0x18, 0x0F, 0xFA, 0xB8, 0x63, 0x64, 0x89, 0x18, 0x68,
    0x7C, 0x19, 0x14, 0x2D, 0x7D, 0x58, 0x00, 0x1E, 0x54, 0x6A,
    0x41, 0x3C, 0x36, 0x3E, 0x56, 0x13, 0x04, 0x3B, 0x2E, 0x4B,
    0x79, 0x43, 0x7A, 0x22, 0x45, 0x6E, 0x3A, 0x75, 0x26, 0xC8,
    0xC0, 0x8E
    };

    unsigned char ida_chars[] =
    {
    0x12, 0x3F, 0x2E, 0xD6, 0xD4, 0x39, 0x3A, 0x3A, 0x32, 0x32,
    0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
    0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
    0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E
    };

    unsigned char ida_chars[] =
    {
    0x12, 0x01, 0x7C, 0x60, 0xD6, 0xD5, 0x3A, 0x3A, 0x32, 0x32,
    0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
    0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
    0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E,
    0xF9, 0xF7
    };

    //1
    unsigned char ida_chars[] =
    {
    0x31, 0xDF, 0xCF, 0x3C, 0x38, 0x39, 0x3A, 0x3A, 0x32, 0x32,
    0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
    0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
    0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E,
    0xC1, 0xDC
    };

    //2
    unsigned char ida_chars[] =
    {
    0x32, 0xDF, 0xCF, 0x3C, 0x38, 0x39, 0x3A, 0x3A, 0x32, 0x32,
    0x3C, 0x3C, 0x3A, 0x33, 0x3E, 0x26, 0x09, 0x3D, 0x3F, 0x2B,
    0x1E, 0x3A, 0x0A, 0x3E, 0x04, 0x39, 0x36, 0x29, 0x15, 0x3B,
    0x38, 0x00, 0x32, 0x02, 0x34, 0x04, 0x3D, 0x36, 0x0F, 0x3E,
    0xC0, 0xDE
    };

    //11
    unsigned char ida_chars[] =
    {
    0x21, 0x50, 0xD9, 0xD0, 0x38, 0x39, 0x3A, 0x3A, 0x32, 0x32,
    0x3C
    };

    //111
    unsigned char ida_chars[] =
    {
    0x1A, 0x38, 0x2C, 0xD6, 0xD4, 0x39, 0x3A, 0x3A, 0x32, 0x32,
    0x3C, 0x3C, 0x3A, 0x33, 0x3E
    };

    //1111
    unsigned char ida_chars[] =
    {
    0x1A, 0x03, 0x26, 0x2D, 0xD6, 0xD5, 0x3A, 0x3A, 0x32, 0x32,
    0x3C, 0x3C, 0x3A
    };

    //22
  • 看起来输入会循环影响后面的四个byte,但是前两个应该可以定下来
  • 三个DWORD为一组,第一个DWORD中的1的位置决定操作;
  • 根据第一个DWORD有两种情况,交叉着来,有一个会取一个输入继续重复的异或,有一个只会取两个数然后异或直接结束;
  • 明白了,看来是个完全无冲突的变换;
  • 随便搞个脚本就完事了,还是太弱了,不够大胆,这种题应该搞快点;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    optflow=[] # 原始数据流就不贴了
    def testloop():
    allmap=dict()
    for i in range(2,15):
    coset=dict()
    for j in range(0,0x100):
    a=i
    b=j
    while a:
    x=a^b
    a=a&b
    a=(a<<1)&0xff
    b=x
    if b in coset.keys():
    print('error')
    print(i,j)
    exit()
    else:
    coset[b]=j
    # print(1)
    allmap[i]=coset
    return allmap

    import struct
    def flowgen2():
    global optflow
    optlist=list()
    for i in range(0,len(optflow),12):
    curpp,firstind,secondind=struct.unpack('<III',bytes(optflow[i:i+12]))
    optlist.append((curpp,firstind,secondind))
    return optlist[:-1]

    target=[0x1F, 0x18, 0x0F, 0xFA, 0xB8, 0x63, 0x64, 0x89, 0x18, 0x68,
    0x7C, 0x19, 0x14, 0x2D, 0x7D, 0x58, 0x00, 0x1E, 0x54, 0x6A,
    0x41, 0x3C, 0x36, 0x3E, 0x56, 0x13, 0x04, 0x3B, 0x2E, 0x4B,
    0x79, 0x43, 0x7A, 0x22, 0x45, 0x6E, 0x3A, 0x75, 0x26, 0xC8,
    0xC0, 0x8E]

    def reback():
    global target
    optlist=flowgen2()
    maplist=testloop()
    for opt in optlist[::-1]:
    op,fi,ni=opt
    if op %2:
    target[fi%64]=maplist[ni][target[fi%64]]
    else:
    target[fi%64]=target[fi%64]^target[ni%64]
    for i in target:
    print(chr(i),end='')

    if __name__=='__main__':
    reback()