前言:
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1582287-1-1.html
立帖为证!--------记录学习的点点滴滴
0x1 收集信息
1.首先,还是查壳,然后看一看文件格式,是windows平台还是linux平台的,是32位的,还是64位的。
2.将文件复制到kali中,然后将ida目录下的linux_server64也复制过来,防止一会远程调试,先备用。
3.运行一下程序,提示输入key,随便输入后提示wrong。
4.现在小菜鸟已经收集到一下信息了:
1)目标是64位linux程序,没有加壳
2)程序流程是输入key进行校验
3)我的目的是找到正确的key
0x2 静态分析
1.将程序拖进ida,找到main函数,F5可以看到伪代码:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
__pid_t v3; // eax
size_t v4; // rax
ssize_t v5; // rbx
bool v6; // al
bool bCheckPtrace; // [rsp+13h] [rbp-1BDh]
ssize_t numRead; // [rsp+18h] [rbp-1B8h]
ssize_t numReada; // [rsp+18h] [rbp-1B8h]
char bufWrite[200]; // [rsp+20h] [rbp-1B0h] BYREF
char bufParentRead[200]; // [rsp+F0h] [rbp-E0h] BYREF
unsigned __int64 v12; // [rsp+1B8h] [rbp-18h]
v12 = __readfsqword(0x28u);
bCheckPtrace = detectDebugging();
if ( pipe(pParentWrite) == -1 )
exit(1);
if ( pipe(pParentRead) == -1 )
exit(1);
v3 = fork();
if ( v3 != -1 )
{
if ( v3 )
{
close(pParentWrite[0]);
close(pParentRead[1]);
while ( 1 )
{
printf("Input key : ");
memset(bufWrite, 0, sizeof(bufWrite));
gets(bufWrite);
v4 = strlen(bufWrite);
v5 = write(pParentWrite[1], bufWrite, v4);
if ( v5 != strlen(bufWrite) )
printf("parent - partial/failed write");
do
{
memset(bufParentRead, 0, sizeof(bufParentRead));
numReada = read(pParentRead[0], bufParentRead, 0xC8uLL);
v6 = bCheckPtrace || checkDebuggerProcessRunning();
if ( !v6 && checkStringIsNumber(bufParentRead) && atoi(bufParentRead) )
{
puts("True");
if ( close(pParentWrite[1]) == -1 )
exit(1);
exit(0);
}
puts("Wrong !!!\n");
}
while ( numReada == -1 );
}
}
close(pParentWrite[1]);
close(pParentRead[0]);
while ( 1 )
{
memset(bufParentRead, 0, sizeof(bufParentRead));
numRead = read(pParentWrite[0], bufParentRead, 0xC8uLL);
if ( numRead == -1 )
break;
if ( numRead )
{
if ( !childCheckDebugResult()
&& bufParentRead[0] == 123
&& strlen(bufParentRead) == 42
&& !strncmp(&bufParentRead[1], "53fc275d81", 0xAuLL)
&& bufParentRead[strlen(bufParentRead) - 1] == 125
&& !strncmp(&bufParentRead[31], "4938ae4efd", 0xAuLL)
&& confuseKey(bufParentRead, 42)
&& !strncmp(bufParentRead, "{daf29f59034938ae4efd53fc275d81053ed5be8c}", 0x2AuLL) )
{
responseTrue();
}
else
{
responseFalse();
}
}
}
exit(1);
}
exit(1);
}
2.看上去代码很多,先不慌,分段拆解,看这里面居然还有反调试checkDebuggerProcessRunning,找到wrong,看这里,似乎有点晕,提示wrong就循环,提示true就直接退出,那就说明这个if不该进去,直接把这段代码pass掉。
if ( !v6 && checkStringIsNumber(bufParentRead) && atoi(bufParentRead) )
{
puts("True");
if ( close(pParentWrite[1]) == -1 )
exit(1);
exit(0);
}
puts("Wrong !!!\n");
3.write和read是读文件和写文件,这里pParentWrite存储着我输入的字符串,pParentRead读取字符串。
printf("Input key : ");
memset(bufWrite, 0, sizeof(bufWrite));
gets(bufWrite);
v4 = strlen(bufWrite);
v5 = write(pParentWrite[1], bufWrite, v4);
memset(&s, 0, 0xC8uLL);
v9 = read(pParentRead[0], &s, 0xC8uLL);
4.继续往后翻,这里将pParentWrite里面的内容复制给s。
memset(&s, 0, 0xC8uLL);
v8 = read(pParentWrite[0], &s, 0xC8uLL);
5.继续,看到关键的比较了,学数组的时候小菜鸟知道数组名就是数组首地址,这里先判断第一个字符是不是{,接着判断长度是不是42,然后晕圈,v12和v13咋来的?
if ( !(unsigned __int8)childCheckDebugResult()
&& s == 123
&& strlen(&s) == 42
&& !strncmp(v12, "53fc275d81", 0xAuLL)
&& *(&s + strlen(&s) - 1) == 125
&& !strncmp(v13, "4938ae4efd", 0xAuLL)
&& (unsigned __int8)confuseKey(&s, 42) == 1
&& !strncmp(&s, "{daf29f59034938ae4efd53fc275d81053ed5be8c}", 0x2AuLL) )
{
responseTrue();
}
else
{
responseFalse();
}
6.去反汇编窗口,按空格键,找到提示字符串,看到如下图所示信息,怀疑我的IDA F5反编译不准确,硬着头皮啃汇编吧,流程图中的汇编上面都是rbp+bufParentRead(我输入的字符串)进行比较,还是没看懂,重启IDA,出现一个提示框全勾上再说。
if ( !childCheckDebugResult()
&& bufParentRead[0] == 123
&& strlen(bufParentRead) == 42
&& !strncmp(&bufParentRead[1], "53fc275d81", 0xAuLL)
&& bufParentRead[strlen(bufParentRead) - 1] == 125
&& !strncmp(&bufParentRead[31], "4938ae4efd", 0xAuLL)
&& confuseKey(bufParentRead, 42)
&& !strncmp(bufParentRead, "{daf29f59034938ae4efd53fc275d81053ed5be8c}", 0x2AuLL) )
7.这下子逻辑清晰了,输入字符串第一个字符必须是{,倒数第一个字符必须是},然后下标1-10对应的字符必须是"53fc275d81",下标31-40对应的字符必须是4938ae4efd,接着调用confuseKey函数将字符串变换,最后和{daf29f59034938ae4efd53fc275d81053ed5be8c}进行比较,看看confuseKey函数:
bool __cdecl confuseKey(char *szKey, int iKeyLength)
{
char szPart1[15]; // [rsp+10h] [rbp-50h] BYREF
char szPart2[15]; // [rsp+20h] [rbp-40h] BYREF
char szPart3[15]; // [rsp+30h] [rbp-30h] BYREF
char szPart4[15]; // [rsp+40h] [rbp-20h] BYREF
unsigned __int64 v7; // [rsp+58h] [rbp-8h]
v7 = __readfsqword(0x28u);
*(_QWORD *)szPart1 = 0LL;
*(_DWORD *)&szPart1[8] = 0;
*(_WORD *)&szPart1[12] = 0;
szPart1[14] = 0;
*(_QWORD *)szPart2 = 0LL;
*(_DWORD *)&szPart2[8] = 0;
*(_WORD *)&szPart2[12] = 0;
szPart2[14] = 0;
*(_QWORD *)szPart3 = 0LL;
*(_DWORD *)&szPart3[8] = 0;
*(_WORD *)&szPart3[12] = 0;
szPart3[14] = 0;
*(_QWORD *)szPart4 = 0LL;
*(_DWORD *)&szPart4[8] = 0;
*(_WORD *)&szPart4[12] = 0;
szPart4[14] = 0;
if ( iKeyLength != 42 )
return 0;
if ( !szKey )
return 0;
if ( strlen(szKey) != 42 )
return 0;
if ( *szKey != 123 )
return 0;
strncpy(szPart1, szKey + 1, 0xAuLL);
strncpy(szPart2, szKey + 11, 0xAuLL);
strncpy(szPart3, szKey + 21, 0xAuLL);
strncpy(szPart4, szKey + 31, 0xAuLL);
memset(szKey, 0, 0x2AuLL);
*szKey = 123;
strcat(szKey, szPart3);
strcat(szKey, szPart4);
strcat(szKey, szPart1);
strcat(szKey, szPart2);
szKey[41] = 125;
return 1;
}
8.前面都没啥,主要看strncpy赋值语句和strcat字符串连接语句,可以看到把字符串分为4段,每段长度位10,然后按照3412的顺序进行连接,先看看替换后的字符串:
daf29f5903 第3段
4938ae4efd 第4段
53fc275d81 第1段
053ed5be8c 第2段
9.调整顺序为1234得到key,{53fc275d81053ed5be8cdaf29f59034938ae4efd},运行程序验证一下,成功。
0x3 总结
1.去攻防世界提交:flag{53fc275d81053ed5be8cdaf29f59034938ae4efd},居然提示错了,然后试{53fc275d81053ed5be8cdaf29f59034938ae4efd}还是错了,最后输入53fc275d81053ed5be8cdaf29f59034938ae4efd终于对了。
2.这道题的逻辑其实很简单,主要是前面看伪代码懵圈了,然后想着IDA动态调试看参数的时候有点晕,一直断不下来,不知道是不是那个反调试的原因,真是奇了怪了。