今天要介紹的是 Dynamic Method,可以算是一種 .NET 的混淆技巧。我會透過講解 FlareOn 的題目,來介紹這個主題。這篇算是遲來的 Writeups XD
FlareOn9 - 08 Backdoor
這題是去年 FlareOn 的題目,也是我覺得去年最難的題目QQ
首先,使用 dnspy 從 main 開始看
public static void Main(string[] args)
{
try
{
try
{
FLARE15.flare_74();
Program.flared_38(args);
}
catch (InvalidProgramException e)
{
FLARE15.flare_70(e, new object[]
{
args
});
}
}
catch
{
}
}
在 main 中,只有 flare_74
是正常的,這種 try + catch 的形式在後面也會一直出現。
flare_74
會儲存大量的資料
// Token: 0x060000A1 RID: 161 RVA: 0x00005954 File Offset: 0x0000C954
public static void flare_74(){
FLARE15.d_b = new List<byte>{...}.ToArray();
FLARE15.gs_b = new List<byte>{...}.ToArray();
FLARE15.cl_b = new List<byte>{...}.ToArray();
FLARE15.wl_b = new List<byte>{...}.ToArray();
FLARE15.pe_b = new List<byte>{...}.ToArray();
FLARE15.gh_b = new List<byte>{...}.ToArray();
FLARE15.rt_b = new List<byte>{...}.ToArray();
FLARE15.d_m = new Dictionary<uint, int>{...};
FLARE15.gs_m = new Dictionary<uint, int>{...};
FLARE15.cl_m = new Dictionary<uint, int>{...};
FLARE15.wl_m = new Dictionary<uint, int>{...};
FLARE15.pe_m = new Dictionary<uint, int>{...};
FLARE15.gh_m = new Dictionary<uint, int>{...};
FLARE15.c = new ObservableCollection<int>{...};
};
flared_38
會看到一堆亂碼
從 ILDSAM 也會解壞掉
flare_70
是在發生 exception 時才會被呼叫
// Token: 0x060000BB RID: 187 RVA: 0x00013E68 File Offset: 0x0001AE68
public static object flare_70(InvalidProgramException e, object[] a)
{
object result;
try
{
result = FLARE15.flared_70(e, a);
}
catch (InvalidProgramException e2)
{
result = FLARE15.flare_71(e2, new object[]
{
e,
a
}, FLARE15.wl_m, FLARE15.wl_b);
}
return result;
}
裡面也是和 Main 函數一樣的 try+catch 形式。
從 Dnspy 觀察這隻 .NET PE 會發現可以分成兩種 method:
flare_xx
代表正常的 methodflared_xx
代表會被 patched 的 method第一層的混淆看起來是透過觸發 exception,再透過 exception handler 還原。在 flare_70
中,flared_70
是壞掉的。而 flare_71
會製作 Dynamic Function 並呼叫。
// Token: 0x060000BC RID: 188 RVA: 0x00013EB8 File Offset: 0x0001AEB8
public static object flare_71(InvalidProgramException e, object[] args, Dictionary<uint, int> m, byte[] b)
{
StackTrace stackTrace = new StackTrace(e);
int metadataToken = stackTrace.GetFrame(0).GetMethod().MetadataToken;
Module module = typeof(Program).Module;
MethodInfo methodInfo = (MethodInfo)module.ResolveMethod(metadataToken);
MethodBase methodBase = module.ResolveMethod(metadataToken);
ParameterInfo[] parameters = methodInfo.GetParameters();
Type[] array = new Type[parameters.Length];
SignatureHelper localVarSigHelper = SignatureHelper.GetLocalVarSigHelper();
for (int i = 0; i < array.Length; i++)
{
array[i] = parameters[i].ParameterType;
}
Type declaringType = methodBase.DeclaringType;
DynamicMethod dynamicMethod = new DynamicMethod("", methodInfo.ReturnType, array, declaringType, true);
DynamicILInfo dynamicILInfo = dynamicMethod.GetDynamicILInfo();
MethodBody methodBody = methodInfo.GetMethodBody();
foreach (LocalVariableInfo localVariableInfo in methodBody.LocalVariables)
{
localVarSigHelper.AddArgument(localVariableInfo.LocalType);
}
byte[] signature = localVarSigHelper.GetSignature();
dynamicILInfo.SetLocalSignature(signature);
foreach (KeyValuePair<uint, int> keyValuePair in m)
{
int value = keyValuePair.Value;
uint key = keyValuePair.Key;
bool flag = value >= 1879048192 && value < 1879113727;
int tokenFor;
if (flag)
{
tokenFor = dynamicILInfo.GetTokenFor(module.ResolveString(value));
}
else
{
MemberInfo memberInfo = declaringType.Module.ResolveMember(value, null, null);
bool flag2 = memberInfo.GetType().Name == "RtFieldInfo";
if (flag2)
{
tokenFor = dynamicILInfo.GetTokenFor(((FieldInfo)memberInfo).FieldHandle, ((TypeInfo)((FieldInfo)memberInfo).DeclaringType).TypeHandle);
}
else
{
bool flag3 = memberInfo.GetType().Name == "RuntimeType";
if (flag3)
{
tokenFor = dynamicILInfo.GetTokenFor(((TypeInfo)memberInfo).TypeHandle);
}
else
{
bool flag4 = memberInfo.Name == ".ctor" || memberInfo.Name == ".cctor";
if (flag4)
{
tokenFor = dynamicILInfo.GetTokenFor(((ConstructorInfo)memberInfo).MethodHandle, ((TypeInfo)((ConstructorInfo)memberInfo).DeclaringType).TypeHandle);
}
else
{
tokenFor = dynamicILInfo.GetTokenFor(((MethodInfo)memberInfo).MethodHandle, ((TypeInfo)((MethodInfo)memberInfo).DeclaringType).TypeHandle);
}
}
}
}
b[(int)key] = (byte)tokenFor;
b[(int)(key + 1U)] = (byte)(tokenFor >> 8);
b[(int)(key + 2U)] = (byte)(tokenFor >> 16);
b[(int)(key + 3U)] = (byte)(tokenFor >> 24);
}
dynamicILInfo.SetCode(b, methodBody.MaxStackSize);
return dynamicMethod.Invoke(null, args);
}
flare_71
的作用如下:
m
中的 KeyValuePair 的 value 會被用來查找對應的 Metadata Token;而 key 會被當成 offset 用來 patch b
的值。b
會被設定為 DynamicMethod 的 code 並且執行。如下圖,flare_71
從 StackTrace 拿到 flared_70
的 Metadata Token 0x060000BA
可以在 ILDASM 中設定 Show token values 檢視 Metadata Token
在各個 method 的視窗中就可以看到標記了各種 metadata token。
接著要看的是有哪些 Method 被重新 patch。
因為 dnspy 不能在執行期間更新被上 patch 的 method,所以必須要手動上 patch。
def patch_bin(_offset, _b, _m):
_bin = open("FlareOn.Backdoor.1.exe", "rb").read()
_patch = b"".join([bytes([_bi]) for _bi in _b])
_patch_size = len(_patch)
_offset += 0xc
for off, val in _m:
_patch = _patch[:off] + struct.pack("<I", val) + _patch[off+4:]
# print(binascii.hexlify(_bin[_offset:(_offset+_patch_size)]))
with open("FlareOn.Backdoor.1.exe", "wb") as f:
_patched_bin = _bin[:_offset] + _patch + _bin[(_offset+_patch_size):]
f.write(_patched_bin)
在上完 Level 1 的 patch 後,一共有 7 的 method 被修改:
flared_70
flared_69
flared_68
flared_67
// Token: 0x060000B4 RID: 180 RVA: 0x00012E00 File Offset: 0x00019E00
public static object flared_67(byte[] b, int tk, object[] a)
{
Dictionary<uint, FLARE06.OT> dictionary = new Dictionary<uint, FLARE06.OT>{...};
Module module = typeof(Program).Module;
MethodBase methodBase = module.ResolveMethod(tk);
MethodInfo methodInfo = (MethodInfo)methodBase;
ParameterInfo[] parameters = methodInfo.GetParameters();
Type[] array = new Type[parameters.Length];
SignatureHelper localVarSigHelper = SignatureHelper.GetLocalVarSigHelper();
for (int i = 0; i < array.Length; i++)
{
array[i] = parameters[i].ParameterType;
}
Type declaringType = methodBase.DeclaringType;
DynamicMethod dynamicMethod = new DynamicMethod("", methodInfo.ReturnType, array, declaringType, true);
DynamicILInfo dynamicILInfo = dynamicMethod.GetDynamicILInfo();
MethodBody methodBody = methodInfo.GetMethodBody();
foreach (LocalVariableInfo localVariableInfo in methodBody.LocalVariables)
{
localVarSigHelper.AddArgument(localVariableInfo.LocalType);
}
byte[] signature = localVarSigHelper.GetSignature();
dynamicILInfo.SetLocalSignature(signature);
int j = 0;
while (j < b.Length)
{
bool flag = b[j] == 254;
uint key;
if (flag)
{
key = 65024U + (uint)b[j + 1];
j++;
}
else
{
key = (uint)b[j];
}
FLARE06.OT ot = dictionary[key];
j++;
switch (ot)
{
case FLARE06.OT.B:
{
uint num = (uint)FLARE15.flare_68(b, j);
num ^= 2727913149U;
bool flag2 = num >= 1879048192U && num < 1879113727U;
int tokenFor;
if (flag2)
{
tokenFor = dynamicILInfo.GetTokenFor(module.ResolveString((int)num));
}
else
{
Type declaringType2 = methodInfo.DeclaringType;
Type[] genericTypeArguments = null;
Type[] genericMethodArguments = null;
bool flag3 = declaringType2.IsGenericType || declaringType2.IsGenericTypeDefinition;
if (flag3)
{
genericTypeArguments = declaringType2.GetGenericArguments();
}
bool flag4 = methodInfo.IsGenericMethod || methodInfo.IsGenericMethodDefinition;
if (flag4)
{
genericMethodArguments = methodInfo.GetGenericArguments();
}
MemberInfo memberInfo = declaringType2.Module.ResolveMember((int)num, genericTypeArguments, genericMethodArguments);
bool flag5 = memberInfo.GetType().Name == "RtFieldInfo";
if (flag5)
{
tokenFor = dynamicILInfo.GetTokenFor(((FieldInfo)memberInfo).FieldHandle, ((TypeInfo)((FieldInfo)memberInfo).DeclaringType).TypeHandle);
}
else
{
bool flag6 = memberInfo.GetType().Name == "RuntimeType";
if (flag6)
{
tokenFor = dynamicILInfo.GetTokenFor(((TypeInfo)memberInfo).TypeHandle);
}
else
{
bool flag7 = memberInfo.Name == ".ctor" || memberInfo.Name == ".cctor";
if (flag7)
{
tokenFor = dynamicILInfo.GetTokenFor(((ConstructorInfo)memberInfo).MethodHandle, ((TypeInfo)((ConstructorInfo)memberInfo).DeclaringType).TypeHandle);
}
else
{
tokenFor = dynamicILInfo.GetTokenFor(((MethodInfo)memberInfo).MethodHandle, ((TypeInfo)((MethodInfo)memberInfo).DeclaringType).TypeHandle);
}
}
}
}
b[j] = (byte)tokenFor;
b[j + 1] = (byte)(tokenFor >> 8);
b[j + 2] = (byte)(tokenFor >> 16);
b[j + 3] = (byte)(tokenFor >> 24);
j += 4;
break;
}
case FLARE06.OT.C:
case FLARE06.OT.E:
j++;
break;
case FLARE06.OT.D:
case FLARE06.OT.G:
j += 4;
break;
case FLARE06.OT.F:
j += 2;
break;
case FLARE06.OT.H:
j += 8;
break;
case FLARE06.OT.I:
j += 4 + FLARE15.flare_68(b, j) * 4;
break;
}
}
dynamicILInfo.SetCode(b, methodBody.MaxStackSize);
return dynamicMethod.Invoke(null, a);
}
flared_66
回傳 sha256
flared_47
flared_35
經過觀察,幾乎剩下沒解開的 method 的 exception handler 都是 flare_70
舉例來說, flare_01
會觸發 exception handler flare_70
flare_70
會呼叫修好的 flared_70
flared_70
會使用到4個 method:
flare_66
→ flared_66
回傳 sha256flare_69
→ flared_69
flare_46
→ flared_47
看起來是 RC4flare_67
→ flared_67
從參數取出 Metadata Token,並建立和呼叫 Dynamic Method到這邊大概能猜到要修完所有使用到 flare_70
的函數 orz
其中真正在構造 Dynamic Method 的函數是在 flare_67
跟前面 Level 1 的算法差不多,差別在於 m
變成 hardcoded, b
則是會經過多個 method 算出,並且要注意最下方的 case switch 會導致 m
的計算上的使用和之前不同。
def flared_68(_b:bytes, _o:int) -> int:
num = _b[_o + 3] << 24
num += _b[_o + 2] << 16
num += _b[_o + 1] << 8
return num + _b[_o]
def patch_bin(_b, off, val):
_b = _b[:off] + struct.pack("<I", val) + _b[off+4:]
return _b
def patch_bin_loop(_offset, _b, _m):
_b = binascii.unhexlify(_b)
j = 0
while j < len(_b):
key = 0
if _b[j] == 254:
key = 65024 + _b[j+1]
j+=1
else:
key = _b[j]
ot = list(filter(lambda x: x[0]==key, _m))[0][1]
j+=1
if ot[-1] == "B":
num = flared_68(_b,j) ^ 2727913149
_b = patch_bin(_b, j, num)
j+=4
elif (ot[-1] == "C") or (ot[-1] == "E"):
j+=1
elif (ot[-1] == "D") or (ot[-1] == "G"):
j+=4
elif (ot[-1] == "F"):
j+=2
elif (ot[-1] == "H"):
j+=8
elif (ot[-1] == "I"):
j+= 4 + (flared_68(_b,j)*4)
else:
continue
# overwrite _bin with _patch
_offset += 0xc
_patch_size = len(_b)
_bin = open("FlareOn.Backdoor.2.exe", "rb").read()
with open("FlareOn.Backdoor.2.exe", "wb") as f:
_patched_bin = _bin[:_offset] + _b + _bin[(_offset+_patch_size):]
f.write(_patched_bin)
用這個 script 就可以將剩下的 method 解開。
後面有關於 DNS Tunnel 的部分也蠻有趣的,但我當時沒解出這題,所以也很難復現,不過網路上蠻多 writeups 可以參考,在 flare_74
宣告的 c
會在這個部分被使用 。
根據 0xdf 的文章,他發現這題原形是來自 Saitama Backdoor。最終,題目的 flag 其實會是透過 DNS Tunnel 以載入圖片的方式顯示。
下一篇要介紹的是 .NET 的 Deserialization Exploit。