第1课:二进制安全逆向介绍
二进制安全,能干什么
-
逆向分析:
负责成品软件的技术原理. 比如分析竞品软件,吸取技术上的优点,进行技术难点公关 -
病毒分析:
负责分析病毒样本.研究恶意代码的技术手段等工作.主要是在安全公司,尤其是在杀毒软件公司需求较多.如360 、腾讯电脑管家等. -
漏洞挖掘分析:
负责分析漏洞样本,或者漏洞的挖掘.目前二进制的主要方向.涉及范围广,从主流浏览器 虚拟机 内核到IOT 还有android 和 IOS移动平台. -
移动安全:
负责移动端安全.如移动端的漏洞挖掘,还有加壳混淆等 -
安全开发:
包含较广.比如硬件平台,内核安全等.一般也是安全公司.如防火墙 主动防御系统 反外挂等
逆向与开发的对比
以C语言举例,C语言代码属于高级语言代码,不能直接被处理器执行,这时候需要由编译器将C语言代码翻译成处理器可以直接理解的机器代码。
编译流程: C语言代码 -> … -> 汇编语言代码 -> … -> 机器代码
机器代码会按照一定的排列方式存储在可执行文件中,最常见的可执行文件有 Windows 的 exe 文件以及 Linux 的 ELF 文件。
逆向工程操作对象就是这一类可执行文件,逆向即通过机器代码反推程序的原理。机器码毫无可读性,可执行文件编排也十分复杂,所以需要一款强大的辅助软件辅助逆向分析过程,使分析者可以专注于代码本身的逻辑层面。
IDA 生成高级语言代码的流程:
机器代码 -> … -> 汇编代码 -> … -> 高级语言代码
IDA 定位 Main 函数
IDA 对符号重新命名
这道题演示了如何使用 IDA 载入并分析一个可执行的二进制文件,并通过字符串定位的方式在茫茫的代码海洋中找到 main 函数,我们又使用 IDA 的伪代码功能生成 main 函数的伪代码,并修复没有名字的函数,使得程序可读性得到极大提升。
思路: 运行程序 -> 收集字符串 -> 寻找字符串引用代码 -> 生成伪代码 -> 修复匿名函数 -> 分析程序逻辑 -> 得到 Flag
视频
aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tL3ZpZGVvL0JWMTdMNDExczdHWC8=
课件
aHR0cHM6Ly9naXRodWIuY29tL1N5Y2xvdmVyVGVhbS9TeWNSZXZMZWFybg==
第2题:简单的加密算法
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
size_t i; // [esp+D0h] [ebp-114h]
char Str1[260]; // [esp+DCh] [ebp-108h] BYREF
printf("Hi CTFer,Input your flag:");
scanf("%s", Str1);
for ( i = 0; i < j__strlen(Str1); ++i )
++Str1[i];
if ( !j__strcmp(Str1, "gmbh|ZPV`GJOE`JU`IBIB~") )
printf("you are right!\n");
else
printf("you are wrong!\n");
return 0;
}
结果
target = bytearray(b'gmbh|ZPV`GJOE`JU`IBIB~')
for x in range(len(target)):
target[x] -=1
print(target.decode())
flag{YOU_FIND_IT_HAHA}
第3题:简单的加密算法2
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
size_t i; // [esp+D0h] [ebp-114h]
char v5[260]; // [esp+DCh] [ebp-108h] BYREF
sub_456502("[5] Hi CTFer,Input your flag:");
sub_4554EF("%s", v5);
for ( i = 0; i < j__strlen(v5); ++i )
v5[i] ^= i;
if ( !j__strcmp(v5, Str2) )
sub_456502("you are right!\n");
else
sub_456502("you are wrong!\n");
return 0;
}
结果
data = [0x66, 0x6D, 0x63, 0x64, 0x7F, 0x5C, 0x49, 0x52, 0x57, 0x4F, 0x43, 0x45, 0x48, 0x52, 0x47, 0x5B, 0x4F, 0x59, 0x53, 0x5B, 0x55, 0x68]
for i in range(len(data)):
data[i] ^= i
print(bytes(data).decode())
flag{YOU_FIND_IT_HAHA}
第4题:Base64 编码逆向
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
size_t v3; // eax
size_t i; // [esp+190h] [ebp-31Ch]
char Str1[520]; // [esp+19Ch] [ebp-310h] BYREF
char Str[260]; // [esp+3A4h] [ebp-108h] BYREF
printf("[4] Hi CTFer,Input your flag:");
scanf("%s", Str);
for ( i = 0; i < j__strlen(Str); ++i )
Str[i] ^= i;
v3 = j__strlen(Str);
sub_455A94(Str, Str1, v3);
if ( !j__strcmp(Str1, "Zm1jZH9cSVJXT0NFSFJHW09ZU1tVaA==") )
printf("you are right!\n");
else
printf("you are wrong!\n");
return 0;
}
结果
import base64
data = base64.b64decode("Zm1jZH9cSVJXT0NFSFJHW09ZU1tVaA==")
data = bytearray(data)
for x in range(len(data)):
data[x] ^= x
print(data.decode())
flag{YOU_FIND_IT_HAHA}
第5题:Base64 变表逆向
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
size_t input_len; // eax
size_t i; // [esp+190h] [ebp-31Ch]
char Str1[520]; // [esp+19Ch] [ebp-310h] BYREF
char input[260]; // [esp+3A4h] [ebp-108h] BYREF
printf("[4] Hi CTFer,Input your flag:");
scanf("%s", input);
for ( i = 0; i < j__strlen(input); ++i )
input[i] ^= i;
input_len = j__strlen(input);
sub_455A94(input, Str1, input_len);
if ( !j__strcmp(Str1, "Wj1gWE9xPSGUQ0KCPCGET09WR1qSzZ==") )
printf("you are right!\n");
else
printf("you are wrong!\n");
return 0;
}
进 sub_455A94() 函数,找下算法特征,推断是 BASE64 算法
标准编码表(T4)
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
魔改之后的编码表(T5)
ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/
所以导致不对
- base64 编码原理 类似于进制转换,256 -> 64 进制
- 数据取每 6 bit -> v (0- 64) -> 从编码表中取出下标 v 对应的字符
分析思路
Wj1gWE9xPSGUQ0KCPCGET09WR1qSzZ==
W 在 T5 中的位置对应到 T4 则是 Z
j 在 T5 中的位置对应到 T4 则是 m
因此将把 Wj 替换成 Zm,后续也是同样的操作,最后用 T4 的脚本即可解密 Flag
结果
import base64
data = 'Wj1gWE9xPSGUQ0KCPCGET09WR1qSzZ' # 去掉 ==
T4 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
T5 = 'ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/'
result = ""
for ch in data:
result += T4[T5.index(ch)]
result = bytearray(base64.b64decode(result + "=="))
for i in range(len(result)):
result[i] ^= i
print(result.decode())
flag{YOU_FIND_IT_HAHA}
C语言:base64 算法
int __cdecl sub_45A3F0(int a1, int a2, int a3)
{
unsigned __int8 v4; // [esp+D3h] [ebp-1Dh]
unsigned __int8 v5; // [esp+D3h] [ebp-1Dh]
int v6; // [esp+DCh] [ebp-14h]
int v7; // [esp+DCh] [ebp-14h]
int v8; // [esp+DCh] [ebp-14h]
int v9; // [esp+DCh] [ebp-14h]
int v10; // [esp+DCh] [ebp-14h]
int v11; // [esp+DCh] [ebp-14h]
int v12; // [esp+E8h] [ebp-8h]
v12 = 0;
v6 = 0;
while ( v12 < a3 )
{
*(_BYTE *)(v6 + a2) = *((_BYTE *)off_529000 + (((int)*(unsigned __int8 *)(v12 + a1) >> 2) & 0x3F));
v7 = v6 + 1;
v4 = (16 * *(_BYTE *)(v12 + a1)) & 0x30;
if ( v12 + 1 >= a3 )
{
*(_BYTE *)(v7 + a2) = *((_BYTE *)off_529000 + v4);
v8 = v7 + 1;
*(_BYTE *)(v8 + a2) = 61;
*(_BYTE *)(++v8 + a2) = 61;
v6 = v8 + 1;
break;
}
*(_BYTE *)(v7 + a2) = *((_BYTE *)off_529000 + (((int)*(unsigned __int8 *)(v12 + a1 + 1) >> 4) & 0xF | v4));
v9 = v7 + 1;
v5 = (4 * *(_BYTE *)(v12 + a1 + 1)) & 0x3C;
if ( v12 + 2 >= a3 )
{
*(_BYTE *)(v9 + a2) = *((_BYTE *)off_529000 + v5);
v10 = v9 + 1;
*(_BYTE *)(v10 + a2) = 61;
v6 = v10 + 1;
break;
}
*(_BYTE *)(v9 + a2) = *((_BYTE *)off_529000 + (((int)*(unsigned __int8 *)(v12 + a1 + 2) >> 6) & 3 | v5));
v11 = v9 + 1;
*(_BYTE *)(v11 + a2) = *((_BYTE *)off_529000 + (*(_BYTE *)(v12 + a1 + 2) & 0x3F));
v6 = v11 + 1;
v12 += 3;
}
*(_BYTE *)(v6 + a2) = 0;
return a2;
}
第6题:IDA 动态调试
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
size_t i; // [esp+190h] [ebp-340h]
char input[264]; // [esp+3A4h] [ebp-12Ch] BYREF
char str1[32]; // [esp+4ACh] [ebp-24h] BYREF
qmemcpy(str1, &unk_50DE50, 034u);
printf("[6] Hi CTFer,Input your flag:");
scanf("%s", input);
for ( i = 0; i < j__strlen(str1); ++i )
str1[i] = ((i + 1) ^ str1[i]) - i;
if ( !j__strcmp(str1, input) )
printf("you are right\n");
else
printf("you are wrong\n");
return 0;
}
动态调试前要确定调试的目的
1、获取运行时数据
2、获取执行流程
3、验证猜想是否正确
例如要获取数据,就需要提前设置断点。 IDA 支持在伪代码层面调试,所以可以直接在 IDA 的伪代码里面设置断点。
点击小蓝点之后,这行代码就会变成红色,就代表成功设置断点。
Windows本地exe - 动态调试
首次使用(需先配置)
选第3个【Local Windows debugger】
配置完成后,去启动
如果【安全提醒】 在CTF题目情况下,都是安全的,默认信任。未知的程序,最好去虚拟机里调试。
此时断点
告诉 ida 这是字符串(按键:A)
拿到
Flag{This_IS_T7_DEBUG_EASY}
第7题:IDA 动态调试解RC4(绕过反调试)
有一些算法的加密与解密是相同的算法过程,例如 RC4、部分简单的异或算法等。
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
int j; // [esp+31Ch] [ebp-69Ch]
int i; // [esp+328h] [ebp-690h]
size_t v6; // [esp+334h] [ebp-684h]
char v7[55]; // [esp+340h] [ebp-678h]
char v8; // [esp+377h] [ebp-641h]
unsigned int v9; // [esp+380h] [ebp-638h]
int v10; // [esp+38Ch] [ebp-62Ch]
char Str[520]; // [esp+398h] [ebp-620h] BYREF
char v12[264]; // [esp+5A0h] [ebp-418h] BYREF
char v13[264]; // [esp+6A8h] [ebp-310h] BYREF
char v14[516]; // [esp+7B0h] [ebp-208h] BYREF
__CheckForDebuggerJustMyCode(&unk_49C00F);
j__memset(v14, 0, 0x200u);
j__memset(v13, 0, 0x100u);
j__memset(v12, 0, 0x100u);
j__memset(Str, 0, 0x200u);
v10 = 0;
v9 = 0;
v7[0] = -28;
v7[1] = 21;
v7[2] = -60;
v7[3] = -19;
v7[4] = -90;
v7[5] = 47;
v7[6] = 86;
v7[7] = 16;
v7[8] = -69;
v7[9] = 19;
v7[10] = -21;
v7[11] = -83;
v7[12] = 117;
v7[13] = 86;
v7[14] = -57;
v7[15] = -69;
v7[16] = -69;
v7[17] = -23;
v7[18] = -71;
v7[19] = -52;
v7[20] = 2;
v7[21] = 58;
v7[22] = 80;
v7[23] = -97;
v7[24] = 54;
v7[25] = -112;
v7[26] = 105;
v7[27] = -66;
v7[28] = 124;
v7[29] = 66;
v7[30] = 68;
v7[31] = -54;
v7[32] = -58;
v7[33] = -44;
v7[34] = 36;
v7[35] = 92;
v7[36] = -46;
v7[37] = -71;
v7[38] = 36;
v7[39] = -63;
v7[40] = 24;
v7[41] = -109;
v7[42] = -77;
v7[43] = -22;
sub_42F057(v14);
sub_42C40B("Welcome!! give me your flag:\n");
do
{
v8 = j_j_j___fgetchar();
if ( v8 == 10 )
break;
Str[v9++] = v8;
}
while ( (int)v9 < 44 );
if ( v9 >= 0x200 )
j____report_rangecheckfailure();
Str[v9] = 0;
v6 = j__strlen(Str);
sub_42CEFB(v13, v14, v6);
for ( i = 0; i < 256; ++i )
v12[i] = v13[i];
sub_42D5B8(v13, Str, v6);
for ( j = 0; j < 44; ++j )
{
if ( v7[j] == Str[j] )
++v10;
}
if ( v10 == 44 )
sub_42C40B("Yes, u right!\n");
else
sub_42C40B("no no no\n");
sub_42D0CC("pause");
return 0;
}
改了函数名
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
int j; // [esp+31Ch] [ebp-69Ch]
int i; // [esp+328h] [ebp-690h]
size_t input_len; // [esp+334h] [ebp-684h]
char v7[55]; // [esp+340h] [ebp-678h]
char v8; // [esp+377h] [ebp-641h]
unsigned int index; // [esp+380h] [ebp-638h]
int v10; // [esp+38Ch] [ebp-62Ch]
char input[520]; // [esp+398h] [ebp-620h] BYREF
char v12[264]; // [esp+5A0h] [ebp-418h] BYREF
char v13[264]; // [esp+6A8h] [ebp-310h] BYREF
char v14[516]; // [esp+7B0h] [ebp-208h] BYREF
__CheckForDebuggerJustMyCode(&unk_50C00F);
j__memset(v14, 0, 0x200u);
j__memset(v13, 0, 0x100u);
j__memset(v12, 0, 0x100u);
j__memset(input, 0, 0x200u);
v10 = 0;
index = 0;
v7[0] = -28;
v7[1] = 21;
v7[2] = -60;
v7[3] = -19;
v7[4] = -90;
v7[5] = 47;
v7[6] = 86;
v7[7] = 16;
v7[8] = -69;
v7[9] = 19;
v7[10] = -21;
v7[11] = -83;
v7[12] = 117;
v7[13] = 86;
v7[14] = -57;
v7[15] = -69;
v7[16] = -69;
v7[17] = -23;
v7[18] = -71;
v7[19] = -52;
v7[20] = 2;
v7[21] = 58;
v7[22] = 80;
v7[23] = -97;
v7[24] = 54;
v7[25] = -112;
v7[26] = 105;
v7[27] = -66;
v7[28] = 124;
v7[29] = 66;
v7[30] = 68;
v7[31] = -54;
v7[32] = -58;
v7[33] = -44;
v7[34] = 36;
v7[35] = 92;
v7[36] = -46;
v7[37] = -71;
v7[38] = 36;
v7[39] = -63;
v7[40] = 24;
v7[41] = -109;
v7[42] = -77;
v7[43] = -22;
sub_49F057((int)v14); // 反调试
sub_49C40B("Welcome!! give me your flag:\n");
do
{
v8 = j_j_j___fgetchar();
if ( v8 == '\n' )
break;
input[index++] = v8;
}
while ( (int)index < 44 );
if ( index >= 0x200 )
j____report_rangecheckfailure();
input[index] = 0;
input_len = j__strlen(input);
fun1(v13, v14, input_len);
for ( i = 0; i < 256; ++i )
v12[i] = v13[i];
sub_49D5B8(v13, input, input_len); // 推测核心算法,可理解为黑盒,有输入输出的地方
for ( j = 0; j < 44; ++j )
{
if ( v7[j] == input[j] )
++v10;
}
if ( v10 == 44 )
sub_49C40B("Yes, u right!\n");
else
sub_49C40B("no no no\n");
sub_49D0CC("pause");
return 0;
}
反调试逻辑
_BYTE *__cdecl sub_4325D0(_BYTE *a1)
{
HWND ForegroundWindow; // eax
unsigned __int64 v2; // rax
unsigned __int64 v3; // rax
int v5; // [esp+25Ch] [ebp-438h]
CHAR String[1028]; // [esp+28Ch] [ebp-408h] BYREF
if ( IsDebuggerPresent() )
*a1 = 89;
else
*a1 = 88;
ForegroundWindow = GetForegroundWindow();
GetWindowTextA(ForegroundWindow, String, 1023);
if ( j__strstr(String, "WinDbg")
|| j__strstr(String, "x64dbg")
|| j__strstr(String, "x32dbg")
|| j__strstr(String, "OllyICE")
|| j__strstr(String, "OllyDBG")
|| j__strstr(String, "Immunity") )
{
a1[1] = 111;
}
else
{
a1[1] = 48;
}
SetLastError((DWORD)"12345");
OutputDebugStringW("Test for debugger!");
if ( (char *)GetLastError() == "12345" )
a1[2] = 110;
else
a1[2] = 117;
if ( !CloseHandle((HANDLE)0x1234) && GetLastError() == 6 )
a1[3] = 66;
else
a1[3] = 65;
if ( NtCurrentPeb()->BeingDebugged )
a1[4] = 97;
else
a1[4] = 64;
v2 = __rdtsc();
v5 = v2;
v3 = __rdtsc();
if ( (unsigned int)(v3 - v5) >= 0xFF )
a1[5] = 100;
else
a1[5] = 68;
a1[6] = 0;
return a1;
}
Windows本地exe - 附加进程
本地先打开【rc4.exe】程序,然后点击
将会已附加的形式,去调试一个正在运行的进程
此时会停下来,点击 继续运行
双击 input 变量
此时可以看到输入的字符串
请注意:在 c 语言中 ,字符串的结尾是 0
伪造 44 位字符串【AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA】
重新调试
双击 input
找到内存地址【00B6F89C】记录下来
单步一下,此时,函数内部已经算好
查看内存(按键:G)
选中 44 位,已 0 结尾
到 16进制【F60DC6D7B7046F0E890DFD835924E8A599C4C8F92B127FB928E05BA06E336AE4B7FA5542F08D11E578E39BD6】
得出结论
内存地址
00B6F89C
输入值
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
结果
F60DC6D7B7046F0E890DFD835924E8A599C4C8F92B127FB928E05BA06E336AE4B7FA5542F08D11E578E39BD6
再次重新调试
- 重新运行程序
- 附加进程
- 断住
修改内存数据
然后
记录下
内存地址
0046F2C8
步过(按键:F8)
在修改内存后,发现是一样的数据。
然后提取 v7 的值,双击进去,复制hex
得到结果【E415C4EDA62F5610BB13EBAD7556C7BBBBE9B9CC023A509F369069BE7C4244CAC6D4245CD2B924C11893B3EA】
再次重新调试
附加进程,输入44位字符串,并修改内存
记录
内存地址
0065F7AC
看到结果了,双击进去
转字符串(按键:A)
结果
SYC{Pjx_s_Wom3n_cl0thing_1s_S0oo0o0_cute!1i}
第8题:IDA 代码修复 & 数组识别
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-BCh]
char v5; // [esp+0h] [ebp-BCh]
int v6; // [esp+0h] [ebp-BCh]
HANDLE TimerQueue; // [esp+4h] [ebp-B8h]
int v8; // [esp+8h] [ebp-B4h]
HANDLE phNewTimer; // [esp+18h] [ebp-A4h] BYREF
char v10[52]; // [esp+1Ch] [ebp-A0h] BYREF
char Destination[52]; // [esp+50h] [ebp-6Ch] BYREF
char Str[5]; // [esp+84h] [ebp-38h] BYREF
char Source[47]; // [esp+89h] [ebp-33h] BYREF
TimerQueue = CreateTimerQueue();
CreateTimerQueueTimer(&phNewTimer, TimerQueue, Callback, 0, 0, 0x3E8u, 0x20u);
sub_401450("Welcome To XDU\n", v4);
sub_401450("Show me your password\n", v5);
sub_4013C0("%s", (char)Str);
if ( sub_401000(Str, "flag{") != (_DWORD)Str || Source[32] != 125 )
sub_401340(v6, TimerQueue);
sub_401360(Destination, Source, 0x20u);
sub_4011F0(Destination);
sub_401270(&unk_4040C0, Destination, v10);
v8 = strcmp(v10, a23gjf13au98hk3);
if ( v8 )
v8 = v8 < 0 ? -1 : 1;
if ( v8 )
sub_401340(v8, TimerQueue);
sub_401450("Congratulations!!!\n", 0);
DeleteTimerQueueEx(TimerQueue, (HANDLE)0xFFFFFFFF);
return 0;
}
代码修复
- 判断返回值是否有意义
- 判断参数的类型
- 判断局部变量的类型与数组大小
- …
随便看个函数。交叉引用,查看只有1处调用
char __cdecl sub_4011F0(char *a1)
{
char result; // al
char *v2; // [esp+4h] [ebp-14h]
char *v3; // [esp+Ch] [ebp-Ch]
int i; // [esp+10h] [ebp-8h]
v3 = a1;
v2 = a1 + 1;
do
result = *v3;
while ( *v3++ );
if ( v3 - v2 != 32 )
sub_401340(v3 - v2, v2);
for ( i = 0; i < 32; ++i )
{
a1[i] ^= LOBYTE(dword_404040[i]);
result = i + 1;
}
return result;
}
但是 ida 认为返回是 char 类型,需要 手动改下。
改成无返回类型(按键:V)
点击函数名,右键
改成【void】代表无返回
也可以点击函数名,按 V 键
void __cdecl sub_4011F0(char *a1)
{
char *v1; // [esp+Ch] [ebp-Ch]
int i; // [esp+10h] [ebp-8h]
v1 = &a1[strlen(a1) + 1];
if ( v1 - (a1 + 1) != 32 )
sub_401340(v1 - (a1 + 1), a1 + 1);
for ( i = 0; i < 32; ++i )
a1[i] ^= LOBYTE(dword_404040[i]);
}
此时,发现减少了很多代码,逻辑就很清晰了。
当然也会有问题,进 sub_401340() 函数,发现 入参缺失了
修复入参类型
void __noreturn sub_401340()
{
char savedregs; // [esp+0h] [ebp+0h]
sub_401450("Emmmm...Wrong\n", savedregs);
exit(0);
}
只需要:进入该函数,按 F5 键 ,再 Esc 键,再按 F5 键,即可恢复
这样更少了
void __cdecl sub_4011F0(char *a1)
{
int i; // [esp+10h] [ebp-8h]
if ( strlen(a1) != 32 )
sub_401340();
for ( i = 0; i < 32; ++i )
a1[i] ^= LOBYTE(dword_404040[i]);
}
修改入参类型,为指针
再来找个函数,改改。
int __cdecl sub_401270(int a1, const char *a2, int a3)
{
signed int v3; // kr00_4
int result; // eax
signed int v5; // [esp+10h] [ebp-8h]
v5 = 0;
v3 = strlen(a2);
while ( v5 < v3 )
{
*(_BYTE *)(a3 + *(_DWORD *)(a1 + 4 * v5)) = a2[v5];
++v5;
}
result = v5 + a3;
*(_BYTE *)(v5 + a3) = 0;
return result;
}
点击函数名,按 V 键
void __cdecl sub_401270(int a1, const char *a2, int a3)
{
signed int v3; // kr00_4
signed int v4; // [esp+10h] [ebp-8h]
v4 = 0;
v3 = strlen(a2);
while ( v4 < v3 )
{
*(_BYTE *)(a3 + *(_DWORD *)(a1 + 4 * v4)) = a2[v4];
++v4;
}
*(_BYTE *)(v4 + a3) = 0;
}
发现这样不显著,当然也不是万能的
回到主函数
观察第3个应该是 指针,跟进去修改
然后
void __cdecl sub_401270(int a1, const char *a2, char *a3)
{
signed int v3; // kr00_4
signed int v4; // [esp+10h] [ebp-8h]
v4 = 0;
v3 = strlen(a2);
while ( v4 < v3 )
{
a3[*(_DWORD *)(a1 + 4 * v4)] = a2[v4];
++v4;
}
a3[v4] = 0;
}
再来观察
sub_401270(&unk_4040C0, Destination, v9);
推断 第一个是 取值,也是指针
void __cdecl sub_401270(int a1, const char *a2, char *a3)
{
signed int v3; // kr00_4
signed int i; // [esp+10h] [ebp-8h]
i = 0;
v3 = strlen(a2);
while ( i < v3 )
{
a3[*(_DWORD *)(a1 + 4 * i)] = a2[i];
++i;
}
a3[i] = 0;
}
一个无符号整型数据占4个字节
a1 + 4 * i = 整数数组寻址:1个元素4字节,第i个元素偏移量,就是数组首地址 + 4 * i
第1个入参改为【int *】类型
这样代码就清晰了,很漂亮
void __cdecl sub_401270(int *a1, const char *a2, char *a3)
{
signed int v3; // kr00_4
signed int i; // [esp+10h] [ebp-8h]
i = 0;
v3 = strlen(a2);
while ( i < v3 )
{
a3[a1[i]] = a2[i];
++i;
}
a3[i] = 0;
}
切回主函数,继续观察
修改字符的存储长度(按键:Y)
此时,主函数的逻辑更清晰了
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-BCh]
char v5; // [esp+0h] [ebp-BCh]
HANDLE TimerQueue; // [esp+4h] [ebp-B8h]
int v7; // [esp+8h] [ebp-B4h]
HANDLE phNewTimer; // [esp+18h] [ebp-A4h] BYREF
char a3[52]; // [esp+1Ch] [ebp-A0h] BYREF
char xxxx[52]; // [esp+50h] [ebp-6Ch] BYREF
char Str[52]; // [esp+84h] [ebp-38h] BYREF
TimerQueue = CreateTimerQueue();
CreateTimerQueueTimer(&phNewTimer, TimerQueue, Callback, 0, 0, 0x3E8u, 0x20u);
sub_401450("Welcome To XDU\n", v4);
sub_401450("Show me your password\n", v5);
scanf("%s", (char)Str);
if ( strstr_0(Str, "flag{") != Str || Str[37] != 125 )
sub_401340();
get_flag_body(xxxx, &Str[5], 0x20u);
sub_4011F0(xxxx);
sub_401270(a1, xxxx, a3);
v7 = strcmp(a3, a23gjf13au98hk3);
if ( v7 )
v7 = v7 < 0 ? -1 : 1;
if ( v7 )
sub_401340();
sub_401450("Congratulations!!!\n", 0);
DeleteTimerQueueEx(TimerQueue, (HANDLE)0xFFFFFFFF);
return 0;
}
先拿 a1 的值
因为是 4个字节访问的
来到
void __cdecl sub_4011F0(char *a1)
{
int i; // [esp+10h] [ebp-8h]
if ( strlen(a1) != 32 )
sub_401340();
for ( i = 0; i < 32; ++i )
a1[i] ^= LOBYTE(dword_404040[i]);
}
因为是 低位 表现的
修改
然后
a1 = [0x00000004, 0x0000000F, 0x0000000B, 0x0000001E, 0x0000000E, 0x00000014, 0x0000001F, 0x00000009, 0x00000017, 0x00000002, 0x00000019, 0x0000001C, 0x00000012, 0x00000010, 0x00000000, 0x00000008, 0x00000011, 0x00000001, 0x00000015, 0x00000003, 0x0000000A, 0x0000001D, 0x0000000C, 0x00000016, 0x00000018, 0x0000000D, 0x0000001B, 0x00000005, 0x00000007, 0x00000006, 0x00000013, 0x0000001A]
target = bytearray(b'23gjf13au98hk3a1090zp8qjs41h39jp')
input_xxx = [0] * 32
for i in range(len(target)):
input_xxx[i] = target[a1[i]]
# input_xxx[0] = target[4]
print(input_xxx)
X = [0x00000053, 0x00000045, 0x0000005C, 0x0000001E, 0x00000050, 0x00000013, 0x0000002F, 0x00000078, 0x00000004, 0x00000053, 0x00000058, 0x0000004A, 0x00000043, 0x00000001, 0x00000041, 0x0000002A, 0x00000008, 0x00000040, 0x00000067, 0x0000002F, 0x0000000C, 0x0000004A, 0x00000012, 0x0000002E, 0x00000041, 0x0000006C, 0x00000005, 0x00000054, 0x00000040, 0x00000012, 0x0000005B, 0x0000004F]
for i in range(len(target)):
input_xxx[i] ^= X[i]
a = bytes(input_xxx).decode()
print(a == '5t4t1c_An4lys1s_1s_E4sy_2_me!!!~')
结果
flag{5t4t1c_An4lys1s_1s_E4sy_2_me!!!~}
第9题:UPX脱壳(待完善)
去看视频。
IDA pro 反汇编程序,基础教程
查阅代码流程视图 / 汇编代码 (按键:空格)
将常量转换为X进制(按键:右击)
Hexadecimal:十六进制
Octal:八进制
Char:字符(按键:R)
查看所有字符串(按键:shift + F12)
交叉引用(按键:X)
复制字节数组(ida插件)
-
需要安装【LazyIDA】插件
https://github.com/P4nda0s/LazyIDA.git -
将【LazyIDA.py】放到 ida_pro 主目录下的 【\plugins】目录。
修改变量/函数名(按键:N)
文章来源:https://www.toymoban.com/news/detail-816168.html
调试技巧(步入/步过/直到返回/光标位置)
文章来源地址https://www.toymoban.com/news/detail-816168.html
常用快捷键
切换文本视图与图表视图 空格键
返回上一个操作地址 ESC
搜索地址和符号 G
对符号进行重命名 N
常规注释 冒号键
可重复注释 分号键
添加标签 Alt+M
查看标签 Ctrl+M
查看段的信息 Ctrl+S
查看交叉应用 X
查看伪代码 F5
搜索文本 Alt+T
搜索十六进 Alt+B
常用快捷键2
a:将数据转换为字符串
f5:一键反汇编
esc:回退键,能够倒回上一部操作的视图(只有在反汇编窗口才是这个作用,如果是在其他窗口按下esc,会关闭该窗口)
shift+f12:可以打开string窗口,一键找出所有的字符串,右击setup,还能对窗口的属性进行设置
ctrl+w:保存ida数据库
ctrl+s:选择某个数据段,直接进行跳转
ctrl+鼠标滚轮:能够调节流程视图的大小
x:对着某个函数、变量按该快捷键,可以查看它的交叉引用
g:直接跳转到某个地址
n:更改变量的名称
y:更改变量的类型
/ :在反编译后伪代码的界面中写下注释
\:在反编译后伪代码的界面中隐藏/显示变量和函数的类型描述,有时候变量特别多的时候隐藏掉类型描述看起来会轻松很多
;:在反汇编后的界面中写下注释
ctrl+shift+w:拍摄IDA快照
u:undefine,取消定义函数、代码、数据的定义
动态调试快捷键
F2:下断点
F3:打开程序
F4:运行到当前光标处(可应用在跳出 循坏)
F7:单步步入(进函数)
F8:单步 步过
F9;运行
F10:打开反汇编选项菜单快捷键
F12:暂时停止
Ctrl+F2:重新开始
Art+F2:结束跟踪
Shift+F2:打开附加选项窗口
Shift+F4:打开条件对话窗
Shift+F7:与F7相同,但是如果被调试程序发生异常而中止,调试器会首先尝试步入被调试程序指定的异常处理
Ctrl+F7:自动步入,在所有的函数调用中一条一条地执行命令,断点或异常时,自动 停止
Shift+F8与F8相同,但是如果被调试程序发生异常而中止,调试器会首先尝试步过被调试程序指定的异常处理
Ctrl+F8:自动步过,一条一条的执行命令,程序到达断点,或者发生异常时,自动步过过程都会停止
Shift+F9:与F9相同,但是如果被调试程序发生异常而中止,调试器会首先尝试执行被调试程序指定的异常处理
Ctrl+F9:执行直到返回,跟踪程序直到遇到返回,在此期间不进入子函数也不更新CPU数据。因为程序是一条一条命令执行的,所以速度可能会慢一些。按Esc键,可以停止跟踪。
Alt+F9:执行直到返回到用户代码段,跟踪程序直到指令所属于的模块不在系统目录中,在此期间不进入子函数也不更新CPU数据。按Esc键,可以停止跟踪。
Ctrl+F11:Run跟踪步入,一条一条执行命令,进入每个子函数调用,并把寄存器的信息加入到Run跟踪的存储数据中。Run跟踪不会同步更新CPU窗口。
Ctrl+F12 :Run跟踪。步过,一条一条执行命令,但是不进入子函数调用,并把寄存器的信息加入到Run跟踪的存储数据中。Run跟踪不会同步更新CPU窗口。
Art+C:快速回到主界面
Alt+B:显示断点窗口
Alt+E:显示模块窗口
Art+L:显示记录窗口
Alt+M:显示内存窗口
Alt+O:显示调试选项窗口
Alt+K:显示呼叫堆栈
Ctrl+E:编辑机器码
Ctrl+G:输入跟随地址
Ctrl+N:查找名称标志,选择你要下断的内容
Ctrl+S:打开查找命令次序窗口
Ctrl+P:显示补丁窗口
Ctrl+F9:返回到跟踪
Ctrl+F8:自动步进扫描,按F12可停止
Ctrl+F7:同上,功能略有不同
Ctrl+F6:回到OL主窗口
到了这里,关于《算法还原 - CTF》逆向exe程序 + ida Pro 反汇编分析伪C代码 + python算法复现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!