【文章标题】: foxabu的KeygenMe1的简单分析
【文章作者】: hawking
【作者邮箱】: rich_hawking@hotmail.com
【软件名称】: NativeClrMixedKeygenMe.exe
【软件大小】: 52.0k
【下载地址】: http://bbs.pediy.com/showthread.php?s=&threadid=40887
【编写语言】: .net
【使用工具】: reflector PEBrowseDbg PEiD
【操作平台】: win2k sp4
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
foxabu兄台的这个KeygenMe没有加壳,甚至都没有作混淆处理,算法其实也超简单。正好适合我这样喜欢.net的菜鸟把玩。
一、静态分析
用reflector打开,很容易就定位到注册按钮的代码。
private unsafe void button_tryreg_Click(object sender, EventArgs e)
{
$ArrayType$$$BY0PP@D e$$$bypp@d;
string s = this.textBox_reg.Text;
string text = this.textBox_machineCode.Text;
lpMachineCode = (sbyte modopt(IsSignUnspecifiedByte)*) Marshal.StringToHGlobalAnsi(text).ToPointer();
int num2 = text.Length / 2;
if ((s.Length >= num2) && native_decode1((sbyte modopt(IsSignUnspecifiedByte)*) Marshal.StringToHGlobalAnsi(s).ToPointer(), (sbyte modopt(IsSignUnspecifiedByte)*) &e$$$bypp@d, 0xff))
{
MessageBox.Show("\u6ce8\u518c\u6210\u529f^_^");
this.button_tryreg.Enabled = false;
}
else
{
MessageBox.Show("\u597d\u50cf\u5931\u8d25\u4e86\u54e6,\u7ee7\u7eed\u54e6~\u5176\u5b9e\u5f88\u7b80\u5355\u7684");
}
}
从上面这段代码可以看出,注册码的长度必须大于等于机器码长度的一半。而且程序会把取到的注册码和一个Array数组及0xFF作为参数,传递给一个native方法native_decode1。
[return: MarshalAs(UnmanagedType.U1)]
[PreserveSig, MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType=MethodCodeType.Native), SuppressUnmanagedCodeSecurity]
public static unsafe bool modopt(CallConvCdecl) native_decode1(sbyte modopt(IsSignUnspecifiedByte)*, sbyte modopt(IsSignUnspecifiedByte)*, int);
可以看出这个native方法会返回一个bool值,从名称看是解码用的。要注册成功,这里必须得返回true。但是这个是native方法,所以reflector分析不出来这个方法具体做了些什么。
再看看reflector里还有没有什么有用的信息。
private void FormMain_Load(object sender, EventArgs e)
{
this.textBox_machineCode.Text = GetmachineCode();
}
internal static unsafe string GetmachineCode()
{
sbyte modopt(IsSignUnspecifiedByte)* numPtr2 = (sbyte modopt(IsSignUnspecifiedByte)*) Marshal.StringToHGlobalAnsi(Environment.MachineName + Environment.UserName).ToPointer();
sbyte modopt(IsSignUnspecifiedByte)* numPtr = (sbyte modopt(IsSignUnspecifiedByte)*) Marshal.AllocHGlobal(0xff).ToPointer();
native_encode(numPtr2, numPtr, 0xff);
IntPtr pUnk = new IntPtr(numPtr2);
Marshal.Release(pUnk);
IntPtr ptr = new IntPtr(numPtr);
return Marshal.PtrToStringAnsi(ptr);
}
[PreserveSig, MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType=MethodCodeType.Native), SuppressUnmanagedCodeSecurity]
public static unsafe void modopt(CallConvCdecl) native_encode(sbyte modopt(IsSignUnspecifiedByte)*, sbyte modopt(IsSignUnspecifiedByte)*, int);
这里我们可以看出机器码其实是将系统环境变量中的MachineName和UserName组合起来,再经由native方法native_encode编码得到的。
[return: MarshalAs(UnmanagedType.U1)]
internal static unsafe bool modopt(CallConvCdecl) decodeAndCompare(sbyte modopt(IsSignUnspecifiedByte)* str)
{
int num2 = stackalloc byte[__CxxQueryExceptionSize()];
IntPtr ptr2 = new IntPtr(lpMachineCode);
string text2 = Marshal.PtrToStringAnsi(ptr2);
IntPtr ptr = new IntPtr(str);
string s = Marshal.PtrToStringAnsi(ptr);
try
{
s = Encoding.ASCII.GetString(Convert.FromBase64String(s), 0, Convert.FromBase64String(s).Length);
}
catch when (?)
{
uint num = 0;
__CxxRegisterExceptionObject((void*) Marshal.GetExceptionPointers(), (void*) num2);
try
{
try
{
return false;
}
catch when (?)
{
}
if (num != 0)
{
throw;
}
}
finally
{
__CxxUnregisterExceptionObject((void*) num2, (int) num);
}
}
return ((s == text2) ? ((bool modopt(CallConvCdecl)) true) : ((bool modopt(CallConvCdecl)) false));
}
还有上面这个方法,从名称看是用于解码并比较的。具体作用就是将传入的参数s看作Base64String,通过Convert.FromBase64String转换成byte数组,然后再通过Encoding.ASCII编码成ASCII字符串。并将得到的这个字符串和机器码作比较,相同则返回true。
刚开始我就是因为没好好在reflector中找找有用的信息,忽略了这个方法,所以白花了不少冤枉功夫。
二、动态调试
用OD调试.net程序是一件很痛苦的事,这里我们使用PEBrowse进行动态调试。
打开PEBrowse,载入NativeClrMixedKeygenMe.exe。不断F5并忽略碰到的异常,直到程序完全跑起来。
菜单中选择View-->Modules Only,使左侧的树型控件只显示Modules。
依次展开左侧的模块列表NativeClrMixedKeygenMe.exe--->.NET Methods--->NativeClrMixedKeygenMe.FormMain
在button_tryreg_Click方法上下断点。输入注册码1234567890,点注册按钮
Disassembly of JITTED NativeClrMixedKeygenMe.FormMain::button_tryreg_Click (06000078) at 0x04AEB9C8
; Stack Size (in BYTES): 288 (0x00000120)
; Number of Parameters: 1
; Local Variables Size (in BYTES): 272 (0x00000110)
; Prologue Size (in BYTES): 35 (0x23)
; Standard Frame
0x4AEB9C8: 6A00 PUSH 0x0 断在这里
0x4AEB9CA: 6A00 PUSH 0x0
0x4AEB9CC: 6A00 PUSH 0x0
0x4AEB9CE: 68107A0C04 PUSH 0x40C7A10
0x4AEB9D3: E8D856510B CALL 0x100010B0
0x4AEB9D8: 55 PUSH EBP
0x4AEB9D9: 8BEC MOV EBP,ESP
0x4AEB9DB: 57 PUSH EDI
0x4AEB9DC: 56 PUSH ESI
0x4AEB9DD: 53 PUSH EBX
0x4AEB9DE: 81EC10010000 SUB ESP,0x110
0x4AEB9E4: C745F0C7C6C8C7 MOV DWORD PTR [EBP-0x10],0xC7C8C6C7; VAR:0x10
; end of prologue
0x4AEB9EB: 8BF1 MOV ESI,ECX
; IL_0000: ldarg.0
; IL_0001: ldfld textBox_reg
; IL_0006: callvirt System.Windows.Forms.TextBox::get_Text()
; IL_000B: stloc.1
;...............................................................
;...............................................................
; IL_0046: ldloca.s 0x05
; IL_0048: call System.IntPtr::ToPointer()
; IL_004D: ldloca.s 0x04
; IL_004F: ldc.i4 0x000000FF
; IL_0054: call native_decode1
; IL_0059: stloc.2
0x4AEBA41: 68FF000000 PUSH 0xFF
0x4AEBA46: 8D95F0FEFFFF LEA EDX,[EBP-0x110] ; VAR:0x110
0x4AEBA4C: 8B8DECFEFFFF MOV ECX,DWORD PTR [EBP-0x114]; VAR:0x114
0x4AEBA52: E8CD84F400 CALL 0x5A33F24 ;一路F10来到这里,然后F11跟进
0x4AEBA57: 25FF000000 AND EAX,0xFF
; IL_005A: ldloc.2
; IL_005B: brfalse.s IL_0076
0x4AEBA5C: 741B JZ 0x4AEBA79 ; (*+0x1D) 爆破的话修改这里
; IL_005D: ldstr ""
; IL_0062: call System.Windows.Forms.MessageBox::Show()
; IL_0067: pop
Disassembly of 0x004019D0 in NativeClrMixedKeygenMe.exe
; Section: .text
0x4019D0: 6AFF PUSH 0xFF
0x4019D2: 68D83C4000 PUSH 0x403CD8 ; .text:0x8B 0x54 0x24 0x08
0x4019D7: 64A100000000 MOV EAX,DWORD PTR FS:[0x0]
0x4019DD: 50 PUSH EAX
0x4019DE: 51 PUSH ECX
0x4019DF: 53 PUSH EBX
0x4019E0: 56 PUSH ESI
0x4019E1: 57 PUSH EDI
0x4019E2: A11CB04000 MOV EAX,DWORD PTR [0x40B01C]; .data:0x4E 0xE6 0x40 0xBB
0x4019E7: 33C4 XOR EAX,ESP
0x4019E9: 50 PUSH EAX
0x4019EA: 8D442414 LEA EAX,[ESP+0x14]
0x4019EE: 64A300000000 MOV DWORD PTR FS:[0x0],EAX
0x4019F4: 90 NOP
0x4019F5: 8B442424 MOV EAX,DWORD PTR [ESP+0x24]
0x4019F9: 50 PUSH EAX
0x4019FA: 8D4C2414 LEA ECX,[ESP+0x14]
0x4019FE: E81D010000 CALL 0x401B20 ;将传入的注册码转换成string
0x401A03: 33DB XOR EBX,EBX ;i = 0
0x401A05: 895C241C MOV DWORD PTR [ESP+0x1C],EBX
0x401A09: 8D4C2410 LEA ECX,[ESP+0x10]
0x401A0D: E8AE010000 CALL 0x401BC0 ;取转换后的string的长度
0x401A12: 85C0 TEST EAX,EAX
0x401A14: 8B742428 MOV ESI,DWORD PTR [ESP+0x28]
0x401A18: 7E29 JLE 0x401A43 ; (*+0x2B)
0x401A1A: 8B7C242C MOV EDI,DWORD PTR [ESP+0x2C]
0x401A1E: 8BFF MOV EDI,EDI
0x401A20: 53 PUSH EBX ; <==0x00401A41(*+0x21)
0x401A21: 8D4C2414 LEA ECX,[ESP+0x14]
0x401A25: E876010000 CALL 0x401BA0 ;取转换后的string中的某一位(substring(i,1))
0x401A2A: 3BFB CMP EDI,EBX
0x401A2C: 7E05 JLE 0x401A33 ; (*+0x7)
0x401A2E: 2AC3 SUB AL,BL ;substring(i,1) - i
0x401A30: 880433 MOV BYTE PTR [EBX+ESI],AL ;保存计算后的结果
0x401A33: 8D4C2410 LEA ECX,[ESP+0x10] ; <==0x00401A2C(*-0x7)
0x401A37: 83C301 ADD EBX,0x1 ;i++
0x401A3A: E881010000 CALL 0x401BC0
0x401A3F: 3BD8 CMP EBX,EAX
0x401A41: 7CDD JL 0x401A20 ; (*-0x21)
0x401A43: 8D4C2410 LEA ECX,[ESP+0x10] ; <==0x00401A18(*-0x2B)
0x401A47: E874010000 CALL 0x401BC0
0x401A4C: 56 PUSH ESI
0x401A4D: C6043000 MOV BYTE PTR [EAX+ESI],0x0
0x401A51: E86AFFFFFF CALL 0x4019C0 ;关键call F11跟进
0x401A56: 83C404 ADD ESP,0x4
0x401A59: 8AD8 MOV BL,AL
0x401A5B: C744241CFFFFFFFF MOV DWORD PTR [ESP+0x1C],0xFFFFFFFF
0x401A63: 8D4C2410 LEA ECX,[ESP+0x10]
0x401A67: E8F4000000 CALL 0x401B60
0x401A6C: 8AC3 MOV AL,BL
0x401A6E: 8B4C2414 MOV ECX,DWORD PTR [ESP+0x14]
0x401A72: 64890D00000000 MOV DWORD PTR FS:[0x0],ECX
0x401A79: 59 POP ECX
0x401A7A: 5F POP EDI
0x401A7B: 5E POP ESI
0x401A7C: 5B POP EBX
0x401A7D: 83C410 ADD ESP,0x10
0x401A80: C3 RET
在左侧模块列表中的decodeAndCompare方法上下断点。F5中断
Disassembly of JITTED decodeAndCompare (06000042) at 0x04AEBAC0
; Stack Size (in BYTES): 76 (0x0000004C)
; Number of Parameters: 0
; Local Variables Size (in BYTES): 60 (0x0000003C)
; Prologue Size (in BYTES): 48 (0x30)
; Standard Frame
0x4AEBAC0: 6A00 PUSH 0x0
0x4AEBAC2: 6A00 PUSH 0x0
0x4AEBAC4: 6A00 PUSH 0x0
0x4AEBAC6: 68D0379000 PUSH 0x9037D0
0x4AEBACB: E8E055510B CALL 0x100010B0
0x4AEBAD0: 55 PUSH EBP
0x4AEBAD1: 8BEC MOV EBP,ESP
0x4AEBAD3: 57 PUSH EDI
0x4AEBAD4: 56 PUSH ESI
0x4AEBAD5: 53 PUSH EBX
0x4AEBAD6: 83EC3C SUB ESP,0x3C
0x4AEBAD9: 33C0 XOR EAX,EAX
0x4AEBADB: 8945C0 MOV DWORD PTR [EBP-0x40],EAX; VAR:0x40
0x4AEBADE: 8945BC MOV DWORD PTR [EBP-0x44],EAX; VAR:0x44
0x4AEBAE1: 8965F0 MOV DWORD PTR [EBP-0x10],ESP; VAR:0x10
0x4AEBAE4: 33C0 XOR EAX,EAX
0x4AEBAE6: 8945E4 MOV DWORD PTR [EBP-0x1C],EAX; VAR:0x1C
0x4AEBAE9: C745B8C7C6C8C7 MOV DWORD PTR [EBP-0x48],0xC7C8C6C7; VAR:0x48
; end of prologue
0x4AEBAF0: 8BF1 MOV ESI,ECX
; IL_0000: call __CxxQueryExceptionSize
; IL_0005: localloc
; IL_0007: stloc.2
0x4AEBAF2: E85D84F400 CALL 0x5A33F54
0x4AEBAF7: 85C0 TEST EAX,EAX
0x4AEBAF9: 7421 JZ 0x4AEBB1C ; (*+0x23)
0x4AEBAFB: 83C003 ADD EAX,0x3
0x4AEBAFE: 83E0FC AND EAX,0xFC
0x4AEBB01: F7D8 NEG EAX
0x4AEBB03: 03C4 ADD EAX,ESP
0x4AEBB05: 7202 JB 0x4AEBB09 ; (*+0x4)
0x4AEBB07: 33C0 XOR EAX,EAX
0x4AEBB09: 852424 TEST DWORD PTR [ESP],ESP ; <==0x04AEBB05(*-0x4), 0x04AEBB18(*+0xF)
0x4AEBB0C: 8BD4 MOV EDX,ESP
0x4AEBB0E: 81EA00100000 SUB EDX,0x1000
0x4AEBB14: 8BE2 MOV ESP,EDX
0x4AEBB16: 3BE0 CMP ESP,EAX
0x4AEBB18: 73EF JAE 0x4AEBB09 ; (*-0xF)
0x4AEBB1A: 8BE0 MOV ESP,EAX
0x4AEBB1C: 8965F0 MOV DWORD PTR [EBP-0x10],ESP; VAR:0x10 ; <==0x04AEBAF9(*-0x23)
0x4AEBB1F: 8945D0 MOV DWORD PTR [EBP-0x30],EAX; VAR:0x30
; IL_0008: ldloca.s 0x06
; IL_000A: ldsfld lpMachineCode
; IL_000F: call System.IntPtr::.ctor()
0x4AEBB22: A170CA4000 MOV EAX,DWORD PTR [0x40CA70]
0x4AEBB27: 8945C4 MOV DWORD PTR [EBP-0x3C],EAX; VAR:0x3C
; IL_0014: ldloc.s 0x06
; IL_0016: call System.Runtime.InteropServices.Marshal::PtrToStringAnsi()
; IL_001B: stloc.s 0x05
0x4AEBB2A: 8B4DC4 MOV ECX,DWORD PTR [EBP-0x3C]; VAR:0x3C
0x4AEBB2D: E86657CEFF CALL 0x47D1298 ; (0x047D1298)
0x4AEBB32: 8945BC MOV DWORD PTR [EBP-0x44],EAX; VAR:0x44
; IL_001D: ldloca.s 0x04
; IL_001F: ldarg.0
; IL_0020: call System.IntPtr::.ctor()
0x4AEBB35: 8975C8 MOV DWORD PTR [EBP-0x38],ESI; VAR:0x38 ;跟到这里,双击ESI寄存器可以看见里面保存的是刚刚经过计算后的注册码字符串
; IL_0025: ldloc.s 0x04
; IL_0027: call System.Runtime.InteropServices.Marshal::PtrToStringAnsi()
; IL_002C: stloc.1
0x4AEBB38: 8B4DC8 MOV ECX,DWORD PTR [EBP-0x38]; VAR:0x38
0x4AEBB3B: E85857CEFF CALL 0x47D1298 ; (0x047D1298)
0x4AEBB40: 8945C0 MOV DWORD PTR [EBP-0x40],EAX; VAR:0x40
; IL_002D: call System.Text.Encoding::get_ASCII()
; IL_0032: ldloc.1
; IL_0033: call System.Convert::FromBase64String()
; IL_0038: ldc.i4.0
; IL_0039: ldloc.1
; IL_003A: call System.Convert::FromBase64String()
; IL_003F: ldlen
; IL_0040: callvirt System.Text.Encoding::GetString()
; IL_0045: stloc.1
; IL_0046: leave.s IL_0095
0x4AEBB43: 833D3014DA0100 CMP DWORD PTR [0x1DA1430],0x0
0x4AEBB4A: 7523 JNE 0x4AEBB6F ; (*+0x25)
0x4AEBB4C: B98CA32D04 MOV ECX,0x42DA38C
0x4AEBB51: E8C664E0FB CALL 0x8F201C
0x4AEBB56: 8BF0 MOV ESI,EAX
0x4AEBB58: 8BCE MOV ECX,ESI
0x4AEBB5A: BA9F4E0000 MOV EDX,0x4E9F
0x4AEBB5F: E8645779FF CALL 0x42812C8 ; (0x042812C8)
0x4AEBB64: 8D153014DA01 LEA EDX,[0x1DA1430]
0x4AEBB6A: E8B77E3875 CALL mscorwks.dll!DllUnregisterServerInternal + 0x0206 ; (0x79E73A26)
0x4AEBB6F: 8B353014DA01 MOV ESI,DWORD PTR [0x1DA1430] ; <==0x04AEBB4A(*-0x25)
0x4AEBB75: 8B4DC0 MOV ECX,DWORD PTR [EBP-0x40]; VAR:0x40
0x4AEBB78: FF1504D14803 CALL DWORD PTR [0x348D104]
0x4AEBB7E: 8BF8 MOV EDI,EAX
0x4AEBB80: 6A00 PUSH 0x0
0x4AEBB82: 8B4DC0 MOV ECX,DWORD PTR [EBP-0x40]; VAR:0x40
0x4AEBB85: FF1504D14803 CALL DWORD PTR [0x348D104]
0x4AEBB8B: FF7004 PUSH DWORD PTR [EAX+0x4]
0x4AEBB8E: 8BCE MOV ECX,ESI
0x4AEBB90: 8BD7 MOV EDX,EDI
0x4AEBB92: 8B01 MOV EAX,DWORD PTR [ECX]
0x4AEBB94: FF90DC000000 CALL DWORD PTR [EAX+0xDC]
0x4AEBB9A: 8945C0 MOV DWORD PTR [EBP-0x40],EAX; VAR:0x40
0x4AEBB9D: E99D000000 JMP 0x4AEBC3F
;...............................................................
;...............................................................
0x4AEBC8B: 5B POP EBX
0x4AEBC8C: 5E POP ESI
0x4AEBC8D: 5F POP EDI
0x4AEBC8E: 5D POP EBP
0x4AEBC8F: C3 RET
三、算法及注册机
算法超简单,只要了解.net的都不成问题。这里就不赘述了。
trcode.Text = System.Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(tmcode.Text)) ;
byte[] buffer = System.Text.Encoding.ASCII.GetBytes(trcode.Text);
for ( int i = 0 ; i < buffer.Length ; i++ )
{
if ( i < 0xFF )
{
buffer[i] = (byte)(buffer[i] + i) ;
}
}
trcode.Text = System.Text.Encoding.Default.GetString(buffer);
注册机见附件。
--------------------------------------------------------------------------------
【经验总结】
.net的软件如果没有经过混淆,托管方法用reflector就可以分析的八九不离十。再结合动态调试工具,就算其中夹杂着一
些非托管代码,难度也不是很大。
tankaiha兄的.net方面文章字字玑珠,像我这样的菜鸟应该仔细研读。关于PEBrowse可以参见tankaiha的《【原创】用
PEBrowse对.Net程序进行动态调试》一文(http://bbs.pediy.com/showthread.php?s=&threadid=24646)
--------------------------------------------------------------------------------
【版权声明】: 感谢看雪论坛、一蓑烟雨, 转载请注明作者并保持文章的完整, 谢谢!
2007年03月13日 10:56:49
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!