还是将程序进行查壳
IDA打开后尝试f5,发现因为堆栈不平衡,无法直接反编译,所以修改一下
勾选堆栈指针,快捷键alt+k,将SP修改为零,如果下面还遇到同理
查看伪代码,直接可以看出flag字符串长度是0x18也就是24,有两个加密函数,wrong和omg
wrong:首先对输入的flag的每个字节根据并1后是否为真进行了亦或下标的操作
omg:wrong得到的结果跟一个全局变量unk_4030C0比较
可以简单还原出来,但发现是一个假的flag
脚本:
###
在下面分析时,发现一个encrpt函数,但f5并不能反汇编成功
这里的for循环应该是把程序里加壳的部分给脱壳,所以动态调试,也因为看到这么多次的循环,猜测为smc自修改代码
意思是自我修改的代码,使程序在运行时自我修改。
用途包括
:1) 使一些重要的跳转位置爆破无效化 (以 smc 对重要位置进行覆写)
2) 使一些重要代码隐藏 (在必要时才实时产生重要代码段,防止程序被人静态分析,也防止一些透过搜寻的破解方法)
在汇编视图的界面,可以看到对一个函数进行了一些操作,很明显可以看出右边的操作是循环对这个函数进行解密,解密之后直接调用,进去这个函数会发现是一堆乱码。
所以OD直接定位到这个函数,下断点,
这一部分对应的是刚才的for循环
f7进入call,会发现这里已经解密
用olldump直接脱壳
这时候就可以用IDA的f5静态调试了
对输入的内容和hahahaha_do_you_find_me?这个字符串进行异或,然后和一个全局变量进行比较,注意只异或了19个字节,然后写出脚本还原一下:
flag{d07abccf8a410c
只有十九个字节,还缺少五个,IDA中看程序的逻辑发现调用flag校验函数之后还有一个finally函数,这个函数正常执行是执行不到的,这个函数同样是加密的,从刚刚从OD中脱出来的程序中可以看到这个函数的明文:
这里每次得数都是随机的,根本无法爆破,因为flag最后一个字节一定是‘}’,那么用‘:’^‘}’=0x47,然后使用“%tp&:”分别异或0x47得到最后5个字节。
完整的脚本:
flag{d07abccf8a410cb37a}
发现judge函数是xor 0xc加了密的,静态ida分析不出来
用idc脚本解密
成功ida识别出函数
这里其实是将输入的字符串与对应的字符串下标xor,最后和程序里面的数据进行比较
下面是解密py脚本
IDC语言为IDA的一种脚本引擎,它之所以叫做IDC是因为它的语法与C语言很相似,这里我参考《IDA Pro权威指南》介绍一些IDC常用的基本语法。
1.IDC的变量没有明确的类型,IDC关键字auto用于引入一个局部变量的声明,用extern关键字引入全局变量的声明,不能在声明全局变量时为其提供初始值。
Example1:
Example2:
2.IDC几乎支持C中的所有运算和逻辑操作符,所有整数操作数均作为有符号的值处理。这会影响到整数比较与右移位运算。如果需要进行逻辑右移位运算,你必须修改结果的最高位,自己移位,如下代码:
Example3:
关于0x7fffffff :
每个十六进制数为4bit,因此8位16进制是4个字节,刚好是一个int整型,F的二进制码为 1111,7的二进制码为 0111。这样一来,整个整数 0x7FFFFFFF 的二进制表示就是除了首位是 0,其余都是1。也就是说,这是最大的整型数 int(因为第一位是符号位,0 表示他是正数)。
3.虽然IDC没有数组数据类型,但你可以使用分片运算符来处理IDC字符串,就好像他们是数组一样,IDC分片的用法:
Example4:
4.与C语言一样,IDC所有简单语句均以分号结束。Switch语句是IDC唯一不支持的C风格复合语句。在使用for语句时IDC不支持复合赋值运算符,如果你希望以除1以外的其他值为单位进行计数,就需要注意这一点,如下代码:
Example5:
5.输出语句(Message函数类似于C中的printf函数)
Example6:
6.IDC文件仅仅在独立程序(.idc文件)支持用户自定义的函数,IDC命令对话框不支持。IDC程序文件的基本结构:
Example7:
7.一些常用函数:
1)void PatchByte(long addr , long val) 设置虚拟地址addr处的一个字节值,PatchByte可更换为PatchWord,PatchDword设置虚拟地址addr处的2字节和4字节值。
2)long Byte(long addr) 从虚拟地址addr读取一个字节值,Byte可更换为Word,Dword读取2字节和4字节值。
3)void Message(string format , …),在输出窗口打印一条格式化消息。
4)void print(…),在输出窗口中打印每个参数的字符串表示形式。
5)long atol(string val),将10进制val转化成对应整数值。
6)long xtol(string val),将16进制val转化成对应整数值。
7)long ord(string ch),返回单字符字符串ch的ASCII值。
8)string Name(long addr),返回与给定地址有关的名称,如果该位置没有名称,则返回空字符串。
抵御静态分析本质上就是降低反汇编代码的可读性,不过这种保护方式通常只是起到了扰乱作用,如果只是单独使用其中一种,那就只是纸老虎而已,理论上通过动态分析的方法,可以完全无视这样的保护,所以这些保护技术需要和其他保护技术相互配合才能达到良好的效果。
1.花指令
2.smc
3.信息隐藏
4.代码与数据结合
self-modify-code,SMC技术比较惹人喜爱,它将一块局部的代码进行加密,而在程序运行过程中对这块代码进行解密,可以达到隐藏关键代码的效果。
最简单的SMC保护效果也是很弱的,因为在程序运行的某一时刻,它一定是解密完成的,这时也就暴露了,使用动态分析运行到这一时刻即可过掉保护;
其次是找到修改代码段的算法,即只要根据静态分析获得解密算法,就可直接写出解密脚本提前解密这段代码。所以SMC通常是配合反追踪技术或是嵌套的使用。
反追踪技术用于对抗动态分析,最常见的就是当程序发现自身正在被调试时,直接使调试结束。之前说到SMC由于在程序运行某一时刻一定是解密完成的,可通过动态分析直接获得解密结果,如果我们加上反追踪技术,就会使得动态分析的难度加大。
不过即使这样,只要分析人员找到了反追踪的关键函数,直接对其进行patch,之后过掉SMC也就顺理成章了。
所以还有更阴险的招数,就是把反调试的代码作为解密SMC的密钥,当我们对反调试代码进行修改时,解密就无法成功。如果下断点来修改寄存器的值来绕过反调试,而不进行代码修改怎么办呢?那么只需要使加密算法以字节为单位进行加密即可,在十分庞大的循环次数下,是不能总是手动去修改寄存器的值的。
那么如何使静态分析解密SMC的算法变得困难呢?这就主要靠嵌套型的SMC了,每段SMC中又包含了另一段SMC,只需要3,4层,就能使静态分析变得极为繁琐。
当SMC综合了各种保护技术的时候,就有种吃鱼肉的感觉,肉中带刺,刺中带肉,容易磨掉人们的耐心。
int
__cdecl main(
int
argc, const char
*
*
argv, const char
*
*
envp)
{
char v4;
/
/
[esp
+
12h
] [ebp
-
96h
]
char v5;
/
/
[esp
+
44h
] [ebp
-
64h
]
DWORD flOldProtect;
/
/
[esp
+
94h
] [ebp
-
14h
]
size_t v7;
/
/
[esp
+
98h
] [ebp
-
10h
]
int
i;
/
/
[esp
+
9Ch
] [ebp
-
Ch]
__main();
puts(
"please input you flag:"
);
if
( VirtualProtect(encrypt,
0xC8u
,
4u
, &flOldProtect)
=
=
0
)
exit(
1
);
scanf(
"%40s"
, &v4);
v7
=
strlen(&v4);
if
( v7 !
=
24
)
{
puts(
"Wrong!"
);
exit(
0
);
}
strcpy(&v5, &v4);
wrong(&v4);
omg(&v4);
for
( i
=
0
; i <
=
186
;
+
+
i )
*
((_BYTE
*
)encrypt
+
i) ^
=
0x41u
;
if
( encrypt(&v5) !
=
0
)
finally
(&v5);
return
0
;
}
int
__cdecl main(
int
argc, const char
*
*
argv, const char
*
*
envp)
{
char v4;
/
/
[esp
+
12h
] [ebp
-
96h
]
char v5;
/
/
[esp
+
44h
] [ebp
-
64h
]
DWORD flOldProtect;
/
/
[esp
+
94h
] [ebp
-
14h
]
size_t v7;
/
/
[esp
+
98h
] [ebp
-
10h
]
int
i;
/
/
[esp
+
9Ch
] [ebp
-
Ch]
__main();
puts(
"please input you flag:"
);
if
( VirtualProtect(encrypt,
0xC8u
,
4u
, &flOldProtect)
=
=
0
)
exit(
1
);
scanf(
"%40s"
, &v4);
v7
=
strlen(&v4);
if
( v7 !
=
24
)
{
puts(
"Wrong!"
);
exit(
0
);
}
strcpy(&v5, &v4);
wrong(&v4);
omg(&v4);
for
( i
=
0
; i <
=
186
;
+
+
i )
*
((_BYTE
*
)encrypt
+
i) ^
=
0x41u
;
if
( encrypt(&v5) !
=
0
)
finally
(&v5);
return
0
;
}
char
*
__cdecl wrong(char
*
a1)
{
char
*
result;
/
/
eax
signed
int
i;
/
/
[esp
+
Ch] [ebp
-
4h
]
for
( i
=
0
; i <
=
23
;
+
+
i )
{
if
( i &
1
)
{
result
=
&a1[i];
a1[i]
-
=
i;
}
else
{
result
=
&a1[i];
a1[i] ^
=
i;
}
}
return
result;
}
char
*
__cdecl wrong(char
*
a1)
{
char
*
result;
/
/
eax
signed
int
i;
/
/
[esp
+
Ch] [ebp
-
4h
]
for
( i
=
0
; i <
=
23
;
+
+
i )
{
if
( i &
1
)
{
result
=
&a1[i];
a1[i]
-
=
i;
}
else
{
result
=
&a1[i];
a1[i] ^
=
i;
}
}
return
result;
}
int
__cdecl omg(char
*
a1)
{
int
result;
/
/
eax
int
v2[
24
];
/
/
[esp
+
18h
] [ebp
-
80h
]
int
i;
/
/
[esp
+
78h
] [ebp
-
20h
]
int
v4;
/
/
[esp
+
7Ch
] [ebp
-
1Ch
]
v4
=
1
;
qmemcpy(v2, &unk_4030C0, sizeof(v2));
for
( i
=
0
; i <
=
23
;
+
+
i )
{
if
( a1[i] !
=
v2[i] )
v4
=
0
;
}
if
( v4
=
=
1
)
result
=
puts(
"hahahaha_do_you_find_me?"
);
else
result
=
puts(
"wrong ~~ But seems a little program"
);
return
result;
}
int
__cdecl omg(char
*
a1)
{
int
result;
/
/
eax
int
v2[
24
];
/
/
[esp
+
18h
] [ebp
-
80h
]
int
i;
/
/
[esp
+
78h
] [ebp
-
20h
]
int
v4;
/
/
[esp
+
7Ch
] [ebp
-
1Ch
]
v4
=
1
;
qmemcpy(v2, &unk_4030C0, sizeof(v2));
for
( i
=
0
; i <
=
23
;
+
+
i )
{
if
( a1[i] !
=
v2[i] )
v4
=
0
;
}
if
( v4
=
=
1
)
result
=
puts(
"hahahaha_do_you_find_me?"
);
else
result
=
puts(
"wrong ~~ But seems a little program"
);
return
result;
}
result
=
"fkcd\x7fagd;Vka{&;Pc_MZq\x0c7f"
i
=
0
flag
=
""
for
j
in
result:
if
(i&
1
):
flag
+
=
chr
(
ord
(j)
+
i)
else
:
flag
+
=
chr
(
ord
(j)^i)
i
+
=
1
print
flag
result
=
"fkcd\x7fagd;Vka{&;Pc_MZq\x0c7f"
i
=
0
flag
=
""
for
j
in
result:
if
(i&
1
):
flag
+
=
chr
(
ord
(j)
+
i)
else
:
flag
+
=
chr
(
ord
(j)^i)
i
+
=
1
print
flag
for
( i
=
0
; i <
=
186
;
+
+
i )
*
((_BYTE
*
)encrypt
+
i) ^
=
0x41u
;
if
( encrypt(&v5) !
=
0
)
finally
(&v5);
for
( i
=
0
; i <
=
186
;
+
+
i )
*
((_BYTE
*
)encrypt
+
i) ^
=
0x41u
;
if
( encrypt(&v5) !
=
0
)
finally
(&v5);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-3-26 14:04
被愿风载尘编辑
,原因: