เป็นไปได้ไหมที่จะเขียนคอมไพเลอร์ JIT (ไปยังโค้ดเนทีฟ) ทั้งหมดในภาษา. NET ที่มีการจัดการ


84

ฉันคิดว่าจะเขียนคอมไพเลอร์ JIT และฉันแค่สงสัยว่าในทางทฤษฎีเป็นไปได้หรือไม่ที่จะเขียนสิ่งทั้งหมดในโค้ดที่มีการจัดการ โดยเฉพาะอย่างยิ่งเมื่อคุณสร้างแอสเซมเบลอร์ในอาร์เรย์ไบต์แล้วคุณจะเข้าสู่แอสเซมเบลอร์เพื่อเริ่มต้นการดำเนินการได้อย่างไร?


ฉันไม่เชื่อว่ามี - ในขณะที่คุณสามารถทำงานในบริบทที่ไม่ปลอดภัยในบางครั้งในภาษาที่มีการจัดการฉันไม่เชื่อว่าคุณสามารถสังเคราะห์ผู้รับมอบสิทธิ์จากตัวชี้ได้และคุณจะข้ามไปยังโค้ดที่สร้างขึ้นได้อย่างไร
Damien_The_Unbeliever

@Damien: โค้ดที่ไม่ปลอดภัยจะไม่ให้คุณเขียนไปยังตัวชี้ฟังก์ชันได้หรือไม่?
Henk Holterman

2
ด้วยชื่อเรื่องเช่น "วิธีโอนการควบคุมไปยังโค้ดที่ไม่มีการจัดการแบบไดนามิก" คุณอาจมีความเสี่ยงที่จะถูกปิดน้อยลง มันดูตรงประเด็นมากขึ้นด้วย การสร้างรหัสไม่ใช่ปัญหา
Henk Holterman

8
แนวคิดที่ง่ายที่สุดคือจดไบต์อาร์เรย์ลงในไฟล์และปล่อยให้ระบบปฏิบัติการรัน ท้ายที่สุดคุณต้องมีคอมไพเลอร์ไม่ใช่ล่าม (ซึ่งอาจเป็นไปได้เช่นกัน แต่ซับซ้อนกว่า)
Vlad

3
เมื่อคุณรวบรวม JIT รหัสที่คุณต้องการแล้วคุณสามารถใช้ Win32 API เพื่อจัดสรรหน่วยความจำที่ไม่มีการจัดการ (ทำเครื่องหมายว่าปฏิบัติการได้) คัดลอกโค้ดที่คอมไพล์แล้วลงในพื้นที่หน่วยความจำนั้นจากนั้นใช้calliรหัส opcode เพื่อเรียกโค้ดที่คอมไพล์
Jack P.

คำตอบ:


71

และสำหรับหลักฐานที่เต็มรูปแบบของแนวคิดที่นี่เป็นความสามารถอย่างเต็มที่แปลของวิธี Rasmus' เพื่อ JIT เข้า F #

open System
open System.Runtime.InteropServices

type AllocationType =
    | COMMIT=0x1000u

type MemoryProtection =
    | EXECUTE_READWRITE=0x40u

type FreeType =
    | DECOMMIT = 0x4000u

[<DllImport("kernel32.dll", SetLastError=true)>]
extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

[<DllImport("kernel32.dll", SetLastError=true)>]
extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, FreeType freeType);

let JITcode: byte[] = [|0x55uy;0x8Buy;0xECuy;0x8Buy;0x45uy;0x08uy;0xD1uy;0xC8uy;0x5Duy;0xC3uy|]

[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>] 
type Ret1ArgDelegate = delegate of (uint32) -> uint32

[<EntryPointAttribute>]
let main (args: string[]) =
    let executableMemory = VirtualAlloc(IntPtr.Zero, UIntPtr(uint32(JITcode.Length)), AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE)
    Marshal.Copy(JITcode, 0, executableMemory, JITcode.Length)
    let jitedFun = Marshal.GetDelegateForFunctionPointer(executableMemory, typeof<Ret1ArgDelegate>) :?> Ret1ArgDelegate
    let mutable test = 0xFFFFFFFCu
    printfn "Value before: %X" test
    test <- jitedFun.Invoke test
    printfn "Value after: %X" test
    VirtualFree(executableMemory, UIntPtr.Zero, FreeType.DECOMMIT) |> ignore
    0

ที่ดำเนินการอย่างมีความสุข

Value before: FFFFFFFC
Value after: 7FFFFFFE

แม้จะมีการโหวตเพิ่มขึ้น แต่ฉันก็ขอให้แตกต่าง: นี่คือการใช้รหัสโดยอำเภอใจไม่ใช่ JIT - JIT หมายถึง "การรวบรวมในเวลาเท่านั้น" แต่ฉันไม่เห็นด้าน "การรวบรวม" จากตัวอย่างโค้ดนี้
rwong

4
@rwong: แง่มุม "การรวบรวม" ไม่เคยอยู่ในขอบเขตของคำถามเดิม ความสามารถในการจัดการโค้ดของการใช้IL -> การแปลงรหัสเนทีฟนั้นค่อนข้างชัดเจน
Gene Belitski

70

ใช่คุณสามารถ. อันที่จริงมันคืองานของฉัน :)

ฉันเขียน GPU.NET ทั้งหมดใน F # (โมดูโลทำการทดสอบหน่วยของเรา) - มันแยกชิ้นส่วนและ JITs IL ในขณะรันไทม์เช่นเดียวกับ. NET CLR เราปล่อยโค้ดเนทีฟสำหรับอุปกรณ์เร่งความเร็วที่คุณต้องการใช้ ขณะนี้เรารองรับเฉพาะ Nvidia GPU แต่ฉันได้ออกแบบระบบของเราให้สามารถกำหนดเป้าหมายใหม่ได้โดยมีการทำงานขั้นต่ำดังนั้นจึงมีแนวโน้มว่าเราจะรองรับแพลตฟอร์มอื่น ๆ ในอนาคต

สำหรับประสิทธิภาพฉันมี F # ที่จะขอบคุณ - เมื่อคอมไพล์ในโหมดที่ปรับให้เหมาะสม (พร้อม tailcalls) คอมไพเลอร์ JIT ของเราอาจเร็วพอ ๆ กับคอมไพลเลอร์ภายใน CLR (ซึ่งเขียนด้วย C ++, IIRC)

สำหรับการดำเนินการเรามีประโยชน์ที่จะสามารถส่งผ่านการควบคุมไปยังไดรเวอร์ฮาร์ดแวร์เพื่อเรียกใช้โค้ด jitted อย่างไรก็ตามสิ่งนี้จะไม่ยากที่จะทำบน CPU เนื่องจาก. NET สนับสนุนตัวชี้ฟังก์ชันไปยังรหัสที่ไม่มีการจัดการ / เนทีฟ (แม้ว่าคุณจะสูญเสียความปลอดภัย / ความปลอดภัยใด ๆ ตามปกติโดย. NET)


4
จุดรวมของ NoExecute ไม่ใช่หรือที่คุณไม่สามารถข้ามไปยังโค้ดที่คุณสร้างขึ้นเองได้? แทนที่จะเป็นไปได้ที่จะข้ามไปยังโค้ดเนทีฟโดยใช้ตัวชี้ฟังก์ชัน: เป็นไปไม่ได้ที่จะข้ามไปยังโค้ดเนทีฟผ่านตัวชี้ฟังก์ชัน?
Ian Boyd

โครงการที่ยอดเยี่ยมแม้ว่าฉันคิดว่าพวกคุณจะได้รับการเปิดเผยมากขึ้นหากคุณทำให้แอปพลิเคชันไม่แสวงหาผลกำไรฟรี คุณจะสูญเสียการเปลี่ยนแปลงจำนวนมากจากระดับ "ผู้ที่ชื่นชอบ" แต่มันก็คุ้มค่าสำหรับการเปิดรับที่เพิ่มขึ้นจากผู้คนจำนวนมากที่ใช้มัน(ฉันรู้ว่าฉันจะทำอย่างแน่นอน;)) !
BlueRaja - Danny Pflughoeft

@IanBoyd NoExecute ส่วนใหญ่เป็นอีกวิธีหนึ่งในการหลีกเลี่ยงปัญหาจากการโอเวอร์รันบัฟเฟอร์และปัญหาที่เกี่ยวข้อง ไม่ใช่การป้องกันจากรหัสของคุณเอง แต่เป็นสิ่งที่ช่วยลดการใช้รหัสที่ผิดกฎหมาย
Luaan

51

เคล็ดลับควรเป็นVirtualAllocพร้อมด้วยEXECUTE_READWRITE-flag (ต้องการ P / Invoke) และMarshal.GetDelegateForFunctionPointer Marshal.GetDelegateForFunctionPointer

นี่คือตัวอย่างจำนวนเต็มหมุนเวียนรุ่นที่แก้ไขแล้ว (โปรดทราบว่าไม่จำเป็นต้องใช้รหัสที่ไม่ปลอดภัยที่นี่):

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint Ret1ArgDelegate(uint arg1);

public static void Main(string[] args){
    // Bitwise rotate input and return it.
    // The rest is just to handle CDECL calling convention.
    byte[] asmBytes = new byte[]
    {        
      0x55,             // push ebp
      0x8B, 0xEC,       // mov ebp, esp 
      0x8B, 0x45, 0x08, // mov eax, [ebp+8]
      0xD1, 0xC8,       // ror eax, 1
      0x5D,             // pop ebp 
      0xC3              // ret
    };

    // Allocate memory with EXECUTE_READWRITE permissions
    IntPtr executableMemory = 
        VirtualAlloc(
            IntPtr.Zero, 
            (UIntPtr) asmBytes.Length,    
            AllocationType.COMMIT,
            MemoryProtection.EXECUTE_READWRITE
        );

    // Copy the machine code into the allocated memory
    Marshal.Copy(asmBytes, 0, executableMemory, asmBytes.Length);

    // Create a delegate to the machine code.
    Ret1ArgDelegate del = 
        (Ret1ArgDelegate) Marshal.GetDelegateForFunctionPointer(
            executableMemory, 
            typeof(Ret1ArgDelegate)
        );

    // Call it
    uint n = (uint)0xFFFFFFFC;
    n = del(n);
    Console.WriteLine("{0:x}", n);

    // Free the memory
    VirtualFree(executableMemory, UIntPtr.Zero, FreeType.DECOMMIT);
 }

ตัวอย่างเต็ม (ตอนนี้ใช้ได้กับทั้ง X86 และ X64)


30

การใช้รหัสที่ไม่ปลอดภัยคุณสามารถ "แฮ็ก" ผู้รับมอบสิทธิ์และทำให้มันชี้ไปที่รหัสการประกอบที่คุณสร้างและเก็บไว้ในอาร์เรย์ได้โดยพลการ แนวคิดคือผู้รับมอบสิทธิ์มี_methodPtrเขตข้อมูลซึ่งสามารถตั้งค่าได้โดยใช้การสะท้อนกลับ นี่คือโค้ดตัวอย่างบางส่วน:

แน่นอนว่านี่คือการแฮ็กสกปรกที่อาจหยุดทำงานเมื่อใดก็ได้เมื่อรันไทม์. NET เปลี่ยนไป

ฉันเดาว่าโดยหลักการแล้วรหัสปลอดภัยที่ได้รับการจัดการอย่างสมบูรณ์ไม่สามารถได้รับอนุญาตให้ใช้ JIT เพราะจะทำลายสมมติฐานด้านความปลอดภัยที่รันไทม์อาศัยอยู่ (เว้นแต่รหัสการประกอบที่สร้างขึ้นมาพร้อมกับหลักฐานที่ตรวจสอบได้ด้วยเครื่องว่าไม่ละเมิดสมมติฐาน ... )


1
แฮ็คที่ดี บางทีคุณอาจคัดลอกบางส่วนของโค้ดลงในโพสต์นี้เพื่อหลีกเลี่ยงปัญหาลิงก์เสียในภายหลัง (หรือเพียงแค่เขียนคำอธิบายเล็ก ๆ ในโพสต์นี้)
Felix K.

ฉันได้รับAccessViolationExceptionถ้าฉันพยายามเรียกใช้ตัวอย่างของคุณ ฉันเดาว่ามันใช้งานได้ก็ต่อเมื่อ DEP ถูกปิดใช้งาน
Rasmus Faber

1
แต่ถ้าฉันจัดสรรหน่วยความจำด้วยแฟล็ก EXECUTE_READWRITE และใช้สิ่งนั้นในฟิลด์ _methodPtr มันก็ใช้ได้ดี เมื่อมองผ่าน Rotor-code ดูเหมือนว่าโดยทั่วไปแล้ว Marshal.GetDelegateForFunctionPointer () ทำยกเว้นว่าจะเพิ่ม thunks พิเศษรอบ ๆ รหัสสำหรับการตั้งค่าสแต็กและการจัดการความปลอดภัย
Rasmus Faber

ฉันคิดว่าลิงก์เสียแล้วอนิจจาฉันจะแก้ไข แต่ฉันไม่พบการย้ายที่ตั้งของต้นฉบับ
Abel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.