在捕获块中使用开关语句的怪异行为
我目前正在研究.NET混淆器,并且在控制流相混淆中,我注意到了一种奇怪的行为。以某种方式,我生成的代码中的堆栈大小不一致。
原始IL:
{
._Statements:
IL_0000: ldstr "MethodD"
IL_0005: call System.Void System.Console::WriteLine(System.String)
Try{
._Statements:
IL_000a: ldc.i4 128736421
IL_000f: stloc.0
IL_0010: ldc.i4 12386123
IL_0015: stloc.1
IL_0016: ldstr "Result D: {0}"
IL_001b: ldloc.0
IL_001c: ldloc.1
IL_001d: add
IL_001e: box System.Int32
IL_0023: call System.String System.String::Format(System.String,System.Object)
IL_0028: call System.Void System.Console::WriteLine(System.String)
IL_002d: leave.s IL_0057
}
Handler{
._Statements:
IL_002f: ldstr "Something horrible happened! MethodD"
IL_0034: call System.Void System.Console::WriteLine(System.String)
IL_0039: call System.Void System.Console::WriteLine(System.Object)
IL_003e: ldc.i4 129387123
IL_0043: ldc.i8 1283769182434
IL_004c: stloc.2
IL_004d: conv.i8
IL_004e: ldloc.2
IL_004f: add
IL_0050: call System.Void System.Console::WriteLine(System.Int64)
IL_0055: leave.s IL_0057
}
._Statements:
IL_0057: call System.Void System.Console::WriteLine()
IL_005c: ret
}
控制流相混淆IL:
/* 0x00000480 727D010070 */ IL_0000: ldstr "MethodD"
/* 0x00000485 280800000A */ IL_0005: call void [mscorlib]System.Console::WriteLine(string)
.try
{
/* 0x0000048A 16 */ IL_000A: ldc.i4.0
/* 0x0000048B 0D */ IL_000B: stloc.3
/* 0x0000048C 2B00 */ IL_000C: br.s IL_000E
/* 0x0000048E 09 */ IL_000E: ldloc.3
// loop start (head: IL_000F)
/* 0x0000048F 4503000000000000000B00000016000000 */ IL_000F: switch (IL_0020, IL_002B, IL_0036)
/* 0x000004A0 20A55CAC07 */ IL_0020: ldc.i4 128736421
/* 0x000004A5 0A */ IL_0025: stloc.0
/* 0x000004A6 17 */ IL_0026: ldc.i4.1
/* 0x000004A7 0D */ IL_0027: stloc.3
/* 0x000004A8 09 */ IL_0028: ldloc.3
/* 0x000004A9 2BE4 */ IL_0029: br.s IL_000F
/* 0x000004AB 204BFFBC00 */ IL_002B: ldc.i4 12386123
/* 0x000004B0 0B */ IL_0030: stloc.1
/* 0x000004B1 18 */ IL_0031: ldc.i4.2
/* 0x000004B2 0D */ IL_0032: stloc.3
/* 0x000004B3 09 */ IL_0033: ldloc.3
/* 0x000004B4 2BD9 */ IL_0034: br.s IL_000F
// end loop
/* 0x000004B6 728D010070 */ IL_0036: ldstr "Result D: {0}"
/* 0x000004BB 06 */ IL_003B: ldloc.0
/* 0x000004BC 07 */ IL_003C: ldloc.1
/* 0x000004BD 58 */ IL_003D: add
/* 0x000004BE 8C0F000001 */ IL_003E: box [mscorlib]System.Int32
/* 0x000004C3 281500000A */ IL_0043: call string [mscorlib]System.String::Format(string, object)
/* 0x000004C8 280800000A */ IL_0048: call void [mscorlib]System.Console::WriteLine(string)
/* 0x000004CD DE43 */ IL_004D: leave.s IL_0092
} // end .try
catch [mscorlib]System.Exception
{
/* 0x000004CF 16 */ IL_004F: ldc.i4.0
/* 0x000004D0 1304 */ IL_0050: stloc.s V_4
/* 0x000004D2 2B00 */ IL_0052: br.s IL_0054
/* 0x000004D4 1104 */ IL_0054: ldloc.s V_4
// loop start (head: IL_0056)
/* 0x000004D6 45020000000000000016000000 */ IL_0056: switch (IL_0063, IL_0079)
/* 0x000004E3 72A9010070 */ IL_0063: ldstr "Something horrible happened! MethodD"
/* 0x000004E8 280800000A */ IL_0068: call void [mscorlib]System.Console::WriteLine(string)
/* 0x000004ED 280D00000A */ IL_006D: call void [mscorlib]System.Console::WriteLine(object)
/* 0x000004F2 17 */ IL_0072: ldc.i4.1
/* 0x000004F3 1304 */ IL_0073: stloc.s V_4
/* 0x000004F5 1104 */ IL_0075: ldloc.s V_4
/* 0x000004F7 2BDD */ IL_0077: br.s IL_0056
// end loop
/* 0x000004F9 20734AB607 */ IL_0079: ldc.i4 129387123
/* 0x000004FE 21E2289BE62A010000 */ IL_007E: ldc.i8 1283769182434
/* 0x00000507 0C */ IL_0087: stloc.2
/* 0x00000508 6A */ IL_0088: conv.i8
/* 0x00000509 08 */ IL_0089: ldloc.2
/* 0x0000050A 58 */ IL_008A: add
/* 0x0000050B 281600000A */ IL_008B: call void [mscorlib]System.Console::WriteLine(int64)
/* 0x00000510 DE00 */ IL_0090: leave.s IL_0092
} // end handler
/* 0x00000512 17 */ IL_0092: ldc.i4.1
/* 0x00000513 1305 */ IL_0093: stloc.s V_5
/* 0x00000515 2B00 */ IL_0095: br.s IL_0097
/* 0x00000517 1105 */ IL_0097: ldloc.s V_5
// loop start (head: IL_0099)
/* 0x00000519 45020000000000000001000000 */ IL_0099: switch (IL_00A6, IL_00A7)
/* 0x00000526 2A */ IL_00A6: ret
/* 0x00000527 280400000A */ IL_00A7: call void [mscorlib]System.Console::WriteLine()
/* 0x0000052C 16 */ IL_00AC: ldc.i4.0
/* 0x0000052D 1305 */ IL_00AD: stloc.s V_5
/* 0x0000052F 1105 */ IL_00AF: ldloc.s V_5
/* 0x00000531 2BE6 */ IL_00B1: br.s IL_0099
// end loop
/* 0x00000533 2A */ IL_00B3: ret
我对堆栈行为的分析(对于混淆IL):
IL_004F Pushes: 1 Pops: 0 Stack usage: 2 (2 because the CLR pushes the Exception object onto the stack before the first instruction in the catch block)
IL_0050 Pushes: 0 Pops: 1 Stack usage: 1
IL_0052 Pushes: 0 Pops: 0 Stack usage: 1
IL_0054 Pushes: 1 Pops: 0 Stack usage: 2
IL_0056 Pushes: 0 Pops: 1 Stack usage: 1
IL_0063 Pushes: 1 Pops: 0 Stack usage: 2
IL_0068 Pushes: 0 Pops: 1 Stack usage: 1
IL_006D Pushes: 0 Pops: 1 Stack usage: 0 (Exception object gets consumed)
IL_0072 Pushes: 1 Pops: 0 Stack usage: 1
IL_0073 Pushes: 0 Pops: 1 Stack usage: 0
IL_0075 Pushes: 1 Pops: 0 Stack usage: 1
IL_0077 Pushes: 0 Pops: 0 Stack usage: 1
back to IL_0056
IL_0056 Pushes: 0 Pops: 1 Stack usage: 0 (This consumes the int32 with value 1 from IL_0075, which jumps to IL_0079)
IL_0079 Pushes: 1 Pops: 0 Stack usage: 1
IL_007E Pushes: 1 Pops: 0 Stack usage: 2
IL_0087 Pushes: 0 Pops: 1 Stack usage: 1
IL_0088 Pushes: 1 Pops: 1 Stack usage: 1
IL_0089 Pushes: 1 Pops: 0 Stack usage: 2
IL_008A Pushes: 1 Pops: 2 Stack usage: 1
IL_008B Pushes: 0 Pops: 1 Stack usage: 0
IL_0090 Pushes: 0 Pops: all Stack usage: 0
DNSpy告诉我,在IL_0077处是不一致的堆栈大小,但是我的分析告诉我,一切都被消耗了。 有趣的是,当我在堆栈(IL_006D DUP)或IL_0075上欺骗异常对象时。 .NET运行时还拒绝运行代码并在我手动修复它之前引发“无效程序异常”。 IMO驱逐这些说明是一个错误,会破坏堆栈。有人可以告诉我为什么这种行为究竟会发生吗?
编辑05.07.2022 20:38-修复错别字并添加堆栈分析
I'm currently working on a .NET Obfuscator and in my control flow obfuscation I'm noticing a weird behaviour. Somehow there is an inconsistent stack size in my generated code.
Original IL:
{
._Statements:
IL_0000: ldstr "MethodD"
IL_0005: call System.Void System.Console::WriteLine(System.String)
Try{
._Statements:
IL_000a: ldc.i4 128736421
IL_000f: stloc.0
IL_0010: ldc.i4 12386123
IL_0015: stloc.1
IL_0016: ldstr "Result D: {0}"
IL_001b: ldloc.0
IL_001c: ldloc.1
IL_001d: add
IL_001e: box System.Int32
IL_0023: call System.String System.String::Format(System.String,System.Object)
IL_0028: call System.Void System.Console::WriteLine(System.String)
IL_002d: leave.s IL_0057
}
Handler{
._Statements:
IL_002f: ldstr "Something horrible happened! MethodD"
IL_0034: call System.Void System.Console::WriteLine(System.String)
IL_0039: call System.Void System.Console::WriteLine(System.Object)
IL_003e: ldc.i4 129387123
IL_0043: ldc.i8 1283769182434
IL_004c: stloc.2
IL_004d: conv.i8
IL_004e: ldloc.2
IL_004f: add
IL_0050: call System.Void System.Console::WriteLine(System.Int64)
IL_0055: leave.s IL_0057
}
._Statements:
IL_0057: call System.Void System.Console::WriteLine()
IL_005c: ret
}
Control flow obfuscated IL:
/* 0x00000480 727D010070 */ IL_0000: ldstr "MethodD"
/* 0x00000485 280800000A */ IL_0005: call void [mscorlib]System.Console::WriteLine(string)
.try
{
/* 0x0000048A 16 */ IL_000A: ldc.i4.0
/* 0x0000048B 0D */ IL_000B: stloc.3
/* 0x0000048C 2B00 */ IL_000C: br.s IL_000E
/* 0x0000048E 09 */ IL_000E: ldloc.3
// loop start (head: IL_000F)
/* 0x0000048F 4503000000000000000B00000016000000 */ IL_000F: switch (IL_0020, IL_002B, IL_0036)
/* 0x000004A0 20A55CAC07 */ IL_0020: ldc.i4 128736421
/* 0x000004A5 0A */ IL_0025: stloc.0
/* 0x000004A6 17 */ IL_0026: ldc.i4.1
/* 0x000004A7 0D */ IL_0027: stloc.3
/* 0x000004A8 09 */ IL_0028: ldloc.3
/* 0x000004A9 2BE4 */ IL_0029: br.s IL_000F
/* 0x000004AB 204BFFBC00 */ IL_002B: ldc.i4 12386123
/* 0x000004B0 0B */ IL_0030: stloc.1
/* 0x000004B1 18 */ IL_0031: ldc.i4.2
/* 0x000004B2 0D */ IL_0032: stloc.3
/* 0x000004B3 09 */ IL_0033: ldloc.3
/* 0x000004B4 2BD9 */ IL_0034: br.s IL_000F
// end loop
/* 0x000004B6 728D010070 */ IL_0036: ldstr "Result D: {0}"
/* 0x000004BB 06 */ IL_003B: ldloc.0
/* 0x000004BC 07 */ IL_003C: ldloc.1
/* 0x000004BD 58 */ IL_003D: add
/* 0x000004BE 8C0F000001 */ IL_003E: box [mscorlib]System.Int32
/* 0x000004C3 281500000A */ IL_0043: call string [mscorlib]System.String::Format(string, object)
/* 0x000004C8 280800000A */ IL_0048: call void [mscorlib]System.Console::WriteLine(string)
/* 0x000004CD DE43 */ IL_004D: leave.s IL_0092
} // end .try
catch [mscorlib]System.Exception
{
/* 0x000004CF 16 */ IL_004F: ldc.i4.0
/* 0x000004D0 1304 */ IL_0050: stloc.s V_4
/* 0x000004D2 2B00 */ IL_0052: br.s IL_0054
/* 0x000004D4 1104 */ IL_0054: ldloc.s V_4
// loop start (head: IL_0056)
/* 0x000004D6 45020000000000000016000000 */ IL_0056: switch (IL_0063, IL_0079)
/* 0x000004E3 72A9010070 */ IL_0063: ldstr "Something horrible happened! MethodD"
/* 0x000004E8 280800000A */ IL_0068: call void [mscorlib]System.Console::WriteLine(string)
/* 0x000004ED 280D00000A */ IL_006D: call void [mscorlib]System.Console::WriteLine(object)
/* 0x000004F2 17 */ IL_0072: ldc.i4.1
/* 0x000004F3 1304 */ IL_0073: stloc.s V_4
/* 0x000004F5 1104 */ IL_0075: ldloc.s V_4
/* 0x000004F7 2BDD */ IL_0077: br.s IL_0056
// end loop
/* 0x000004F9 20734AB607 */ IL_0079: ldc.i4 129387123
/* 0x000004FE 21E2289BE62A010000 */ IL_007E: ldc.i8 1283769182434
/* 0x00000507 0C */ IL_0087: stloc.2
/* 0x00000508 6A */ IL_0088: conv.i8
/* 0x00000509 08 */ IL_0089: ldloc.2
/* 0x0000050A 58 */ IL_008A: add
/* 0x0000050B 281600000A */ IL_008B: call void [mscorlib]System.Console::WriteLine(int64)
/* 0x00000510 DE00 */ IL_0090: leave.s IL_0092
} // end handler
/* 0x00000512 17 */ IL_0092: ldc.i4.1
/* 0x00000513 1305 */ IL_0093: stloc.s V_5
/* 0x00000515 2B00 */ IL_0095: br.s IL_0097
/* 0x00000517 1105 */ IL_0097: ldloc.s V_5
// loop start (head: IL_0099)
/* 0x00000519 45020000000000000001000000 */ IL_0099: switch (IL_00A6, IL_00A7)
/* 0x00000526 2A */ IL_00A6: ret
/* 0x00000527 280400000A */ IL_00A7: call void [mscorlib]System.Console::WriteLine()
/* 0x0000052C 16 */ IL_00AC: ldc.i4.0
/* 0x0000052D 1305 */ IL_00AD: stloc.s V_5
/* 0x0000052F 1105 */ IL_00AF: ldloc.s V_5
/* 0x00000531 2BE6 */ IL_00B1: br.s IL_0099
// end loop
/* 0x00000533 2A */ IL_00B3: ret
My analysis of stack behaviour (for obfuscated IL):
IL_004F Pushes: 1 Pops: 0 Stack usage: 2 (2 because the CLR pushes the Exception object onto the stack before the first instruction in the catch block)
IL_0050 Pushes: 0 Pops: 1 Stack usage: 1
IL_0052 Pushes: 0 Pops: 0 Stack usage: 1
IL_0054 Pushes: 1 Pops: 0 Stack usage: 2
IL_0056 Pushes: 0 Pops: 1 Stack usage: 1
IL_0063 Pushes: 1 Pops: 0 Stack usage: 2
IL_0068 Pushes: 0 Pops: 1 Stack usage: 1
IL_006D Pushes: 0 Pops: 1 Stack usage: 0 (Exception object gets consumed)
IL_0072 Pushes: 1 Pops: 0 Stack usage: 1
IL_0073 Pushes: 0 Pops: 1 Stack usage: 0
IL_0075 Pushes: 1 Pops: 0 Stack usage: 1
IL_0077 Pushes: 0 Pops: 0 Stack usage: 1
back to IL_0056
IL_0056 Pushes: 0 Pops: 1 Stack usage: 0 (This consumes the int32 with value 1 from IL_0075, which jumps to IL_0079)
IL_0079 Pushes: 1 Pops: 0 Stack usage: 1
IL_007E Pushes: 1 Pops: 0 Stack usage: 2
IL_0087 Pushes: 0 Pops: 1 Stack usage: 1
IL_0088 Pushes: 1 Pops: 1 Stack usage: 1
IL_0089 Pushes: 1 Pops: 0 Stack usage: 2
IL_008A Pushes: 1 Pops: 2 Stack usage: 1
IL_008B Pushes: 0 Pops: 1 Stack usage: 0
IL_0090 Pushes: 0 Pops: all Stack usage: 0
dnSpy is telling me that at IL_0077 is the inconsistent stack size, yet my analysis tells me that everything gets consumed.
Funnily enough when I either dupe the exception object on the stack (IL_006D dup) or IL_0075 it works. The .NET Runtime also refuses to run the code and throws an "Invalid program exception" before I manually fix it. IMO duping those instructions is an error and will corrupt the stack. Can anybody tell me why exactly this behaviour occurs?
Edit 05.07.2022 20:38 - Fix typos and add stack analysis
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这根本不是堆栈验证的工作原理。没有任何变量可能是或不会的,没有动态分析。分析仅查看堆栈上的对象是什么类型,无论您如何达到这一点,都需要堆栈是相同的。
switch
简单地分析为一堆条件br
跳。因此,在IL_0075
堆栈是空的,您将加载int
,然后跳回IL_0056
。但是分析已经传递iL_0056
,堆栈包含exception
int
,但现在只有int
,因此不一致的堆栈错误。仔细阅读:
This is simply not how stack verification works. There is no dynamic analysis of what any variables might or might not be. The analysis only looks at what types the objects on the stack are, and it requires the stack to be the same no matter how you reached that point.
A
switch
is simply analyzed as a bunch of conditionalbr
jumps. So atIL_0075
the stack is empty, you load anint
then jump back toIL_0056
. But analysis has already passedIL_0056
, at which the stack containedException
int
but now it's onlyint
, hence the inconsistent stack error.Take a careful read of ECMA-335 Section III 1.8.1.1: