damCTF- rev/schlage (beginner) __WP

damCTF- rev/schlage (beginner) __WP

  1. 先用file命令查看

    0Rkyfe.jpg 可以看到是64位的ELF文件,debug信息还在
  2. 接着使用radare2反汇编

    0RE8xJ.jpg

使用VV命令进入视图模式


我们的任务是解锁,这里又5把锁
0RVJfS.jpg


关键的代码是这5个函数
0RVIk6.jpg


Pin1

第三把锁比较简单,来看第一把锁
0RZxKJ.jpg

也可以在视图模式按o再输入函数旁边的字母
0Rnph6.jpg

这是一个循环操作,共五次,每一次将两个数字异或,最后和0xee比较,相同则成功
rbp+rax-0x0e即指向开头移入地址的5个数

0x3e xor 0x57 xor 0x81 xor 0xd3 xor 0x 25 xor 0x 93 xor 0xee 结果为0x99

jle:

JNB al, bl ;al里的内容不等于bl时跳转
JBE al, bl ;al里的内容小于或等于bl时跳转

;同理,JGB是大于或等于,JLE是小于或等于
;A(above)大于,B(below)小于,E(equal)等于,用于比较无符号数
;G(great)大于,L(less than)小于, E(equal)等于,用于比较带符号数


pin5

0RuadA.jpg #### srand() 第5把锁用到了`srand()`和`rand()`两个函数。

根据种子产生随机数

  1. 给srand()提供一个种子,它是一个unsigned int类型;
  2. 调用rand(),它会根据提供给srand()的种子值返回一个随机数(在0到RAND_MAX之间);

也就是,一个种子,它所产生的随机数是固定的。

1
2
3
unsigned int a=1111638594//0x42424242
srand(a);
cout<<rand()<<endl;

输入rand()生成的随机数即可,即为1413036362

lea

lea是“load effective address”的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数,例如:lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里数据赋给eax。而mov指令则恰恰相反,例如:mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。


pin2

0RKg1K.jpg 查看汇编代码可以知道,pin2根据实际输出了一个种子,根据种子调用`rand() `即可。

这个种子下一把锁还要用到,再一次调用rand(),获得的是第二个随机数。


pin4

我认为这是最难得了,主要是编程。
直接看r2 太复杂,所以我用IDA PRO,然后F5反编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
puts("What's your favorite sentence?");
fgets(s, 32, stdin);
s[strcspn(s, "\n")] = 0; //换行符变为0x00
v3 = rand() % 10 + 65;
v1 = 0;
v4 = strlen(s); // strlen 遇到/0结束
for ( i = 0; i < v4; ++i )
v1 += v3 ^ s[i]; //将随机值与字符异或,结果相加要为291
if ( v1 == 291 )
{
puts("Such a cool sentence!");
byte_20203C = 1;
}
else
{
puts("Not a big fan of that sentence");
}

注释已经写在代码中,接下来,就是编程破解得到符合的字符串。

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
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

int main()
{
/*FILE *pe=NULL;
char s[1]={'1'};
pe=popen("nc chals.damctf.xyz 31932","rw");
if(pe==NULL)
cout<<"connect failed"<<endl;
while (1)
{
fwrite(s,sizeof(s),1,pe);
fgets(s,20,pe);
cout<<s;
break;
}
pclose(pe);*/

unsigned int a=1602480388;//1111638594
srand(a);
cout<<rand()<<endl;


int v3 = rand() % 10 + 65;
cout<<"v3="<<v3<<endl; //=74
int remain=291; //291下搜索
int qu=0;
int resu;
for (int i=0;i<32;i++){ //最大允许输入31个字符
for(int j=0;j<58;j++){ //从A开始搜索
resu=v3^int('A'+j);
if(resu==0) //==0没有用
{continue;}
else if ( resu<remain || remain%resu==0){ //实际上感觉不用加后面的条件
qu+=remain/resu; //累计得到的字符数
if(qu>31){ //不能大于31
qu-=remain/resu;
continue;
}
for(int k=0;k<remain/resu;k++) //输出成功的字符
cout<<char('A'+j);
cout<<endl;
remain=remain%resu; //更新余下值
cout<<"i= "<<i<<" "<<"resu="<<resu<<"qu= "<<qu<<"remain="<<remain<<endl;
i=qu-1; //更新i
cout<<"after i= "<<i<<endl;
break;
}
}

if (remain==0) //填满291 成功
{
cout<<"find"<<"i="<<i<<endl;
break;
}
else if(i>=31) //最后都没有搜索到,失败
{
cout<<endl<<"don't find"<<endl;
cout<<"remainder= "<<remain<<endl;
break;
}
}
return 0;
}

输入字符,成功解锁。

0Rlsts.jpg

最后flag文件在远程服务器,但链接时间很短,可能需要脚本自动化发送,有点小难😢


fgets()

C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

strlen()

C 库函数 size_t strlen(const char *str) 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。

结束语

不愧是新手题,只是最后编程花了我不少时间。