TL;DR
WCTF2018的一道.NET
逆向題硝全,利用了.NET
的即時(shí)編譯(JIT)
機(jī)制批糟,動(dòng)態(tài)修改了Func3函數(shù)的函數(shù)指針赚爵,寫了一段脫殼代碼進(jìn)去,異或解密了了一段匯編并將Func4和Func5的函數(shù)指針指過去村怪,從而修改了Func4和Func5的邏輯秽浇。
Solve
注:得到了FAKEFLAG的話可以跳過PART1
PART1 FAKEFLAG
使用dnSpy反編譯程序,發(fā)現(xiàn)在Main
函數(shù)中調(diào)用了Lib.Verify
函數(shù)驗(yàn)證了讀取的字符串:
private static void Main()
{
Console.Write("Enter your flag: ");
if (Lib.Verify(Console.ReadLine().Trim()))
{
Console.WriteLine("Great :)");
return;
}
Console.WriteLine("Wrong :(");
}
進(jìn)一步觀察Lib.Verify函數(shù):
public static bool Verify(string s)
{
byte[] bytes = Encoding.ASCII.GetBytes(s);
if (bytes.Length != 32)
{
return false;
}
byte[] array = Lib.Func2();
Lib.Func3(array, bytes);
Lib.Func4(array, bytes);
Lib.Func5(array, bytes);
for (int i = 0; i < 32; i++)
{
if (bytes[i] != array[3104 + i])
{
return false;
}
}
return true;
}
我們的輸入保存在bytes
中甚负,首先驗(yàn)證了其長(zhǎng)度要為32柬焕,然后通過Func2
函數(shù)生成了一個(gè)byte數(shù)組array
,之后依次調(diào)用了Func3
梭域、Func4
斑举、Func5
三個(gè)函數(shù)對(duì)bytes
和array
進(jìn)行了操作,最后將bytes
的最終值與array
后面的一段進(jìn)行了比較碰辅。
于是我們首先看Func2函數(shù)是如何生成array
數(shù)組的:
public static byte[] Func2()
{
AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider();
aesCryptoServiceProvider.BlockSize = 128;
aesCryptoServiceProvider.KeySize = 128;
aesCryptoServiceProvider.Mode = CipherMode.CBC;
aesCryptoServiceProvider.Padding = PaddingMode.PKCS7;
byte[] result;
using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(Resources.bin)))
{
aesCryptoServiceProvider.IV = binaryReader.ReadBytes(16);
aesCryptoServiceProvider.Key = binaryReader.ReadBytes(16);
using (CryptoStream cryptoStream = new CryptoStream(binaryReader.BaseStream, aesCryptoServiceProvider.CreateDecryptor(), CryptoStreamMode.Read))
{
using (MemoryStream memoryStream = new MemoryStream())
{
cryptoStream.CopyTo(memoryStream);
result = memoryStream.ToArray();
}
}
}
return result;
}
可以看出懂昂,這段代碼其中就是讀取了一段資源(Resources.bin),并對(duì)其進(jìn)行了AES解密没宾。解密用的IV和key也在其中凌彬。我們可以自己寫代碼解密,也可以直接動(dòng)態(tài)調(diào)試拿到循衰。
之后我們?cè)倏戳硗馊齻€(gè)Func:
public static void Func3(byte[] b, byte[] x)
{
byte[] array = new byte[32];
for (int i = 0; i < 32; i++)
{
array[i] = (byte)b.Skip(i * 32).Take(32).Zip(x, (byte x1, byte x2) => (int)(x1 * x2)).Sum();
}
Array.Copy(array, x, 32);
}
public static void Func4(byte[] b, byte[] x)
{
byte[] array = new byte[32];
for (int i = 0; i < 32; i++)
{
array[i] = (byte)b.Skip(1024 + i * 32).Take(32).Zip(x, (byte x1, byte x2) => (int)(x1 * x2)).Sum();
}
Array.Copy(array, x, 32);
}
public static void Func5(byte[] b, byte[] x)
{
byte[] array = new byte[32];
for (int i = 0; i < 32; i++)
{
array[i] = (byte)(b.Skip(2048 + i * 32).Take(32).Zip(x, (byte x1, byte x2) => (int)(x1 * x2)).Sum() + (int)b[3072 + i]);
}
Array.Copy(array, x, 32);
}
這三個(gè)函數(shù)的邏輯都非常清晰铲敛,在32次循環(huán)中,每次從b數(shù)組(即之前aes解密出的array
)讀取32個(gè)byte会钝,分別與x(即我們輸入的bytes
)的對(duì)應(yīng)byte做乘法伐蒋,用最終的sum構(gòu)建一個(gè)新數(shù)組,最后復(fù)制回x
數(shù)組迁酸。所以很明顯先鱼,這就是將array
中的一段數(shù)據(jù)作為一個(gè)32*32
矩陣,將我們的輸入作為一個(gè)32*1
的向量奸鬓,做了一個(gè)矩陣乘法焙畔。注意Func5最后在結(jié)果上又加了一個(gè)向量。
有了這些邏輯串远,我們就很容易實(shí)現(xiàn)出一個(gè)逆過程宏多,求出三個(gè)矩陣的逆儿惫,把過程反過來進(jìn)行一次就好了。然而伸但,最后我們的程序得到了一段FAKEFLAGFAKEFLAGFAKEFLAGFAKEFLAG
肾请,嘗試在程序中輸入也無法通過。那么是哪里出了問題呢更胖?
PART2 - REAL FLAG
為了尋找問題所在铛铁,我們首先嘗試用DNSPY動(dòng)態(tài)調(diào)試,發(fā)現(xiàn)Func3的返回值雖然與我們預(yù)期的一樣函喉,但是在步入Func3時(shí)卻沒有進(jìn)Func3避归,反而是進(jìn)了Func2荣月。另外Func4和Func5中的斷點(diǎn)都沒有進(jìn)去管呵,函數(shù)的返回結(jié)果也與我們的預(yù)期不一樣。
于是我們猜測(cè)哺窄,一定有某個(gè)地方對(duì)這幾個(gè)函數(shù)做了手腳捐下。于是我們從頭把代碼看了一遍,最終在Resources.Resources()
中看到了一段可疑的代碼:
unsafe static Resources()
{
IntPtr intPtr = ldftn(Func) - 16;
long num = *intPtr;
IntPtr intPtr2 = ldftn(Func) - 8;
long num2 = *intPtr2;
ref long ptr = ldftn(Func) - 16;
IntPtr intPtr3 = ldftn(Func) + 5;
long num3 = (long)(*(intPtr3 + 1));
ptr = *(intPtr3 + (IntPtr)(((int)(*(intPtr3 + 2)) << 3) + 3)) + (num3 << 3);
ref long ptr2 = ldftn(Func) - 8;
object obj = *(ldftn(Func) - 16);
object obj2;
for (;;)
{
obj2 = obj;
if (*obj2 == 5)
{
break;
}
obj = obj2 + 16;
}
ptr2 = *(obj2 + 8);
long num4 = *(ldftn(Func) - 8);
*num4 = 6293447916875450697L;
long num5 = num4 + 8L;
*num5 = 996842507592L;
long num6 = num5 + 8L;
*num6 = -5023708761407594752L;
long num7 = num6 + 8L;
*num7 = 2247216228701921188L;
long num8 = num7 + 8L;
*num8 = 5195160555404404409L;
long num9 = num8 + 8L;
*num9 = 543045289092056715L;
long num10 = num9 + 8L;
*num10 = 612363414786457928L;
long num11 = num10 + 8L;
*num11 = 5245003925894368584L;
long num12 = num11 + 8L;
*num12 = 3816147333L;
long num13 = num12 + 8L;
object obj3 = *(ldftn(Func) - 16);
object obj4;
for (;;)
{
obj4 = obj3;
if (*obj4 == 6)
{
break;
}
obj3 = obj4 + 16;
}
*(obj4 + 8) = *(ldftn(Func) - 8);
object obj5 = *(ldftn(Func) - 16);
object obj6;
for (;;)
{
obj6 = obj5;
if (*obj6 == 7)
{
break;
}
obj5 = obj6 + 16;
}
*(obj6 + 8) = *(ldftn(Func) - 8) + 89L;
object obj7 = *(ldftn(Func) - 16);
object obj8;
for (;;)
{
obj8 = obj7;
if (*obj8 == 8)
{
break;
}
obj7 = obj8 + 16;
}
*(obj8 + 8) = *(ldftn(Func) - 8) + 170L;
*intPtr2 = num2;
*intPtr = num;
}
首先我們可以注意到9個(gè)奇怪的long萌业,這些數(shù)值被寫到了*(ldftn(Func) - 8)
開始的一段地址中坷襟。
之后的3個(gè)for循環(huán),分別從*(ldftn(Func) - 16)
開始生年,找到06婴程、07和08開始的16個(gè)byte,并把后8byte分別改為*(ldftn(Func) - 8)
抱婉、*(ldftn(Func) - 8) + 89
和*(ldftn(Func) - 8) + 170
的地址档叔。聯(lián)想到之前Func3、Func4蒸绩、Func5函數(shù)無法正常調(diào)試且邏輯被修改衙四,我們就可以自然地想到就是在這里修改了這三個(gè)函數(shù)的指針。而且06患亿、07传蹈、08也分別對(duì)應(yīng)了Func3、Func4步藕、Func5 token的最后一個(gè)byte:
進(jìn)一步分析函數(shù)指針被修改后指向的位置惦界,F(xiàn)unc3的指針被改到了
*(ldftn(Func) - 16)
,而這就是之前9個(gè)long寫到的地方咙冗≌赐幔可以猜測(cè)這9個(gè)long就是Func3函數(shù)的實(shí)際代碼。我們?cè)儆?jì)算一下這9個(gè)long的總長(zhǎng)度:9*8=72
乞娄,而Func4指向的是*(ldftn(Func) - 8) + 89
瞬逊,顯然是在修改的9個(gè)long的后面显歧。那這部分的代碼是從哪里來的呢?為了搞清具體邏輯确镊,我們?cè)龠M(jìn)行動(dòng)態(tài)調(diào)試士骤,把斷點(diǎn)下在Resources構(gòu)造函數(shù)后面(不知道為什么Resources函數(shù)里面無法下斷點(diǎn)),在內(nèi)存中搜索找到這9個(gè)long的地址:我們發(fā)現(xiàn)了一段疑似的匯編代碼蕾域。重新調(diào)試一次拷肌,把斷點(diǎn)設(shè)在Resources函數(shù)前面,將這段內(nèi)存的內(nèi)容前后對(duì)比一下:
發(fā)現(xiàn)確實(shí)就是修改了這段匯編代碼的開頭旨巷,并且把三個(gè)函數(shù)的指針指到了這段匯編代碼中巨缘。
于是我們Dump下這段內(nèi)存,用IDA查看采呐,發(fā)現(xiàn)這段匯編其實(shí)就是Func2函數(shù)(那個(gè)AES解密函數(shù))若锁,只是修改了開頭的一部分。但是很顯然這兩部分并無法拼合在一起斧吐,而且也無法想象Func4和Func5其實(shí)只是執(zhí)行Func2的一部分又固,所以這段代碼應(yīng)該不是最終運(yùn)行的代碼。
于是我們這次把斷點(diǎn)下載Func4和Func5執(zhí)行后煤率,然后再嘗試Dump這段匯編:
發(fā)現(xiàn)后面的代碼被更新了仰冠,然后Func4和Func5的入口看起來也比較正常了。這次我們?cè)儆肐DA打開蝶糯,邏輯就十分清晰了:
Func3前面一段代碼不會(huì)影響返回結(jié)果(實(shí)際上是脫殼代碼洋只,后面會(huì)詳細(xì)分析這部分),只在最后做了一個(gè)矩陣乘法昼捍,跟原來的Func3邏輯是一樣的识虚。
Func4對(duì)矩陣做了轉(zhuǎn)置,并進(jìn)行了5次矩陣乘法端三,并在最后將把向量里的值都異或了0x5A舷礼。
Func5中則是將向量異或了Func4中矩陣的前32個(gè)Byte,再進(jìn)行矩陣乘法郊闯。這兩個(gè)操作循環(huán)重復(fù)10次妻献。
整體邏輯跟之前相差不大,貼一份robin大佬的解題代碼团赁,運(yùn)行得到真Flag:
flag{884RN4SoqUt9Cu87pVSPG0ndA8}
I=Integers(256)
def func1(mat,input):
return mat*input.T
def func1_inv(mat,input):
return (~mat)*input
def func2(mat,input):
for i in range(5):
input=mat.T*input
input=[int(x[0]) for x in input]
return [x^^0x5a for x in input]
def func2_inv(mat,input):
input=[x^^0x5a for x in input]
input=matrix(I,input)
input=input.T
for i in range(5):
input=(~mat.T)*input
return input
def func3(mat,input,iv):
for i in range(10):
input=[x^^y for x,y in zip(input,iv)]
input=matrix(I,input)
input=mat*input.T
input=[int(x[0]) for x in input]
return input
def func3_inv(mat,input,iv):
for i in range(10):
input=matrix(I,input)
input=(~mat)*(input.T)
input=[int(x[0]) for x in input]
input=[x^^y for x,y in zip(input,iv)]
return input
f=open("bin","rb")
data=f.read()
f.close()
m1=map(ord,data[:1024])
m2=map(ord,data[1024:1024*2])
m3=map(ord,data[1024*2:1024*3])
m4=map(ord,data[1024*3:1024*3+32])
m5=map(ord,data[1024*3+32:1024*32+64])
input=[48]*32
input=matrix(I,input)
ma1=[m1[i*32:i*32+32] for i in range(32)]
ma2=[m2[i*32:i*32+32] for i in range(32)]
ma3=[m3[i*32:i*32+32] for i in range(32)]
ma1=matrix(I,ma1)
ma2=matrix(I,ma2)
ma3=matrix(I,ma3)
rm1= func1(ma1,input)
assert( input.T == func1_inv(ma1,rm1))
rm2=func2(ma2, rm1)
assert( func2_inv(ma2,rm2)==rm1)
rm3=func3(ma3,rm2,[int(x) for x in ma2[0]])
assert( func3_inv(ma3,rm3,[int(x) for x in ma2[0]])==rm2)
re2=func3_inv(ma3,m5,[int(x) for x in ma2[0]])
re1=func2_inv(ma2,re2)
re0=func1_inv(ma1,re1)
re=[int(x[0]) for x in re0]
flag=map(chr,re)
print "".join(flag)
Appendix
這道題雖然解決了育拨,但其中還有一些細(xì)節(jié)我們還是沒有搞懂。例如為什么.NET程序的函數(shù)最終指向一段匯編欢摄,F(xiàn)unc5和Func6的匯編代碼是從哪里出來等熬丧。在比賽后,我們又進(jìn)行了一點(diǎn)簡(jiǎn)單的探索怀挠。
.NET即時(shí)編譯機(jī)制
.NET中的C#析蝴、VB.NET害捕、F#等語(yǔ)言的編譯過程并不是像C/C++一樣直接編譯出原生代碼,而是首先編譯成IL中間語(yǔ)言闷畸,在運(yùn)行時(shí)尝盼,再由JIT (Just-In-Time) compiler根據(jù)需要,將IL編譯成匯編代碼佑菩。我們運(yùn)行的.NET的exe中實(shí)際上保存的就是IL盾沫,而不是匯編代碼:
實(shí)際執(zhí)行中,函數(shù)的調(diào)用需要通過Method Table來找到method的地址殿漠。在程序運(yùn)行的開始赴精,函數(shù)指針指向一個(gè)Stub。在函數(shù)被調(diào)用時(shí)绞幌,JIT compiler會(huì)編譯這個(gè)method并將原本指向Stub的指針指向編譯好的代碼蕾哟。于是,之后調(diào)用該函數(shù)就直接執(zhí)行這段匯編代碼啊奄。
我們可以使用WinDbg工具來幫助我們理解這一過程:
> !DumpMT -md 0007ff9ec805b00
EEClass: 00007ff9ec992068
Module: 00007ff9ec804118
Name: WCTF2018Rev.Lib
mdToken: 0000000002000003
File: D:\ctf\wctf\WCTF2018Rev_Release.exe
BaseSize: 0x18
ComponentSize: 0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
00007ffa4ae3c000 00007ffa4a997fb8 PreJIT System.Object.ToString()
00007ffa4aea40f0 00007ffa4a997fc0 PreJIT System.Object.Equals(System.Object)
00007ffa4af14490 00007ffa4a997fe8 PreJIT System.Object.GetHashCode()
00007ffa4ae6f390 00007ffa4a998000 PreJIT System.Object.Finalize()
00007ff9ec9100c0 00007ff9ec805af8 NONE WCTF2018Rev.Lib..ctor()
00007ff9ec910500 00007ff9ec805a98 JIT WCTF2018Rev.Lib.Verify(System.String)
00007ff9ec910098 00007ff9ec805aa8 NONE WCTF2018Rev.Lib.Func(Int32)
00007ff9ec9100a0 00007ff9ec805ab8 NONE WCTF2018Rev.Lib.Func2()
00007ff9ec9100a8 00007ff9ec805ac8 NONE WCTF2018Rev.Lib.Func3(Byte[], Byte[])
00007ff9ec9100b0 00007ff9ec805ad8 NONE WCTF2018Rev.Lib.Func4(Byte[], Byte[])
00007ff9ec9100b8 00007ff9ec805ae8 NONE WCTF2018Rev.Lib.Func5(Byte[], Byte[])
觀察Verify函數(shù)中渐苏,在運(yùn)行Func2之前的Method Table掀潮。內(nèi)存中Method Table的格式是每個(gè)函數(shù)8個(gè)byte的MethodDesc加上后面8個(gè)byte的Method Entry(就是函數(shù)指針菇夸,之后稱Method Entry):
00007ff9`ec805a98 03 00 00 21 05 00 28 00 00 05 91 ec f9 7f 00 00 (Verify)
00007ff9`ec805aa8 04 00 02 20 06 00 28 00 98 00 91 ec f9 7f 00 00 (Func)
00007ff9`ec805ab8 05 00 04 20 07 00 28 20 a0 00 91 ec f9 7f 00 00 (Func2)
00007ff9`ec805ac8 06 00 06 20 08 00 28 00 a8 00 91 ec f9 7f 00 00 (Func3)
00007ff9`ec805ad8 07 00 08 20 09 00 28 00 b0 00 91 ec f9 7f 00 00 (Func4)
00007ff9`ec805ae8 08 00 0a 20 0a 00 28 20 b8 00 91 ec f9 7f 00 00 (Func5)
還沒有被JIT編譯的函數(shù),其Method Entry指向一個(gè)Stub(在WinDbg中顯示為NONE)
> dd 00007ff9ec910098
00007ff9`ec910098 614313e8 05025e5f (Stub of Func) 61430be8 04045e5f (Stub of Func2)
00007ff9`ec9100a8 614303e8 03065e5f (Stub of Func3) 6142fbe8 02085e5f (Stub of Func4)
00007ff9`ec9100b8 6142f3e8 010a5e5f (Stub of Func5) 6142ebe8 000c5e5f
由于Verify函數(shù)已經(jīng)被執(zhí)行仪吧,所以已經(jīng)被JIT編譯庄新,其Method Entry指向其匯編實(shí)現(xiàn),而其他函數(shù)都指向Stub薯鼠。
如果我們完整地運(yùn)行完程序(Func3择诈、Func4、Func5均已執(zhí)行完):
> !DumpMT -md 00007ff9ec835b00
EEClass: 00007ff9ec9c2068
Module: 00007ff9ec834118
Name: WCTF2018Rev.Lib
mdToken: 0000000002000003
File: D:\ctf\wctf\WCTF2018Rev_Release.exe
BaseSize: 0x18
ComponentSize: 0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
00007ffa4ae3c000 00007ffa4a997fb8 PreJIT System.Object.ToString()
00007ffa4aea40f0 00007ffa4a997fc0 PreJIT System.Object.Equals(System.Object)
00007ffa4af14490 00007ffa4a997fe8 PreJIT System.Object.GetHashCode()
00007ffa4ae6f390 00007ffa4a998000 PreJIT System.Object.Finalize()
00007ff9ec9400c0 00007ff9ec835af8 NONE WCTF2018Rev.Lib..ctor()
00007ff9ec940500 00007ff9ec835a98 JIT WCTF2018Rev.Lib.Verify(System.String)
00007ff9ec940098 00007ff9ec835aa8 NONE WCTF2018Rev.Lib.Func(Int32)
00007ff9ec9405c0 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2()
00007ff9ec9405c0 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2() (shoule be Func3)
00007ff9ec940619 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2() (shoule be Func4)
00007ff9ec94066a 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2() (shoule be Func5)
這里由于Method Table被改掉了出皇,WinDbg顯示有點(diǎn)問題羞芍。不過Entry還是可以正常看郊艘,說明我們之前的分析結(jié)果是正確的:
修改之后荷科,F(xiàn)unc3指向了Func2的匯編地址(Func2匯編的前一部分是被9個(gè)long修改過的),F(xiàn)unc4指向了Func2 + 0x59纱注, Func5指向了Func2 + 0xaa畏浆。
到這里我們的第一個(gè)問題就解決了。順便附上WinDbg用到的相關(guān)命令:
- 加載SOS.dll
> .load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll
- 設(shè)斷點(diǎn)并運(yùn)行到斷點(diǎn)
> bp 7ffa9bcece5d
> g
- 加載sos模塊
> .loadby sos clr
- 找到Method Table地址
> !Name2EE * WCTF2018Rev.Lib
--------------------------------------
Module: 00007ff9ec834118
Assembly: WCTF2018Rev_Release.exe
Token: 0000000002000003
MethodTable: 00007ff9ec835b00
EEClass: 00007ff9ec9c2068
Name: WCTF2018Rev.Lib
--------------------------------------
- 打印Method Table
!DumpMT -md 00007ff9ec835b00
EEClass: 00007ff9ec9c2068
Module: 00007ff9ec834118
Name: WCTF2018Rev.Lib
mdToken: 0000000002000003
File: D:\ctf\wctf\WCTF2018Rev_Release.exe
BaseSize: 0x18
ComponentSize: 0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
00007ffa4ae3c000 00007ffa4a997fb8 PreJIT System.Object.ToString()
00007ffa4aea40f0 00007ffa4a997fc0 PreJIT System.Object.Equals(System.Object)
00007ffa4af14490 00007ffa4a997fe8 PreJIT System.Object.GetHashCode()
00007ffa4ae6f390 00007ffa4a998000 PreJIT System.Object.Finalize()
00007ff9ec9400c0 00007ff9ec835af8 NONE WCTF2018Rev.Lib..ctor()
00007ff9ec940500 00007ff9ec835a98 JIT WCTF2018Rev.Lib.Verify(System.String)
00007ff9ec940098 00007ff9ec835aa8 NONE WCTF2018Rev.Lib.Func(Int32)
00007ff9ec9405c0 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2()
00007ff9ec9405c0 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2()
00007ff9ec940619 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2()
00007ff9ec94066a 00007ff9ec835ab8 JIT WCTF2018Rev.Lib.Func2()
- 打印Method Description
> !DumpMD 00007ff9ec835ab8
Method Name: WCTF2018Rev.Lib.Func2()
Class: 00007ff9ec9c2068
MethodTable: 00007ff9ec835b00
mdToken: 0000000006000005
Module: 00007ff9ec834118
IsJitted: yes
CodeAddr: 00007ff9ec9405c0
Transparency: Critical
參考:
淺談.NET中的IL代碼
Debugging .NET with WinDbg
Method加殼
另一個(gè)問題就是Func4和Func5的匯編是如何生成的狞贱。
我們已經(jīng)知道Method在運(yùn)行時(shí)刻获,根據(jù)Method Entry找到Method的匯編代碼。而在Resources函數(shù)中瞎嬉,F(xiàn)unc4和Func5的Method Entry指針都被指向了Func2中一段無意義代碼中蝎毡,只有Func3指向的是一段修改過的代碼(看起來比較像脫殼代碼)厚柳。所以我們?cè)賮碜屑?xì)看一下這段代碼的實(shí)現(xiàn):
我們可以看到,在for循環(huán)中沐兵,v5指向的地址被修改了共
38*8=0x130
個(gè)字節(jié)草娜,而v5指向的恰恰就是Func3函數(shù)的結(jié)束部分(從0x44開始):修改的這部分就是Func4和Func5的匯編代碼。而這些代碼是從哪里來的呢痒筒?
我們繼續(xù)觀察脫殼代碼宰闰,發(fā)現(xiàn)其實(shí)就是把a(bǔ)rray數(shù)組從0x8開始的部分與一個(gè)常數(shù)進(jìn)行了
0x1F2FB740F5455FA4
異或解密(實(shí)際上每次異或都會(huì)把這個(gè)常數(shù)按位循環(huán)移動(dòng)一下)。就是說簿透,這個(gè)array數(shù)組(就是resource.bin那個(gè)資源)不僅構(gòu)造出了個(gè)FAKEFLAG移袍,構(gòu)造出了個(gè)真FLAG,還特么藏了兩個(gè)函數(shù)在里面(你以為我是Flag老充,其實(shí)我還是個(gè)函數(shù))葡盗。不得不說出題人還真的有點(diǎn)想法(可惜線下賽基本被所有隊(duì)做出來了?_?)。
未解決問題
有一個(gè)沒搞明白的地方啡浊,就是在Resources函數(shù)中觅够,遍歷Method Table的指針:
IntPtr intPtr = ldftn(Func) + 5;
long num = (long)(*(intPtr + 1));
ptr = *(intPtr + (IntPtr)(((int)(*(intPtr + 2)) << 3) + 3)) + (num << 3);
這個(gè)ptr指針是如何計(jì)算出來的,還是沒弄明白……