查壳
无壳
IDA
v11为输入,在sub_401000函数进行处理。中间暂时看不懂。最后让v13字符串逐位与37异或,最后得到的字符串为 ‘you_know_how_to_remove_junk_code’
sub_401000:
a1:v15 ; a2:v13(结尾比对) ; a3:v11(输入) ; a4:v11的长度
这部分看吐了……真就和最后对比字符串一样,要自己排除垃圾代码,下文注释掉的代码基本没用……
后半部分是关键,将输入的字符串的ASCⅡ值作为索引,检索table: byte_574E40的值,然后v14通过位移 与 或 操作,每次在末尾加上检索值得二进制形式(每次取后6位添加),四个字符一组。
int __usercall sub_561000@<eax>(unsigned int *a1@<edx>, _BYTE *a2@<ecx>, unsigned __int8 *a3, unsigned int a4)
{
int v4; // ebx
unsigned int v5; // eax
int v6; // ecx
unsigned __int8 *v7; // edi
int v8; // edx
bool v9; // zf
unsigned __int8 v10; // cl
char v11; // cl
_BYTE *v12; // esi
unsigned int v13; // ecx
int v14; // ebx
unsigned __int8 v15; // cl
char v16; // dl
int v20; // [esp+14h] [ebp-4h]
unsigned int v21; // [esp+14h] [ebp-4h]
int i; // [esp+24h] [ebp+Ch]
// v4 = 0;
// v5 = 0;
// v6 = 0;
// v20 = 0;
// if ( !a4 )
// return 0;
// v7 = a3;
// do
// {
// v8 = 0;
// v9 = v5 == a4;
// if ( v5 < a4 )
// {
// do
// {
// if ( a3[v5] != 32 )
// break;
// ++v5;
// ++v8;
// }
// while ( v5 < a4 );
// v9 = v5 == a4;
// }
// if ( v9 )
// break;
// if ( a4 - v5 >= 2 && a3[v5] == 13 && a3[v5 + 1] == 10 || (v10 = a3[v5], v10 == 10) )
// {
// v6 = v20;
// }
// else
// {
// if ( v8 )
// return -44;
// if ( v10 == 61 && (unsigned int)++v4 > 2 )
// return -44;
// if ( v10 > 0x7Fu )
// return -44;
// v11 = byte_574E40[v10];
// if ( v11 == 127 || (unsigned __int8)v11 < 0x40u && v4 )
// return -44;
// v6 = ++v20;
// }
// ++v5;
// }
// while ( v5 < a4 );
// if ( !v6 )
// return 0;
v12 = a2;
v13 = ((unsigned int)(6 * v6 + 7) >> 3) - v4;
if ( a2 && *a1 >= v13 ) // a1:v15 ; a2:v13(结尾比对) ; a3:v11(输入) ; a4:v11的长度
{
v21 = 3;
v14 = 0;
for ( i = 0; v5; --v5 ) // v5 输入字符串的长度
{
v15 = *v7; // v7是输入字符串,其字符挨个传给v15
if ( *v7 != 13 && v15 != 10 && v15 != 32 )
{
v16 = byte_574E40[v15]; // v15是输入的ASCⅡ值,这里作为base表的索引
v21 -= v16 == 64;
v14 = v16 & 0x3F | (v14 << 6); // 取v16二进制的后6位,v14整体左移6位。通过或运算缝合,实现v14+=v16二进制的后六位
if ( ++i == 4 ) //四个字符串切割成3位
{
i = 0;
if ( v21 )
*v12++ = BYTE2(v14);
if ( v21 > 1 )
*v12++ = BYTE1(v14);
if ( v21 > 2 )
*v12++ = v14;
}
}
++v7;
}
*a1 = v12 - a2;
return 0;
}
*a1 = v13;
return -42;
}
解题
菜鸡读到junk_code是真要命,读了好久好久才知道大概,有些细节没搞懂怎么回事,但是连蒙带猜流程差不多捋清了:
输入字符串,进行base64解密(没错就是解密,这里本来看不到table的,但是字符串四转三二进制保存有点眼熟),最后每位字符和37异或得到给出的字符串
EXP
import base64
arr='you_know_how_to_remove_junk_code'
arr2=''
flag=''
for s in arr:
arr2+=chr(ord(s)^37)
flag=base64.b64encode(arr2)
print(flag)
flag
XEpQek5LSlJ6TUpSelFKeldASEpTQHpPUEtOekZKQUA=
坑&填坑
1.下图判断我动调的结果是v21不变,前两次*v12++是junk code,到那时这样的话v12一次性就存入了4个字符的二进制数据,后面也没相应分割操作,迷惑
#提问时的理解:
多次测试发现v21始终是定值3,整个大if判断逻辑就是
每4次输入的值传给v12(v12声明为byte* v12),
然后v12指针指向下一位
解决:
既然是取v12的地址,*v12++的性质很像数组每次添加元素,那我就跳转到其内存地址观察三次if操作的变化:
结果就是将4个输入的字符串的6位二进制字符串分割成三份。虽然看不懂代码咋实现三等份分割效果的,但是通过动调结果能知道函数作用。
2.关键函数的末尾并没有对a2(传入的v13)进行操作or返回,但是最后确实v13是该函数变化后的值
解决:
a2是v13传入的形参,函数开头介绍了其值储存在ecx里
动调观察ecx,每一次if操作都将分割后的数据储存到ecx中:
动调观察汇编:
这里随便输入测试,此时ebx是 0xB6399C
.text:00561139 mov ecx, ebx //ebx存储输入的4字符对表转换后的值
.text:0056113B shr ecx, 10h //10h为 01 0000 shr向右位移,
//这里是ecx右移16进制的4位(取 B6)
.text:0056113E mov [esi], cl //cl取ecx的低位,仍然是B6存入esi
.text:00561140 inc esi //inc +1指令,
//奇怪的是这里执行完esi清0
.text:00561141
.text:00561141 loc_561141: ; CODE XREF: sub_561000+137↑j
.text:00561141 cmp edx, 1 //这里是if判断
.text:00561144 jbe short loc_56114E
.text:00561146 mov ecx, ebx //故技重施,将转换后的值全给ecx
.text:00561148 shr ecx, 8 //ecx右移十六进制的两位,剩B639
.text:0056114B mov [esi], cl //取ecx低位,是39
.text:0056114D inc esi
.text:0056114E
.text:0056114E loc_56114E: ; CODE XREF: sub_561000+144↑j
.text:0056114E cmp edx, 2
.text:00561151 jbe short loc_561156
.text:00561153 mov [esi], bl
.text:00561155 inc esi
3.看汇编解决了四个字符串是如何变成三个的,但是又有新问题出现:每次esi进行inc +1操作后为啥都清零了…… 保存的值又存在哪里,v13是ecx寄存器,但是在函数结束前只有ebx完整保存着未分割前的值,既不知道谁调用了ebx,也不知道分割后的值如何跑进后面的v13
4.最后答案是用base64,但base64 表从头到尾都没看到过,唯一一个根据表操作的表还被隐藏了,看不出来是不是base64……
其它补充
SHR SHL SAR:
SAR:算数右移(可用作有符号除法)
用符号位补空位
eg:1000 0000 算术右移一位: 1100 0000
SHR:逻辑右移
用0补空位,原最低位移入进位标志CF
格式:SHR cnt 其中cnt可以是数字(二进制移动位数等于cnt的十进制数值);cnt也可以是cl。
eg:1000 0000 逻辑右移一位: 0100 0000
SHL:逻辑左移
基本同上