好友
阅读权限 30
听众
最后登录 1970-1-1
本帖最后由 pk8900 于 2019-12-17 22:16 编辑
0x00:前言
好久没有发贴了,现在论坛里大神好多啊,看完大神的帖子,真不知道该写点什么,只能水一贴了,写一下昨天逆向一个CFT的过程和思路吧。
0x01:程序32/64位识别
CTF题目没有说明,只提供一个附件,下载后,发现无扩展名,估计是Linux程序,于是用十六进制编辑器(Uedit)打开查看,在文件头部发现 ELF 字符,确定为Linux程序,再往下查看,发现“/lib64/ld-linux-x86-64.so”字符,确认为64位Linux程序。
说一下Linux程序识别的方法,因为Linux系统中文件并非是通过扩展名来标识文件类型的,所以linux程序在WINDOWS系统里只能通过查看文件内容进行确定,(估计有相关的工具,之前下载了一个,发现不好用,就放弃了。)通过十六进制编辑器查看更方便一些。
linux程序32位和64位的区别如图:
我认为通过查看字符串资源:/lib/ld-linux.so 或 /lib64/ld-linux-x86-64.so 来区分更方便一些。也可以用Linux的 FILE 命令查看,这个CTF程序为64位程序,那就用IDA 64打开进行分析。
0x02:分析过程
IDA64加载后,查看函数列表,找到Main函数,F5分析伪代码如下:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
69
70
71
int
__fastcall main(
__int64
a1,
char
**a2,
char
**a3)
{
size_t
v3;
int
v5;
int
v6;
int
i;
unsigned
int
seed;
unsigned
int
v9;
char
v10;
char
v11[16];
char
v12[32];
char
s;
char
s1[40];
unsigned
__int64
v15;
v15 = __readfsqword(0x28u);
seed = 0;
puts
(
"Welcome to Pub Zorro!!"
);
printf
(
"Straight to the point. How many drinks you want?"
, a2);
__isoc99_scanf(
"%d"
, &v5);
if
( v5 <= 0 )
{
printf
(
"You are too drunk!! Get Out!!"
, &v5);
exit
(-1);
}
printf
(
"OK. I need details of all the drinks. Give me %d drink ids:"
, (unsigned
int
)v5);
for
( i = 0; i < v5; ++i )
{
__isoc99_scanf(
"%d"
, &v6);
if
( v6 <= 16 || v6 > 65535 )
{
puts
(
"Invalid Drink Id."
);
printf
(
"Get Out!!"
, &v6);
exit
(-1);
}
seed ^= v6;
}
i = seed;
v9 = 0;
while
( i )
{
++v9;
i &= i - 1;
}
if
( v9 != 10 )
{
puts
(
"Looks like its a dangerous combination of drinks right there."
);
puts
(
"Get Out, you will get yourself killed"
);
exit
(-1);
}
srand
(seed);
MD5_Init((
__int64
)&v10);
for
( i = 0; i <= 29; ++i )
{
v9 =
rand
() % 1000;
sprintf
(&s,
"%d"
, v9);
v3 =
strlen
(&s);
MD5_Update(&v10, &s, v3);
v12[i] = v9 ^ LOBYTE(dword_6020C0[i]);
}
v12[i] = 0;
MD5_Final(v11, &v10);
for
( i = 0; i <= 15; ++i )
sprintf
(&s1[2 * i],
"%02x"
, (unsigned
__int8
)v11[i]);
if
(
strcmp
(s1,
"5eba99aff105c9ff6a1a913e343fec67"
) )
{
puts
(
"Try different mix, This mix is too sloppy"
);
exit
(-1);
}
return
printf
(
"\nYou choose right mix and here is your reward: The flag is nullcon{%s}\n"
, v12);
}
通过静态分析Main函数流程,发现程序写脚本得到FLAG:
1、程序要求输入:Straight to the point. How many drinks you want?
输入数:V5 >=0,且后续V5并没有引用,所以只要随意输入大于0的数就可以。
2、"OK. I need details of all the drinks. Give me %d drink ids:", (unsigned int)v5
输入ids:变量为v6,seed ^= v6;运行后,ids存入seed中,后续作为生成随机数的种子。
3、对IDS进行较验:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
i = seed;
v9 = 0;
while
( i )
{
++v9;
i &= i - 1;
}
if
( v9 != 10 )
{
puts
(
"Looks like its a dangerous combination of drinks right there."
);
puts
(
"Get Out, you will get yourself killed"
);
exit
(-1);
}
此代码进行了一个自减1并位与操作,核对计数为10,实现就是对输入的ids(16-65534) WORD值进行位较验,如果数字中有正好有10个位是1,则符合要求,由此可知最小可满足要求的数是1023,1023的二进制中低10位全是1,因此满足这一条件的数字应该不少,但可以写代码逐一列出。
4、用上一步的IDS【seed】做为随机数种子,生成30个小于1000的随机数,并分别与LOBYTE(dword_6020C0)异或,结果存到V12中,最后的flag就是V12中的内容(nullcon{%s}\n", v12)。
以上步骤分析完后,总结一下,可以通过写一个程序进行计算。
于是用VS2013写代码如下:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
#include "IDA.h"
#include<stdio.h>
#include<stdlib.h>
bool
myEnc(
int
n);
int
main()
{
int
i, j,n;
for
(
int
y = 16; y < 65534; y++){
n = 0;
int
m = y;
while
(m)
{
++n;
m &= m - 1;
if
(n>=11)
break
;
}
if
(n==10){
if
(myEnc(y)){
printf
(
"n=%d \n"
, y);
break
;
}
}
}
system
(
"pause"
);
return
0;
}
bool
myEnc(
int
n){
srand
(n);
int
enclist[] = { 0x3C8, 0x32, 0x2CE, 0x302, 0x7F, 0x1B8, 0x37E, 0x188, 0x349, 0x27F, 0x5E, 0x234, 0x354, 0x1A3, 0x96, 0x340, 0x128, 0x2FC, 0x300, 0x28E, 0x126, 0x1B, 0x32A, 0x2F5, 0x15F, 0x368, 0x1EB, 0x79, 0x11D, 0x24E };
unsigned
char
flag[31] = { 0 };
int
j;
for
(
int
i = 0; i < 29; i++)
{
j =
rand
() % 1000;
flag[i] = j ^ LOBYTE(enclist[i]);
if
(flag[i]>128)
return
false
;
}
flag[30] =
'\0'
;
printf
(
" %s \n"
, flag);
return
true
;
}
编译运行,可是没有找到flag,于是对程序反复检查,发现没有错误啊,不行动态调试吧,对CTF程序进行动态调试,扔到虚拟机 的 64位ubuntu 中,用IDA进行远程调试,输入第2步ids值输入:1023,发现了问题所在:
WINDOWS中,种子为1023时,生成的随机数为: 379 59 741 439 368 145 906 571 287 595 385 624 524 884 895 300......
Linux中,种子为1023时,生成的随机数为:808,14,219,336,499,953,745,120,164,303,30,151,640,588.....
原来在两个系统中种子相同,生成的随机数却不同,看来只能在linux中试一下了,于是在ubuntu中安装了VSCODE进行调试,结果和CTF程序运行一致,找到Flag.
[Asm] 纯文本查看 复制代码
1
2
3
4
[Running] cd
"/root/C++/Hello/"
&& g++ hello.cpp -o hello &&
"/root/C++/Hello/"
hello
sh: 1:
pause
:
not
found
nu11c0n_s4yz_x0r1n6_1s_4m4z1ng
n=59306
拼接后得到最终flag为:nullcon{nu11c0n_s4yz_x0r1n6_1s_4m4z1ng}
也就是原程序中输入ids:59306 即可得到flag
至此分析完成。至于程序中关于md5值计算的部分,大致分析应为生成的随机数除1000取余后,组成一个字串,对字串求MD5_32,这部分没进行验证,CTF中用的应该是OPENSSL库中的算法。
附上CTF程序:
Reverse018_zorropub.rar
(2.97 KB, 下载次数: 23)
免费评分
查看全部评分