jocker
这道题我原来写过,但是当时半生不熟的写完了,现在再来一遍感觉非常丝滑
堆栈不平衡
进入程序后一个大大的报错
sp寄存器不平衡,原来只是知道碰见这个报错应该怎么办,正好借着这次分享的机会研究一下
先打开ida左上角的Options然后勾选上Stack pointer就可以看到
在调用一个函数之后sp就变为负数了
这是因为push和pop不匹配 为什么不匹配呢? 进入这个函数看看
看了这个函数我们可以知道是这个函数出现了问题,它retf的数据为 0x4904 正常函数执行之后要返回调用这个函数的下一条指令,这样我们就知道了这里为什么会出现错误
如何修复
回到main函数部分,在出现错误的指令的前一条指令处alt+k修改堆栈
改为0即可
如法炮制几次即可修复,这样我们就得到了完美的main函数开始分析
分析main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char input[50]; // [esp+12h] [ebp-96h] BYREF
char Destination[80]; // [esp+44h] [ebp-64h] BYREF
DWORD flOldProtect; // [esp+94h] [ebp-14h] BYREF
size_t input_len; // [esp+98h] [ebp-10h]
int i; // [esp+9Ch] [ebp-Ch]
__main();
puts("please input you flag:");
if ( !VirtualProtect(encrypt, 200u, 4u, &flOldProtect) )
exit(1);
scanf("%40s", input);
input_len = strlen(input);
if ( input_len != 24 ) 输入长度要为24
{
puts("Wrong!");
exit(0);
}
strcpy(Destination, input);
wrong(input);
omg(input);
for ( i = 0; i <= 186; ++i )
*((_BYTE *)encrypt + i) ^= 0x41u; 将entry指向的区域每个字节进行异或(smc)
if ( encrypt(Destination) )
finally(Destination);
return 0;
}
VirtualProtect函数用于更改指定进程的虚拟内存区域的保护属性。它可以使指定的虚拟内存区域变为可读,可写,可执行,也可设置为不可访问,从而保护该区域
BOOL VirtualProtect(
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flNewProtect,
[out] PDWORD lpflOldProtect
);
第一个参数为要更改保护属性的起始页地址
第二个参数为更改的大小
第三个为内存保护选项、
第四个为指向变量的指针,这个变量接受指定区域的第一页的先前访问保护值
这个函数在这里就是确保指向的区域的保护属性已经被去除(如果在保护会返回1)
char *__cdecl wrong(char *a1)
{
char *result; // eax
int i; // [esp+Ch] [ebp-4h]
for ( i = 0; i <= 23; ++i )
{
result = &a1[i];
if ( (i & 1) != 0 )
a1[i] -= i; 如果为当前数字编号为奇数
else
a1[i] ^= i; 为偶数
}
return result;
}
值得一提的是这里前面就把字符串的地址赋予result是有用(某种情况下)
int __cdecl omg(char *a1)
{
int v2[24]; // [esp+18h] [ebp-80h] BYREF
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] ) 更改后的input要与v2的一致
v4 = 0;
}
if ( v4 == 1 )
return puts("hahahaha_do_you_find_me?");
else
return puts("wrong ~~ But seems a little program");
}
unsigned char ida_chars[] =
{
0x66,0x6B,0x63,0x64,0x7F,0x61,0x67,0x64, 0x3B,0x56,0x6B,0x61,0x7B,
0x26,0x3B,0x50,0x63,0x5F,0x4D,0x5A,0x71,0x0C,0x37 0x66
};
脚本:
#include<stdio.h>
int main()
{
char ida[24] =
{
0x66,0x6B,0x63,0x64,0x7F,0x61,0x67,0x64, 0x3B,0x56,0x6B,0x61,0x7B, 0x26,0x3B,0x50,0x63,0x5F,0x4D,0x5A,0x71,0x0C,0x37,0x66
};
int i;
int j;
for(i=0;i<24;i++)
{
if(i%2==0) //即为偶数
{
ida[i] ^= i;
}
else
{
ida[i] += i;
}
}
for(j=0;j<24;j++)
{
printf("%c",ida[j]);
}
return 0;
}
得到flag
flag{fak3_alw35_sp_me!!} 假的
下个断点让smc自己解除
smc:我自己的理解就是对某段代码继续加密(最常见的就是异或),一般可以直接动调解除
还可以用idapython
sea = ScreenEA()
c = 1
for i in range(0,186):
c = Byte(sea+i)^0x41
PatchByte(sea+i,c)
在这里下个断点然后输入假flag
重新创建函数
这里的Buffer是 hahahaha_do_you_find_me?
还有finall函数
找到403040里面是
unsigned char ida_chars[] =
{
0x0E, 0x0D, 0x09,
0x06, , 0x13,
0x05, 0x58, 0x56,
, 0x3E, , 0x06,
0x0C, 0x3C, 0x1F,
0x57, 0x14,
0x6B, 0x57 0x59,
};
解密entry里面的 脚本:
#include<stdio.h>
int main()
{
char ida[18] =
{
0x0E,0x0D,0x09,0x06,0x13,0x05,0x58,0x56,0x3E,0x06,0x0C,0x3C, 0x1F,0x57,0x14,0x6B,0x57,0x59
};
char ha[18]="hahahaha_do_you_find_me?";
char flag[18]={0};
int i;
int j;
for(i=0;i<18;i++)
{
flag[i]=ha[i]^ida[i];
}
for(j=0;j<18;j++)
{
printf("%c",flag[j]);
}
return 0;
}
flag{d07abccf8a410
提示还有一部分flag 查看finally函数
int __cdecl sub_40159A(char *a1)
{
unsigned int v1; // eax
char v3[9]; // [esp+13h] [ebp-15h] BYREF
int v4; // [esp+1Ch] [ebp-Ch]
strcpy(v3, "%tp&:");
v1 = time(0);
srand(v1);
v4 = rand() % 100;
v3[6] = 0;
*&v3[7] = 0;
if ( (v3[v3[5]] != a1[v3[5]]) == v4 )
return puts("Really??? Did you find it?OMG!!!");
else
return puts("I hide the last part, you will not succeed!!!");
}
坏了,这是真的随机加密
看了一下别人的wp发现是纯脑洞 根据flag的最后1位为} 再猜测是异或然后解密
singal
这道题用angr执行会非常快,angr我认为就是把程序的可能都来一遍看看什么可以
不介绍angr直接使用
脚本:
import angr
p=angr.Project("./signal.exe")
state=p.factory.entry_state()
sm=p.factory.simgr(state)
sm.explore(find=0x4017A5,avoid=0x4016ED)
if sm.found:
find_state=sm.found[0]
print(find_state.posix.dumps(0))
没错就是这么神奇!
就这样结束感觉太水了,再来点angr的笔记吧
angr
angr是什么
就是帮你寻找flag的工具(bushi)
angr是一个支持多处理架构用于二进制分析的工具,它提供了动态符号执行的能力和多种静态分析的能力。
符号执行,初始参数用变量替换,模拟程序的执行流程,维护执行到各个位置时的状态
什么是符号执行?
符号执行就是利用符号状态和符号执行来得到我们的目标单位
一句话概括:angr可以利用自身特性约束求解最终走到我们想要的位置
那么如何约束求解呢?
就比如如果你想要走到print("Path-2")的位置 就需要满足
自身特性是什么?
利用符号替代自身就是angr的特性
剩下的就自己去学习吧haha