ISCC 2018 leftleftrightright题目下载
Download分析
解压后文件夹里只有一个leftleftrightright.exe文件,先运行一下发现什么都没有显示,随便输入一些字符串后会显示"try again!"。然后,拖到PEID里查壳
可以看到有一个UPX的壳,这个UPX的壳可以用UPX Unpacker脱壳
脱完壳后加载到IDA中分析,直接Shift+F12查找字符串,可以找到"try again!"和一些与flag有关的字符串,双击"try again!"这个字符串跳转到rdata段,然后在跳转到相对应的汇编代码段。
F5查看伪代码
int sub_401170() { int v0; // edx@1 int v1; // ebx@1 int v2; // edi@1 void **v3; // esi@1 int v4; // ecx@1 void **v5; // eax@2 void **v6; // edx@3 void *v7; // eax@5 char *v8; // ecx@6 unsigned int v9; // eax@8 void *v10; // eax@16 unsigned int v11; // eax@19 unsigned int v12; // edi@26 signed int v13; // eax@26 const char *v14; // edx@30 int v15; // eax@32 int v16; // ecx@32 unsigned int v17; // eax@36 unsigned int v18; // ecx@38 unsigned int v19; // eax@48 unsigned int v20; // esi@50 int v22; // [sp+10h] [bp-5Ch]@1 void *Memory; // [sp+14h] [bp-58h]@5 unsigned int v24; // [sp+28h] [bp-44h]@5 void *v25; // [sp+2Ch] [bp-40h]@1 int v26; // [sp+3Ch] [bp-30h]@1 unsigned int v27; // [sp+40h] [bp-2Ch]@1 void *Dst; // [sp+44h] [bp-28h]@1 unsigned int v29; // [sp+54h] [bp-18h]@1 unsigned int v30; // [sp+58h] [bp-14h]@1 int v31; // [sp+68h] [bp-4h]@1 v27 = 15; v26 = 0; LOBYTE(v25) = 0; v31 = 0; v30 = 15; v29 = 0; LOBYTE(Dst) = 0; LOBYTE(v31) = 1; sub_401F60(std::cin, &v25); v1 = v26; v2 = 0; v3 = (void **)v25; v4 = v26 - 1; v22 = v26 - 1; while ( v1 > 0 ) { --v1; v5 = &v25; if ( v1 & 2 ) { v6 = &Dst; if ( v27 >= 0x10 ) v5 = v3; LOBYTE(v6) = *((_BYTE *)v5 + v4); v7 = (void *)sub_401AD0((int)&Memory, (int)v6, &Dst); sub_401480(&Dst, v7); if ( v24 >= 0x10 ) { v8 = (char *)Memory; if ( v24 + 1 >= 0x1000 ) { if ( (unsigned __int8)Memory & 0x1F ) goto LABEL_35; v9 = *((_DWORD *)Memory - 1); if ( v9 >= (unsigned int)Memory ) goto LABEL_35; v8 = (char *)Memory - v9; if ( (char *)Memory - v9 < (char *)4 || (unsigned int)v8 > 0x23 ) goto LABEL_35; v8 = (char *)*((_DWORD *)Memory - 1); } j_free(v8); } v4 = v22-- - 1; } else { if ( v27 >= 0x10 ) v5 = v3; LOBYTE(v0) = *((_BYTE *)v5 + v2); v10 = (void *)sub_401AD0((int)&Memory, v0, &Dst); sub_401480(&Dst, v10); if ( v24 >= 0x10 ) { v8 = (char *)Memory; if ( v24 + 1 >= 0x1000 ) { if ( (unsigned __int8)Memory & 0x1F ) goto LABEL_35; v11 = *((_DWORD *)Memory - 1); if ( v11 >= (unsigned int)Memory ) goto LABEL_35; v8 = (char *)Memory - v11; if ( (char *)Memory - v11 < (char *)4 || (unsigned int)v8 > 0x23 ) goto LABEL_35; v8 = (char *)*((_DWORD *)Memory - 1); } j_free(v8); } v4 = v22; ++v2; } } v12 = v29; v13 = 29; if ( v29 < 0x1D ) v13 = v29; if ( sub_401090(v13) || v12 < 0x1D || (v14 = "flag is right!", v12 > 0x1D) ) v14 = "try again!"; v15 = sub_401BF0(std::cout, v14); std::basic_ostream>::operator<<(v15, sub_401E30); system("pause"); if ( v30 >= 0x10 ) { v8 = (char *)Dst; if ( v30 + 1 >= 0x1000 ) { if ( (unsigned __int8)Dst & 0x1F ) LABEL_35: invalid_parameter_noinfo_noreturn(v8); v17 = *((_DWORD *)v8 - 1); if ( v17 >= (unsigned int)v8 ) v17 = invalid_parameter_noinfo_noreturn(v8); v18 = (unsigned int)&v8[-v17]; if ( v18 < 4 ) v17 = invalid_parameter_noinfo_noreturn(v18); if ( v18 > 0x23 ) v17 = invalid_parameter_noinfo_noreturn(v18); v8 = (char *)v17; } j_free(v8); } v30 = 15; v29 = 0; LOBYTE(Dst) = 0; if ( v27 >= 0x10 ) { if ( v27 + 1 >= 0x1000 ) { if ( (unsigned __int8)v25 & 0x1F ) invalid_parameter_noinfo_noreturn(v16); v19 = (unsigned int)*(v3 - 1); if ( v19 >= (unsigned int)v3 ) v19 = invalid_parameter_noinfo_noreturn(v16); v20 = (unsigned int)v3 - v19; if ( v20 < 4 ) v19 = invalid_parameter_noinfo_noreturn(v16); if ( v20 > 0x23 ) v19 = invalid_parameter_noinfo_noreturn(v16); v3 = (void **)v19; } j_free(v3); } return 0; }
发现这段代码对输入的字符串进行了限制,长度必须为29,然后上面的那个循环就是对输入的字符串打乱。在if里发现了sub_401090(v13)函数,跟进去之后的伪代码
int __cdecl sub_401090(unsigned int a1) { int v1; // ecx@0 const char *v3; // esi@3 unsigned int v4; // edx@3 bool v5; // cf@7 unsigned __int8 v6; // al@9 unsigned __int8 v7; // al@11 unsigned __int8 v8; // al@13 if ( !a1 ) return 0; v3 = "s_imsaplw_e_siishtnt{g_ialt}F"; v4 = a1 - 4; if ( a1 < 4 ) { LABEL_6: if ( v4 == -4 ) return 0; } else { while ( *(_DWORD *)v1 == *(_DWORD *)v3 ) { v1 += 4; v3 += 4; v5 = v4 < 4; v4 -= 4; if ( v5 ) goto LABEL_6; } } v5 = *(_BYTE *)v1 < (const unsigned __int8)*v3; if ( *(_BYTE *)v1 != *v3 ) return -v5 | 1; if ( v4 != -3 ) { v6 = *(_BYTE *)(v1 + 1); v5 = v6 < v3[1]; if ( v6 != v3[1] ) return -v5 | 1; if ( v4 != -2 ) { v7 = *(_BYTE *)(v1 + 2); v5 = v7 < v3[2]; if ( v7 != v3[2] ) return -v5 | 1; if ( v4 != -1 ) { v8 = *(_BYTE *)(v1 + 3); v5 = v8 < v3[3]; if ( v8 != v3[3] ) return -v5 | 1; } } } return 0; }
找到了特殊字符串"s_imsaplw_e_siishtnt{g_ialt}F",后面的代码大致是把打乱后的字符串与整个特殊字符串进行对比。
然后开始用IDA进行动态调试,脱完壳的程序调试不了,只能拿原来加了壳的进行调试。
先找到start函数的入口,然后拉到后面可以看到一个红色高亮显示的"start endp ; sp-analysis failed",
在这一行的上面可以看到一个"jmp loc_1924C7"在这条代码的地方下一个断点,然后开始动态调试。
然后在程序窗口里输入29位无重复的字符串:0123456789abcdefghiklmnopqrst,输入完后不会显示,字符串还在缓存里面。
然后按F8进行单步调试。
调试到我们输入的字符串显示出来的时候,这个时候调试会停住,按下回车才会继续。我们在这里下一个断点,按下回车程序会直接结束。
这时候我们重新再调一遍。
运行到我们刚才下段点的地方的时候我们按F7加入这个函数里面。
然后继续F8单步调试,调试到程序窗口出现我们输入的字符串的时候按下回车继续,这时候会发现程序没停止,还是可以继续调试。
之后会进入一个循环,这个循环会把我们输入的字符串一个一个的放到Ecx寄存器里,一直按F8就可以了。
循环结束后继续运行一会后会进入一个新的循环,并且把Ecx中的字符串放到Esi中,这个循环就是打乱输入的那个循环。
这时候可以往下找一下有没有特殊的字符串,有的话可以在那里下一个断点跳出循环。
这时候我们就可以看打乱后的字符串了。把鼠标放在Ecx后面的数据地址上面,然后往下滚鼠标滑轮,可以看到全部的字符串。
然后把刚才的那个特殊字符串"s_imsaplw_e_siishtnt{g_ialt}F"进行位移,规则可以从这两个字符串"edfgcbhia9kl87mn65op43qr21st0","0123456789abcdefghiklmnopqrst"的位置关系发现。
C++脚本
#include#include #include
运行后的flag:Flag{this_was_simple_isnt_it}
Comments | NOTHING