iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
Security

Windows Security 101系列 第 21

[Day21] .NET - Dynamic Method (FlareOn9 - 08 Backdoor)

  • 分享至 

  • xImage
  •  

今天要介紹的是 Dynamic Method,可以算是一種 .NET 的混淆技巧。我會透過講解 FlareOn 的題目,來介紹這個主題。這篇算是遲來的 Writeups XD

FlareOn9 - 08 Backdoor 這題是去年 FlareOn 的題目,也是我覺得去年最難的題目QQ

Static Analysis

首先,使用 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 會看到一堆亂碼

https://ithelp.ithome.com.tw/upload/images/20231005/20120098M2GogH7lyV.png

從 ILDSAM 也會解壞掉

https://ithelp.ithome.com.tw/upload/images/20231005/20120098nP9quZ5wlt.png

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 代表正常的 method
  • flared_xx 代表會被 patched 的 method

Dynamic Analysis

Level 1

第一層的混淆看起來是透過觸發 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 的作用如下:

  • 從 StackTrace 取得 Metadata Token
    • Metadata Token 就是在用來定位在 Metadata tables 裡不同的欄位
  • Metadata Token 用來取得和 Method 及其結構中的重要資訊,像是參數格式和 Local Variables 等
  • For loop 中,每個 m 中的 KeyValuePair 的 value 會被用來查找對應的 Metadata Token;而 key 會被當成 offset 用來 patch b 的值。
  • 在 loop 結束後, b 會被設定為 DynamicMethod 的 code 並且執行。

如下圖,flare_71 從 StackTrace 拿到 flared_70 的 Metadata Token 0x060000BA

https://ithelp.ithome.com.tw/upload/images/20231005/20120098RLx81glWF9.png

可以在 ILDASM 中設定 Show token values 檢視 Metadata Token

https://ithelp.ithome.com.tw/upload/images/20231005/201200980mbVOW3gPQ.png

在各個 method 的視窗中就可以看到標記了各種 metadata token。

https://ithelp.ithome.com.tw/upload/images/20231005/20120098oDY4i7qsyC.png

接著要看的是有哪些 Method 被重新 patch。

https://ithelp.ithome.com.tw/upload/images/20231005/20120098mJ4CHBFH0b.png

因為 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 2

在上完 Level 1 的 patch 後,一共有 7 的 method 被修改:

flared_70

https://ithelp.ithome.com.tw/upload/images/20231005/201200983b1jO9p3R2.png

flared_69

https://ithelp.ithome.com.tw/upload/images/20231005/20120098A3xbOQAXAO.png

flared_68

https://ithelp.ithome.com.tw/upload/images/20231005/20120098NijMksGOv4.png

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

https://ithelp.ithome.com.tw/upload/images/20231005/20120098Hqd5FvO65e.png

flared_47

https://ithelp.ithome.com.tw/upload/images/20231005/20120098kCseJYRwuX.png

flared_35

https://ithelp.ithome.com.tw/upload/images/20231005/20120098hQVqHK2r9d.png

經過觀察,幾乎剩下沒解開的 method 的 exception handler 都是 flare_70

https://ithelp.ithome.com.tw/upload/images/20231005/20120098njaYIxOPF6.png

舉例來說, flare_01 會觸發 exception handler flare_70

https://ithelp.ithome.com.tw/upload/images/20231005/20120098ly5lnqmFFe.png

flare_70 會呼叫修好的 flared_70

https://ithelp.ithome.com.tw/upload/images/20231005/201200985En1sg9P4K.png

flared_70 會使用到4個 method:

  • flare_66flared_66 回傳 sha256
  • flare_69flared_69
  • flare_46flared_47 看起來是 RC4
  • flare_67flared_67 從參數取出 Metadata Token,並建立和呼叫 Dynamic Method

https://ithelp.ithome.com.tw/upload/images/20231005/20120098crK99HutND.png

到這邊大概能猜到要修完所有使用到 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 Tunneling

後面有關於 DNS Tunnel 的部分也蠻有趣的,但我當時沒解出這題,所以也很難復現,不過網路上蠻多 writeups 可以參考,在 flare_74 宣告的 c 會在這個部分被使用 。

根據 0xdf 的文章,他發現這題原形是來自 Saitama Backdoor。最終,題目的 flag 其實會是透過 DNS Tunnel 以載入圖片的方式顯示。

下一篇要介紹的是 .NET 的 Deserialization Exploit。

References


上一篇
[Day20] .NET - Common Intermediate Lanaguage (CIL Code)
下一篇
[Day22] .NET - Deserialization Exploit
系列文
Windows Security 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言