PETools查看概况
打开PETools,32位程序,Line number和Local Symbols已移除,Byte of machine word are reversed (high),入口点Section: [CODE], EP: 0x0000384C,因此没有壳。
IDA静态分析
载入IDA,把sub_40423C改成WndProc(随便百度一下,对照一个标准的mfc demo可以得知),sub_404024改成solve1,sub_403F60改成about_window,sub_402ED0改成extract_string(注:这是静态分析阶段的错误印象,之后会揭晓其含义)。
// 只有solve1是有意思的
LRESULT __stdcall WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
LRESULT v4; // ebx@1
v4 = 0;
switch ( Msg )
{
case 1u:
::lParam = (LPARAM)LoadBitmapA(hInstance, "software");
break;
case 2u:
DeleteObject(::wParam);
PostQuitMessage(0);
break;
case 0xFu:
hdc = BeginPaint(hWnd, &Paint);
MoveToEx(hdc, 20, 55, 0);
LineTo(hdc, 400, 55);
MoveToEx(hdc, 10, 214, 0);
LineTo(hdc, 400, 214);
MoveToEx(hdc, 10, 216, 0);
LineTo(hdc, 400, 216);
SelectObject(hdc, h);
break;
case 0x111u:
if ( lParam == dword_406538 )
solve1();
if ( lParam == dword_40653C )
about_window();
if ( lParam == dword_406540 )
SendMessageA(hWnd, 2u, 0, 0);
break;
default:
v4 = DefWindowProcA(hWnd, Msg, wParam, lParam);
break;
}
return v4;
}
// 这么长的代码,怎么可能静态查看呢?
int solve1()
{
int v0; // ebx@1
char *v1; // eax@3
int v2; // ebx@3
char *v3; // eax@5
int v4; // eax@5
int v5; // esi@7
int v6; // ebx@7
signed int v7; // eax@8
int v8; // ebx@10
int v9; // esi@10
char *v10; // ST08_4@12
char *v11; // eax@12
unsigned int v13; // [sp-18h] [bp-20h]@1
_UNKNOWN *v14; // [sp-14h] [bp-1Ch]@1
int *v15; // [sp-10h] [bp-18h]@1
signed __int32 v16; // [sp+0h] [bp-8h]@1
int v17; // [sp+4h] [bp-4h]@1
int savedregs; // [sp+8h] [bp+0h]@1
v17 = 0;
v16 = 0;
v15 = &savedregs;
v14 = &loc_4041BD;
v13 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v13);
sub_402DE8((int)&v17);
sub_402DE8((int)&v16);
v0 = GetWindowTextLengthA(dword_406534);
if ( v0 > 0 )
{
do
{
sub_402ED8((int)&v16, (int)dword_4041D4);
--v0;
}
while ( v0 );
}
v1 = get_402FA5_str((char *)v16);
GetWindowTextA(dword_406534, v1, 50);
v2 = GetWindowTextLengthA(dword_406530);
if ( v2 > 0 )
{
do
{
sub_402ED8((int)&v17, (int)dword_4041D4);
--v2;
}
while ( v2 );
}
v3 = get_402FA5_str((char *)v17);
GetWindowTextA(dword_406530, v3, 50);
// v4 = strlen(v17) = strlen(Name)
v4 = extract_string(v17);
// 求v8变量的关键循环
if ( v4 > 0 )
{
v5 = 0;
v6 = v4;
if ( v4 > 0 )
{
v7 = 1;
do
{
v5 += (v7 - 1) * *(_BYTE *)(v17 + v7 - 1);
++v7;
--v6;
}
while ( v6 );
}
v8 = dword_4050B8[v5 & 0xF];
sub_402F1C(v16, dword_404204);
// 对Register进行一些处理,返回值送给v9
v9 = sub_403FA0(v16);
sub_402E80((volatile signed __int32 *)&v17, (signed __int32)"Error");
sub_402E80(&v16, (signed __int32)"Invalid");
if ( v8 == v9 )
{
sub_402E80((volatile signed __int32 *)&v17, (signed __int32)dword_404220);
sub_402E80(&v16, (signed __int32)dword_40422C);
}
}
else
{
sub_402E80(&v16, (signed __int32)dword_4041E0);// 这里实际上就是Name为空,弹出“Your Name?”的
sub_402E80((volatile signed __int32 *)&v17, (signed __int32)"Error");
}
v10 = get_402FA5_str((char *)v17);
v11 = get_402FA5_str((char *)v16);
MessageBoxA_0(hWnd, v11, v10, 0);
__writefsdword(0, v13);
v15 = (int *)&loc_4041C4;
return sub_402E0C((int)&v16, 2);
}
动静结合
solve1实在过于难懂,只能上x64dbg了。
左上角几个蓝色按钮,只用到从左到右第3个“运行“按钮,第5、6个”步进“和”步过“按钮。步进就是要进入函数调用,步过则不进入函数。
在sub_402E80((volatile signed __int32 *)&v17, (signed __int32)"Error");对应的汇编代码CODE:0040414E mov edx, offset aError_0 ; "Error"处打断点,在程序界面随便输入些什么,然后点击x64dbg左上角的运行按钮。结合IDA的c伪代码和x64dbg动态调试得,变量v17就是Name,v16就是Register#。
前面说过extract_string我看不懂,但用x64dbg,打断点+“步过”,验证ABCDE、abcdefgh等数据可知,extract_string就是strlen。
同样是在动态调试过程中才得知,dword_404220就是OK,dword_40422C就是Thanks a lot。因此我们只需要关注v8和v9变量怎么算出来的。
v8就是这段代码,十分简单易懂的含义(记住变量v17就是Name)。
v5 = 0;
v6 = v4;
if ( v4 > 0 )
{
v7 = 1;
do
{
v5 += (v7 - 1) * *(_BYTE *)(v17 + v7 - 1);
++v7;
--v6;
}
while ( v6 );
}
v8 = dword_4050B8[v5 & 0xF];
/*
edx = strlen(Name)
CODE:0040410C lea ebx, [edx+edx]
CODE:0040410F add ebx, 63h
CODE:00404112 and ebx, 0FFFFh
CODE:00404118 and esi, 0FFFFh
CODE:0040411E shl ebx, 10h
CODE:00404121 add esi, ebx
CODE:00404123 mov ebx, esi
CODE:00404125 mov esi, ebx
*/
一开始我以为ida分析有错误,因为漏了下面那段汇编。但实际上没有错误,只不过是因为去除那段代码不影响结果,所以ida帮我们优化了。
v9则是下面的函数(把extract_string改成strlen):
int __usercall sub_403FA0@<eax>(signed __int32 a1@<eax>)
{
int v1; // ecx@1
int v2; // edx@1
signed int v3; // eax@1
int v4; // ebx@3
unsigned int v6; // [sp-Ch] [bp-18h]@1
_UNKNOWN *v7; // [sp-8h] [bp-14h]@1
int *v8; // [sp-4h] [bp-10h]@1
int v9; // [sp+4h] [bp-8h]@1
signed __int32 v10; // [sp+8h] [bp-4h]@1
int savedregs; // [sp+Ch] [bp+0h]@1
v9 = 0;
v10 = a1;
sub_402F90();
v8 = &savedregs;
v7 = &loc_404016;
v6 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v6);
sub_402E80((volatile signed __int32 *)&v9, v10);
v1 = strlen(v9) + 1;
v2 = 0;
v3 = 1;
do
v2 = *(_BYTE *)(v9 + v3++ - 1) + 10 * v2 - 48;
while ( v1 != v3 );
v4 = v2;
__writefsdword(0, v6);
v8 = (int *)&loc_40401D;
sub_402E0C((int)&v9, 2);
return v4;
}
这里涉及的函数有点多,不可能一个个看的,还是需要x64dbg:在v9 = sub_403FA0(v16)对应的汇编代码CODE:00404144 call sub_403FA0打断点,到断点处x64dbg点击一下“步过”按钮,然后查看v9(即esi寄存器)的值。
我们的结论很简单:只有以下代码有用:
v1 = strlen(v9) + 1;
v2 = 0;
v3 = 1;
do
v2 = *(_BYTE *)(v9 + v3++ - 1) + 10 * v2 - 48;
while ( v1 != v3 );
v4 = v2;
它就是把Register#从字符串转为int,并且无法正确处理带负号的情形。所以,我们只需要在IDA中shift+E导出dword_4050B8数组,即可开始编写注册机。
因为它不能处理负号,所以我们需要看一看Name对应的注册码转32位有符号数以后是不是负的,负的要丢弃。下面给出的几个Name都是合法的。
def calc(a):
ans = 0
for i in range(3,-1,-1):
ans = ans << 8 | a[i]
return ans
tbl = [
0x26, 0x11, 0x79, 0x19, 0x07, 0x10, 0x79, 0x19, 0x79, 0x19,
0x26, 0x11, 0x79, 0x19, 0x07, 0x10, 0x78, 0x56, 0x34, 0x12,
0xF0, 0xDE, 0xBC, 0x9A, 0x34, 0x34, 0x12, 0x12, 0x78, 0x78,
0x78, 0x78, 0xC6, 0xCC, 0xC6, 0xCC, 0x00, 0xCC, 0x00, 0xCC,
0xFF, 0xEF, 0xEF, 0xFF, 0x55, 0x55, 0xCC, 0xDD, 0x89, 0x87,
0x67, 0x67, 0xCC, 0xCB, 0xCE, 0xCE, 0xAB, 0x99, 0x88, 0x77,
0x66, 0x77, 0x33, 0x44
]
names = [
"iloveacm","ctfer","HANS","WSW","wsw",
"1234567890"
]
for name in names:
ans = 0
for i in range(len(name)):
ans += i * ord(name[i])
ans &= 0xf
ans <<= 2
print(name,[hex(v) for v in tbl[ans:ans+4]],calc(tbl[ans:ans+4]))
资源下载
- exe文件可以在这个视频的简介给出的链接找到。
- IDA6.6可以在jsj.aust.edu.cn的ctf toolkit找到。