CTF的一些做题记录

RE-dice

主要就是扔骰子游戏,要扔3-1-3-3-7

根据字符串提示找到最后会输出的flag的位置,我这里是v84,按alt+t搜索文本v84,再按ctrl+t搜索下一个

flag =v84^v87

发现v84一开始被byte_xxx赋值了,找到数据,选中全部按shift+E,初始值保存十六进制数据到文本

132138153D3357472D276A73440526595C79174445771A75497D054A78746A70420271050F220800

然后跟进到第二个对v84操作处

img1

又要开始跟进v87了…

它也被byte_xxx赋初始值,提取数据02370F350F3C15073C302A30551237151E35015100

跟进发现v87和v86异或了

2

再跟进,发现v87又和v85异或了

3

终于就结束了

最后flag=v84^v87^v86^v85

那么我们先关注v86

我终于不用再qq截图了… cmd+shift+4 截图,选中文件return重命名

4

v86被赋初值16,其他地方没有改动了

再关注v85,初值为6

5

注意这里的time函数,也是反调试用的,比如od调试时time就会比较大,这里可以理解为程序运行时间超过2秒就乘以2

所以这里我们只需要一次v85*=2即可

接着根据31337的五次结果,总结一下所有操作就是

v85=6 *=2 +=4 *=3 +=2 *=2 *=50 /=50 +=65 -=65 *=42 /=42

算出v85=100

最后写出解密代码(还是C好用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v84 = "132138153D3357472D276A73440526595C79174445771A75497D054A78746A70420271050F2208"
v87 = "02370F350F3C15073C302A30551237151E350151"
v86 = 16
v85 = 100

v87 = [ord(i) for i in v87.decode('hex')]
for i in xrange(len(v87)):
v87[i]^=v85^v86

v84 = [ord(i) for i in v84.decode('hex')]

res = ""
for i in xrange(len(v84)):
res += chr(v84[i]^v87[i%len(v87)])

print res

补充一些,IDA的流程图…….是可以拖动的

6

举个例子,比如我要把jnz改成jz,以机器码修改的方式

7

光标处改成8就差不多了,应该是机器码显示的长度(而不是进制,因为我试了15

8

网上查一下发现JNZ就是这里的75,而JZ是74

patch一下

9

这样1/6的概率就变成5/6了,或者直接改成jmp更好

RE-catalyst

catalyst是催化剂的意思,应该就是让我们nop掉一堆的sleep吧

直接找到关键部分

10

跟进第一个函数

11

12

显然这里是对username的长度进行了限制,复制代码,爆破一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<stdlib.h>

int main(int argc,char**argv){
int i=0,result;
for(i = 0;i<100;i++){
if ( 4 * (i >> 2) != i || 4 * (i >> 4) == i >> 2 || (result = (unsigned int)(i >> 3), !result) || i >> 4 ){
// do nothing
}
else{
printf("%d\n",i);
}
}

return 0;
}

结果是username长度只能是8或12,是第一个限制

4 * (i >> 2) == i 实际表示i是4的倍数,懒得分析了直接爆破吧

进入第二个函数,就是一堆运算

13

写一个z3的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from z3 import *
inp = [BitVec('a%d' %i, 56) for i in range(3)]
sol_1 = inp[0] - inp[1] + inp[2]
sol_2 = inp[1] +3 *(inp[2]+inp[0])
sol_3 = inp[2]*inp[1]

s = Solver()
s.add(sol_1==1550207830)
s.add(sol_2==12465522610)
s.add(sol_3==3651346623716053780)
print s.check()
m = s.model()
r = []
for i in inp:
print m[i]

输出结果:

14

然后把长整型转换成字符串,to_bytes需要py3支持

1
2
3
4
5
userin = [1635017059,1953724780,1868915551]
username = ''
for i in userin:
username+=i.to_bytes(4,'little').decode('utf-8')
print(username)

每四个字节转成一个小端字符,再UTF-8解码

输出: catalyst_ceo

跟进第三个函数,发现没什么影响

15

继续第四个函数

16

这个没什么用…

17

这里主要是随机种子已经定下了,因此我们写另一个程序运行一下就好

第五个函数就是简单的异或

最终的exp:

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char xor_bytes[]={
66, 19, 39, 98, 65, 53, 107, 15, 123, 70, 60, 62, 103,
12, 8, 89, 68, 114, 54, 5, 15, 21, 84, 67, 56, 23, 29,
24, 8, 14, 92, 49, 33, 22, 2, 9, 24, 20, 84, 89
};

char*username = "catalyst_ceo";

int main(int argc,char**argv){
int a[10];
int*ptr = (int*)username;
srand(ptr[0]+ptr[1]+ptr[2]);
a[0] = rand()+0x55EB052A;
a[1] = rand()+0xEF76C39;
a[2] = rand()+0xCC1E2D64;
a[3] = rand()+0xC7B6C6F5;
a[4] = rand()+0x26941BFA;
a[5] = rand()+0x260CF0F3;
a[6] = rand()+0x10D4CAEF;
a[7] = rand()+0xC666E824;
a[8] = rand()+0xFC89459C;
a[9] = rand()+0x2413073A;

char*flag = (char*)a;
for(int i = 0;i<40;i++){
printf("%c",flag[i]^xor_bytes[i]);
}
//ALEXCTF{1_t41d_y0u_y0u_ar3__gr34t__reverser__s33}
}

好多wp说这里a[]要设置成unsigned int,我感觉没道理啊…这里我跑出来没问题

也可以写个程序看看猜测的对不对,这里是因为前面太多sleep,懒得调试

不过以后如果出了问题可以考虑一下有/无符号的问题

我可以清楚地控制C中的每一个bit,为什么要用python呢?

不完全归纳的结论:

int + 32bits,不管后面的32bits是int还是uint,都等于int + 32bits(补),貌似需要加一个前提(32bits)没超过int的上界

也就是说,后面的32bits直接看成int就行了,不需要纠结有无符号