0x00 RSA
这题比较烧脑,考察的是rsa算法,先简单了解一下rsa:
0x01
本篇文章的OD测试flag是“1234567890ABCDEF12345”,不再赘述。
这个程序先对一个给定的长字符串取出每个字符,依次转化成数据(‘A’——>A),然后经过两个函数sub_4019F0和sub_401940的处理,
先看看sub_4019F0函数,
int __usercall sub_4019F0@<eax>(int *a1@<ecx>, int *a2@<esi>)
{
int v2; // eax
int v3; // edx
char *v4; // edi
char *v5; // ecx
int v6; // ebx
int v7; // edi
__int64 v8; // kr00_8
bool v9; // zf
int v10; // eax
int v12; // [esp+10h] [ebp-8h]
int v13; // [esp+14h] [ebp-4h]
v2 = *a1;
v3 = 0;
v4 = (a1 + 1);
v5 = (a2 + 1);
if ( v2 )
{
v6 = 0;
if ( v2 > 0 )
{
v7 = v4 - v5;
v12 = v7;
v13 = v2;
while ( 1 )
{
v8 = v3 + 16i64 * *&v5[v7];
*v5 = v8;
v5 += 4;
v9 = v13-- == 1;
v3 = HIDWORD(v8);
if ( v9 )
break;
v7 = v12;
}
v6 = v2;
}
a2[v6 + 1] = v3;
*a2 = v6;
do
{
if ( a2[*a2 + 1] )
break;
v10 = *a2 - 1;
*a2 = v10;
}
while ( v10 >= 0 );
if ( ++*a2 > 128 )
{
*a2 = 128;
return 4099;
}
}
else
{
*a2 = 0;
}
return 0;
}
动调一下看出eax寄存器放的是下标,ebx放的是值,esi和ecx放的是内存单元00978680的地址。
数据窗口中跟随00978680,然后F7步入发现无实际作用。内存单元中值不曾改变
继续看下一个函数sub_401940,
int __usercall sub_401940@<eax>(int *a1@<eax>, unsigned int a2@<ecx>)
{
int v2; // edx
int v3; // ecx
int result; // eax
int v5; // esi
int v6; // ecx
_DWORD *v7; // edx
int v9; // ecx
int v10; // ecx
v2 = *a1;
if ( *a1 )
{
a1[1] += a2;
v5 = 1;
if ( a1[1] < a2 )
{
if ( v2 == 1 )
{
a1[2] = 1;
v5 = 2;
}
else
{
v6 = a1[2] + 1;
a1[2] = v6;
if ( !v6 )
{
v7 = a1 + 2;
while ( 1 )
{
++v5;
++v7;
if ( v5 >= *a1 )
break;
if ( (*v7)++ != -1 )
goto LABEL_15;
}
a1[v5 + 1] = 1;
}
LABEL_15:
++v5;
}
}
if ( v5 >= *a1 )
{
v9 = v5 - 1;
if ( v5 - 1 >= 127 )
v9 = 127;
*a1 = v9;
if ( v9 >= 0 )
{
do
{
if ( a1[*a1 + 1] )
break;
v10 = *a1 - 1;
*a1 = v10;
}
while ( v10 >= 0 );
}
++*a1;
}
result = v5 <= 128 ? 0 : 0x1003;
}
else
{
a1[1] = a2;
*a1 = 0;
do
{
if ( a1[*a1 + 1] )
break;
v3 = *a1 - 1;
*a1 = v3;
}
while ( v3 >= 0 );
++*a1;
result = 0;
}
return result;
}
ecx和ebx中放的是数值,eax放的是内存单元00978680的地址。
F7步入然后走出发现数据读入以968084为起始的内存单元,
既然只是对978680内存单元的操作,直接数据窗口跟随,然后F8步过,看数据窗口的变化即可,几次循环进行下来,发现sub_4019F0进行的其实是移位操作,方便sub_401940把数据填入978684。
直接循环外下断点按F9走出循环看数据窗口,不出所料,数据被读入了978684,与字符串的一致。
(分析到后面可以发现处理的那串字符原来是rsa的那个大素数N。)
0x02
在输入前还有一个sub_406230函数
用od调试这个函数前看一下寄存器,其中ebx里放的是某内存地址跟"www.51asm.com"所存放的地址的偏移地址,eax里放的是"www.51asm.com"字符串地址978EA0,F8步过发现这个函数没什么实际意义。
然后是对输入的flag必须要是字母和数字的限制。
然后跟程序开头把一串字符变成大数一样,把输入的flag转化成了数据,
不出意外存入的是00978A94内存空间,这就是flag对应的数据(16进制大数1234567890ABCDEF12345)地址。
运行完循环,数据窗口中跟随00978A90,flag对应的数据已经存入00978A94
0x03 sub_406330
其实看函数sub_406330,这个函数被两次调用,
第一次传入的参数是dword_448C98("www.51asm.com"字符串的地址)和byte_448EA0(输入的flag字符串的地址,注意区分输入的flag数据的地址)。
int __fastcall sub_406330(_DWORD *a1, _BYTE *a2)
{
size_t v2; // ebx
_BYTE *v3; // esi
int result; // eax
size_t v5; // edi
size_t v6; // ebx
size_t i; // eax
char v8; // cl
int v10; // [esp+14h] [ebp-624h]
char v11[520]; // [esp+18h] [ebp-620h] BYREF
int v12; // [esp+220h] [ebp-418h]
char v13[516]; // [esp+224h] [ebp-414h] BYREF
int v14; // [esp+428h] [ebp-210h]
int v15[130]; // [esp+42Ch] [ebp-20Ch] BYREF
v2 = 0;
v3 = a2;
if ( !Size )
return 4097;
if ( Size == 1 )
{
*a2 = 0;
return 4097;
}
if ( *a1 )
{
(sub_4018E0)();
(sub_401910)(16);
v10 = 0;
if ( v14 )
{
v5 = 1;
while ( 1 )
{
(sub_4020E0)(v11);
v3[++v5 - 2] = byte_407104[v14 != 0 ? v15[0] : 0];
++v2;
if ( v5 >= Size )
break;
v14 = v12;
if ( v12 )
{
if ( v12 > 0 )
{
qmemcpy(v15, v13, 4 * v12);
v3 = a2;
}
if ( v14 )
continue;
}
goto LABEL_16;
}
v10 = 0x1001;
}
LABEL_16:
Size = v2;
v6 = v2 - 1;
v3[v6 + 1] = 0;
for ( i = 0; v6 > i; ++i )
{
v8 = v3[v6];
v3[v6] = v3[i];
v3[i] = v8;
--v6;
}
result = v10;
}
else
{
*a2 = 48;
Size = 2;
result = 0;
}
return result;
}
od动调看一下它的原理,步入这个函数前依旧是看一下的各个寄存器的值,其中edx是输入的flag所在的地址,ecx是"www.51asm.com"的地址。
调试几次我们可以发现这个函数其实是将用户名ascii编码成数据,再转化成字符(例:‘A’——>65——>'65’窗口输出),最后整体倒置输出,
第二次的调用异曲同工,从内存单元中读取我们之前存入的数据然后编码,原封不动的把我们的输入输出来。
0x04 sub_403E30
这个函数也是调用了两次,&dword_448C98里面存放的是"www.51asm.com",而dword_448A90存放的是我们的flag对应的大数
这个函数的源码比较长,而且还有ollvm混淆,就不贴上源代码了
直接od按F8步过并数据窗口跟随看看它们对两个地址做了什么修改,数据没有发生改变
0x05 sub_401510
这个函数应该就是关键函数了,参考别的博主的wp后知道这个函数总体流程是先生成公钥e,每次循环(一共循环100次)e会自增,每一个更新后的e会用来做一次rsa加密然后与字符串“Happy Birthday Buddy”作比较,这个循环直到比较出相同(此时成功)或者100次每一次都没有加密出 “Happy Birthday Buddy”(此时便失败),才会退出循环。
unk_448EB5存放的应该就是提示正确的字符串,其实细心点可以发现 byte_448EA0 这个地址为 “加密后的地址 + 0x15” 处的起始地址,0x15刚好是字符串"Happy Birthday Buddy"的长度+1(因为根据strcmp函数的原理,要算上\0,strcmp函数才验证成功),也就是说我们输入的flag加密出来应该就包含有成功提示信息。
所以最后程序应该是加密出来一个“Happy Birthday Buddy\0\输入正确的提示”。这个提示其实是自定义的
int sub_401510()
{
int v0; // eax
int v1; // esi
int v2; // eax
char v4[1048]; // [esp+10h] [ebp-830h] BYREF
int v5; // [esp+428h] [ebp-418h] BYREF
int v6; // [esp+630h] [ebp-210h]
int v7[130]; // [esp+634h] [ebp-20Ch]
v0 = 1;
v7[1] = 0;
v7[0] = 17;
v6 = 1;
do
{
if ( v7[v0] )
break;
v6 = --v0;
}
while ( v0 >= 0 );
v6 = v0 + 1;
sub_4064D0(&v5); // 利用用户名www.51asm.com生成初始公钥e。
v1 = 0;
while ( 1 )
{
v2 = 0;
dword_44888C[0] = 1;
dword_448888 = 0;
do
{
if ( dword_44888C[v2] )
break;
dword_448888 = --v2;
}
while ( v2 >= 0 );
dword_448888 = v2 + 1;
if ( !sub_405310(&dword_448680, v4, 0) ) // 数据初始化,构造大数
sub_401000(v5, v4); // rsa加密,密文存入dword_448888
Size = 10240;
if ( dword_448888 )
{
sub_4062A0(); // 把dword_448888中的密文复制到byte_448EA0
}
else
{
memset(byte_448EA0, 0, 0x2800u);
Size = 0;
}
if ( !strcmp("Happy Birthday Buddy", byte_448EA0) )// 密文与"Happy Birthday Buddy"作比较
break;
sub_401940(1);
if ( ++v1 >= 100 ) // 如果满100次就退出循环
return 0;
}
return 1;
}
0x06 Crack
既然它做的是加密操作,我们反过来对字符串进行解密,后面的那个提示正确的字符串是自定义的,中间有个’\x0’用于strcmp比较函数用,所以我们设置密文为
"Happy Birthday Buddy\x0ok"
转换成大数就是
c=6B6F007964647542207961646874726942207970706148
OD动调得到初始公钥e(e是在从e到e+99间变化的)
初始e=9CA87DE3775787F7695F3F316E503600348AB6F58BEF375D0ED8F8BE84425FA7A6C3
还有程序一开始写入内存的公钥N
16进制:
N=A324F100182D501F6F6F78F397A3AA59641023D6A3DED8A4BF344F1E0FC71C188F4D
10进制形式:
N=4836049822470645700707675549457249771340211537787234319173530875014794666294415181
必要的数据有了就可以进行解密操作
N——>p,q
先用在线工具求出N的素因子
(用RSA-Tool分解太慢了,建议用在线因式分解工具或者yafu分解)
16进制:
p=b89eb7e0c9a568202f38b169d9d7e27b93
q=e2389b3c14e6423ee5eb9db5dac2559f
10进制:
p=62822928286347608648869628628072621308819
q=76979057716441943100156208562083628995999
e——>d
用RSA-Tool生成d,因为不知道哪个e与N的欧拉函数互素,所以需要e到e+99一个个尝试,发现其实e+2就已经满足了。
得到私钥d
d=3AD75813052BC545DAC589519734FF0972200E8E31DFA08DE50D15CFA2667132E4E1
Encrypt
网站或者工具解密一下(由于密文有自定义部分,所以答案不唯一):
274964845CCC8AAA8A0CD62B0971A23954F741240EEDD43A02F79DA2B7372D68C80C
得到flag
flag{274964845CCC8AAA8A0CD62B0971A23954F741240EEDD43A02F79DA2B7372D68C80C}
参考wp:
- https://bbs.pediy.com/thread-214562-1.htm
- https://bbs.pediy.com/thread-258108.htm