ฉันตัดสินใจว่าถึงเวลาแล้วที่มีการเปิดตัว Pyarmor ที่เหมาะสม ทุกคนที่อยู่ในปัจจุบันนั้นล้าสมัยไม่ทำงานเลยหรือให้ผลผลิตบางส่วนเท่านั้น ฉันวางแผนที่จะทำให้สิ่งนี้สนับสนุน Pyarmor เวอร์ชันล่าสุด
โปรดแสดงที่เก็บถ้าคุณพบว่ามีประโยชน์ ฉันขอขอบคุณจริงๆ
มี 3 วิธีที่แตกต่างกันสำหรับการเปิด pyarmor ในโฟลเดอร์วิธีการในที่เก็บนี้คุณจะพบไฟล์ทั้งหมดที่จำเป็นสำหรับแต่ละวิธี ด้านล่างคุณจะพบการเขียนโดยละเอียดเกี่ยวกับวิธีที่ฉันเริ่มต้นจนถึงผลิตภัณฑ์สุดท้าย ฉันหวังว่าผู้คนจำนวนมากจะเข้าใจว่ามันทำงานอย่างไรด้วยวิธีนี้แทนที่จะใช้เครื่องมือ
นี่คือรายการของปัญหา/คุณสมบัติที่หายไปทั้งหมด ฉันไม่มีเวลาพอที่จะแก้ไขตัวเองได้ดังนั้นฉันจึงต้องพึ่งพาผู้มีส่วนร่วมอย่างมาก
ปัญหา:
คุณสมบัติที่ขาดหายไป:
สำคัญ: ใช้เวอร์ชัน Python เดียวกันทุกที่ดูว่าโปรแกรมที่คุณกำลังเปิดตัวถูกรวบรวมด้วย ถ้าคุณไม่คุณจะประสบปัญหา
method_1.pyrun.pydumps คุณสามารถค้นหาไฟล์ .pyc ที่ไม่ได้บรรจุอย่างสมบูรณ์ หมายเหตุ: อย่าใช้ unpacker แบบคงที่สำหรับสิ่งที่ต่ำกว่าเวอร์ชัน 3.9.7, marshal.loads log บันทึกการตรวจสอบถูกเพิ่มในและหลัง 3.9.7 เท่านั้น ผู้มีส่วนร่วมใด ๆ สามารถเพิ่มการสนับสนุนได้
python3 bypass.py filename.pyc (แทนที่ filename.pyc ด้วยชื่อไฟล์จริงเห็นได้ชัด)dumps คุณสามารถค้นหาไฟล์ .pyc ที่ไม่ได้บรรจุอย่างสมบูรณ์การมีส่วนร่วมมีความสำคัญมาก ฉันไม่มีเวลาพอที่จะแก้ไขปัญหาทั้งหมดที่ระบุไว้ข้างต้น โปรดมีส่วนร่วมหากคุณทำได้
การบริจาคก็ยินดีต้อนรับเช่นกัน:
BTC - 37RQ1XEB5Q8SCMMKK3MVMD4RBE5FV7EMMH
ETH - 0x28152666867856FA48B3924C185D7E1FB36F3B9A
LTC - MFHDLRDZAQYGZXUVXQFM4RWVGBMRZMDZAO
นี่คือการเขียนที่รอคอยมานานเกี่ยวกับกระบวนการเต็มรูปแบบที่ฉันผ่านไปเพื่อ deobfuscate หรือ unpack pyarmor ฉันจะผ่านการวิจัยทั้งหมดที่ฉันทำและในตอนท้ายให้ 3 วิธีสำหรับการเปิด pyarmor พวกเขาทั้งหมดมีเอกลักษณ์และใช้งานได้ในสถานการณ์ที่แตกต่างกัน ฉันอยากจะพูดถึงฉันไม่ได้รู้อะไรมากมายเกี่ยวกับ Python Internals ดังนั้นมันจึงใช้เวลานานกว่าสำหรับคนอื่น ๆ ที่มีประสบการณ์มากขึ้นใน Python Internals
Pyarmor มีเอกสารที่ครอบคลุมมากเกี่ยวกับวิธีที่พวกเขาทำทุกอย่างฉันขอแนะนำให้คุณอ่านอย่างเต็มที่ Pyarmor ลูปผ่านวัตถุโค้ดทุกชิ้นและเข้ารหัสมัน มีส่วนหัวและส่วนท้ายแบบคงที่ ขึ้นอยู่กับว่าเปิดใช้งาน "โหมด WRAP" หรือไม่
wrap header:
LOAD_GLOBALS N (__armor_enter__) N = length of co_consts
CALL_FUNCTION 0
POP_TOP
SETUP_FINALLY X (jump to wrap footer) X = size of original byte code
changed original byte code:
Increase oparg of each absolute jump instruction by the size of wrap header
Obfuscate original byte code
...
wrap footer:
LOAD_GLOBALS N + 1 (__armor_exit__)
CALL_FUNCTION 0
POP_TOP
END_FINALLY
จากเอกสาร Pyarmor
ในส่วนหัวมีการโทรไปยังฟังก์ชัน __armor_enter__ ซึ่งจะถอดรหัสวัตถุรหัสในหน่วยความจำ หลังจากที่วัตถุรหัสเสร็จสิ้นฟังก์ชั่น __armor_exit__ จะถูกเรียกซึ่งจะเข้ารหัสวัตถุรหัสอีกครั้งอีกครั้งดังนั้นจึงไม่มีการถอดรหัสวัตถุโค้ดจะถูกทิ้งไว้ในหน่วยความจำ
เมื่อเรารวบรวมสคริปต์ pyarmor เราจะเห็นว่ามีไฟล์จุดเข้าและโฟลเดอร์ pytransform โฟลเดอร์นี้มีไฟล์ dll และ __init__.py
dist
│ test.py
└───pytransform
| _pytransform.dll
| __init__.py
ไฟล์ __init__.py ไม่จำเป็นต้องทำอะไรมากมายกับการถอดรหัสวัตถุรหัส ส่วนใหญ่จะใช้เพื่อให้เราสามารถนำเข้าโมดูล มีการตรวจสอบบางอย่างเช่นว่าคุณใช้ระบบปฏิบัติการอะไรถ้าคุณต้องการอ่านมันเป็นโอเพ่นซอร์สเพื่อให้คุณสามารถเปิดได้เหมือนสคริปต์ Python ปกติ
สิ่งที่สำคัญที่สุดคือการโหลด _pytransform.dll และเปิดเผยฟังก์ชั่นของมันไปยังลูกโลกของล่าม Python ในสคริปต์ทั้งหมดเราจะเห็นได้ว่าจาก pytransform มันนำเข้า pyarmor_runtime
from pytransform import pyarmor_runtime
pyarmor_runtime ()
__pyarmor__ ( __name__ , __file__ , b' x50 x59 x41 x5...' ) ฟังก์ชั่นนี้จะสร้างฟังก์ชั่นทั้งหมดที่จำเป็นในการเรียกใช้สคริปต์ Pyarmor เช่นฟังก์ชั่น __armor_enter__ และ __armor_exit__
ทรัพยากรแรกที่ฉันพบคือเธรดนี้ในฟอรัม tuts4you ที่นี่ผู้ใช้ extremecoders เขียนโพสต์สองสามเรื่องเกี่ยวกับวิธีที่เขาเปิดไฟล์ Pyarmor Protected File เขาแก้ไขซอร์สโค้ด CPYTHON เพื่อทิ้งจอมพลของวัตถุโค้ดทุกชิ้นที่ดำเนินการ
ในขณะที่วิธีนี้ยอดเยี่ยมสำหรับการเปิดเผยค่าคงที่ทั้งหมด แต่ก็เหมาะน้อยกว่าถ้าคุณต้องการได้รับ bytecode นี่เป็นเพราะ:
__armor_enter__ ซึ่งเป็นจุดเริ่มต้นของวัตถุรหัส เนื่องจากฟังก์ชั่น __armor_enter__ จึงถอดรหัสไว้ในหน่วยความจำมันจะไม่ถูกทิ้งโดย cpython มีบางคนที่ทดลองใช้การทิ้งวัตถุโค้ดที่ถอดรหัสจากหน่วยความจำโดยการฉีดรหัส Python
ในวิดีโอนี้มีคนแสดงให้เห็นว่าเขาถอดฟังก์ชันการถอดรหัสทั้งหมดในหน่วยความจำได้อย่างไร
อย่างไรก็ตามเขายังไม่พบวิธีการทิ้งโมดูลหลักเพียงฟังก์ชั่น โชคดีที่เขาเผยแพร่รหัสของเขาที่เขาเคยฉีดรหัส Python ในพื้นที่เก็บข้อมูล GitHub เราจะเห็นว่าเขาสร้าง DLL ซึ่งเขาเรียกฟังก์ชันที่ส่งออกจาก Python DLL เพื่อเรียกใช้รหัส Python อย่างง่าย ขณะนี้เขาได้เพิ่มการสนับสนุนสำหรับการค้นหา Python DLLS สำหรับเวอร์ชัน 3.7 ถึง 3.9 เท่านั้น แต่คุณสามารถเพิ่มเวอร์ชันเพิ่มเติมได้อย่างง่ายดายโดยการปรับเปลี่ยนแหล่งที่มาและทำการคอมไพล์ใหม่ เขาทำมันดังนั้นมันจะดำเนินการรหัสที่พบใน code.py ด้วยวิธีนี้มันง่ายที่จะแก้ไขรหัส Python โดยไม่ต้องสร้างโครงการใหม่ทุกครั้ง
ในที่เก็บเขามีไฟล์ Python ซึ่งจะทิ้งชื่อฟังก์ชันทั้งหมดไปยังไฟล์ที่มีที่อยู่ที่เกี่ยวข้องในหน่วยความจำหากไม่มีหน่วยความจำพบว่ามันยังไม่ได้ถูกเรียกว่ายังไม่ได้ถูกถอดรหัส
# Copyright holder: https://github.com/call-042PE
# License: GNU GPL v3.0 (https://github.com/call-042PE/PyInjector/blob/main/LICENSE)
import os , sys , inspect , re , dis , json , types
hexaPattern = re . compile ( r'b0x[0-9A-F]+b' )
def GetAllFunctions (): # get all function in a script
functionFile = open ( "dumpedMembers.txt" , "w+" )
members = inspect . getmembers ( sys . modules [ __name__ ]) # the code will take all the members in the __main__ module, the main problem is that it can't dump main code function
for member in members :
match = re . search ( hexaPattern , str ( member [ 1 ]))
if ( match ):
functionFile . write ( "{ " functionName " : " " + str ( member [ 0 ]) + " " , " functionAddr " : " " + match . group ( 0 ) + " " } n " )
else :
functionFile . write ( "{ " functionName " : " " + str ( member [ 0 ]) + " " , " functionAddr " :null} n " )
functionFile . close ()
GetAllFunctions () จากที่เก็บของ Call-042PE
ในรหัสคุณจะเห็นว่าเขาเพิ่มความคิดเห็นที่บอกว่าปัญหาที่เขามีคือเขาไม่สามารถเข้าถึงวัตถุรหัสโมดูลหลักได้
หลังจาก googling จำนวนมากฉันถูกนิ่งงันไม่สามารถหาอะไรเกี่ยวกับวิธีรับวัตถุที่ใช้รหัสปัจจุบัน บางครั้งในโครงการที่ไม่เกี่ยวข้องฉันเห็นการเรียกใช้ฟังก์ชันไปยัง sys._getframe() ฉันทำวิจัยเกี่ยวกับสิ่งที่มันทำมันได้รับเฟรมวิ่งปัจจุบัน
คุณสามารถให้จำนวนเต็มเป็นอาร์กิวเมนต์ที่จะเดินขึ้นไปสแต็กการโทรและรับเฟรมที่ดัชนีเฉพาะ
sys . _getframe ( 1 ) # get the caller's frameตอนนี้เหตุผลที่สิ่งนี้สำคัญเพราะเฟรมใน Python นั้นเป็นเพียงวัตถุโค้ด แต่มีข้อมูลเพิ่มเติมเกี่ยวกับสถานะในหน่วยความจำ ในการรับวัตถุโค้ดจากเฟรมเราสามารถใช้แอตทริบิวต์. f_code คุณจะคุ้นเคยกับสิ่งนี้หากคุณได้สร้างเวอร์ชัน cpython ที่กำหนดเองซึ่งทิ้งวัตถุรหัสที่ดำเนินการตามที่เราได้รับวัตถุรหัสจากเฟรมที่นั่น
...
1443 tstate -> frame = frame ;
1444 co = frame -> f_code ;
... จากรุ่น cpython ที่กำหนดเองของฉัน
ดังนั้นตอนนี้เราได้คิดหาวิธีรับวัตถุรหัสที่เรียกใช้ปัจจุบันเราสามารถเดินสแต็กการโทรขึ้นไปจนกว่าเราจะพบโมดูลหลักซึ่งจะถูกถอดรหัส
ตอนนี้เราได้คิดแนวคิดหลักเกี่ยวกับวิธีการแกะ Pyarmor ตอนนี้ฉันจะแสดง 3 วิธีในการแกะกล่องที่ฉันพบว่ามีประโยชน์เป็นการส่วนตัวในสถานการณ์ที่แตกต่างกัน
คนแรกต้องการให้คุณฉีดรหัส Python ดังนั้นคุณจะต้องเรียกใช้สคริปต์ Pyarmor เมื่อเราทิ้งวัตถุรหัสหลักอย่างที่ฉันอธิบายไว้ข้างต้นปัญหาหลักคือฟังก์ชั่นบางอย่างจะยังคงได้รับการเข้ารหัสดังนั้นวิธีแรกจะเรียกใช้ฟังก์ชันรันไทม์ Pyarmor เพื่อให้ฟังก์ชั่นทั้งหมดที่จำเป็นในการถอดรหัสวัตถุโค้ดถูกโหลดเช่น __armor_enter__ และ __armor_exit__
นี่เป็นเรื่องง่ายที่จะทำ แต่ Pyarmor คิดเรื่องนี้พวกเขาใช้โหมด จำกัด คุณสามารถระบุสิ่งนี้เมื่อรวบรวมสคริปต์ Pyarmor โดยค่าเริ่มต้นโหมด จำกัด คือ 1
ฉันไม่ได้ทดสอบทุกโหมด จำกัด แต่ใช้งานได้สำหรับค่าเริ่มต้น
เมื่อเราพยายามเรียกใช้รหัสนี้ในการแก้ไขของเราคุณจะได้รับข้อผิดพลาดต่อไปนี้:
> >> from pytransform import pyarmor_runtime
> >> pyarmor_runtime ()
Check bootstrap restrict mode failed สิ่งนี้ทำให้เราไม่สามารถใช้ __armor_enter__ และ __armor_exit__
ดังนั้นขั้นตอนต่อไปที่ฉันทำคือติดต่อ extremecoders บน tuts4you เขาช่วยฉันด้วยการพูดถึงว่าฉันสามารถแก้ไข _pytransform.dll ได้ ฉันยังอยากจะขอบคุณเขาที่ให้ทางออกแก่ฉันในการทำสิ่งนี้ใน Python
ถ้าเราเปิด _pytransform.dll ในดีบักเกอร์ดั้งเดิมฉันเลือก x64dbg เราจะมองหาสตริงทั้งหมดในโมดูลปัจจุบัน
หากเรากรองสิ่งนี้ตอนนี้โดยการค้นหา "bootstrap" เราจะได้รับสิ่งต่อไปนี้
เมื่อเราดูการถอดชิ้นส่วนในผลการค้นหาครั้งแรกคุณจะเห็นว่ามีการอ้างอิงของ _errno ซึ่งบ่งชี้ว่าอาจมีข้อผิดพลาดเกิดขึ้นบางบรรทัดด้านล่างที่เราสามารถเห็นข้อผิดพลาดที่เราได้รับใน Python
เมื่อเราเพิ่งจะไม่รวมทุกอย่างจากจุดกระโดดที่กระโดดข้ามรหัสที่ก่อให้เกิดข้อผิดพลาดไปสู่การส่งคืนไม่มีทางที่จะเกิดข้อผิดพลาดได้
ตอนนี้ถ้าเราบันทึกสิ่งนี้และแทนที่ _pytransform.dll คุณจะเห็นว่าเมื่อเราลองใช้รหัสเดียวกันอีกครั้งข้อผิดพลาดจะไม่เกิดขึ้นและเราสามารถเข้าถึงฟังก์ชั่น __armor_enter__ และ __armor_exit__
> >> from pytransform import pyarmor_runtime
> >> pyarmor_runtime ()
> >> __armor_enter__
< built - in function __armor_enter__ >
> >> __armor_exit__
< built - in function __armor_exit__ > ตอนนี้มันค่อนข้างเหนื่อยถ้าเราต้องทำสิ่งนี้สำหรับทุกสคริปต์ pyarmor ที่เราต้องการแกะออกมาดังนั้น extremecoders จึงสร้างสคริปต์ที่กำหนดที่อยู่เฉพาะในหน่วยความจำใน Python
# Credit to extremecoders (https://forum.tuts4you.com/profile/79240-extreme-coders/) for writing the script
# Credit to me for adding the comments explaining it
import ctypes
from ctypes . wintypes import *
VirtualProtect = ctypes . windll . kernel32 . VirtualProtect
VirtualProtect . argtypes = [ LPVOID , ctypes . c_size_t , DWORD , PDWORD ]
VirtualProtect . restype = BOOL
# Load the dll in memory, this is useful because once it's loaded in memory it won't need to get loaded again so all the changes we make will be kept, including the bootstrap bypass
h_pytransform = ctypes . cdll . LoadLibrary ( "pytransform \ _pytransform.dll" )
pytransform_base = h_pytransform . _handle # Get the memory address where the dll is loaded
print ( "[+] _pytransform.dll loaded at" , hex ( pytransform_base ))
# We got this offset like I showed above with x64dbg, it's the first address where we start the NOP
patch_offset = 0x70A18F80 - pytransform_base
num_nops = 0x70A18FD5 - 0x70A18F80 # Minus the end address, this is the size that the NOP will be. The result will be 0x55
oldprotect = DWORD ( 0 )
PAGE_EXECUTE_READWRITE = DWORD ( 0x40 )
print ( "[+] Setting memory permissions" )
VirtualProtect ( pytransform_base + patch_offset , num_nops , PAGE_EXECUTE_READWRITE , ctypes . byref ( oldprotect ))
print ( "[+] Patching bootstrap restrict mode" )
ctypes . memset ( pytransform_base + patch_offset , 0x90 , num_nops ) # 0x90 is NOP
print ( "[+] Restoring memory permission" )
VirtualProtect ( pytransform_base + patch_offset , num_nops , oldprotect , ctypes . byref ( oldprotect ))
print ( "[+] All done! Pyarmor bootstrap restrict mode disabled" ) หากเราใส่รหัสนี้ไว้ในไฟล์ที่เรียกว่า restrict_bypass.py เราสามารถใช้มันได้เช่นต่อไปนี้โดยใช้ _pytransform.dll ต้นฉบับดั้งเดิม
> >> import restrict_bypass
[ + ] _pytransform . dll loaded at 0x70a00000
[ + ] Setting memory permissions
[ + ] Patching bootstrap restrict mode
[ + ] Restoring memory permission
[ + ] All done ! Pyarmor bootstrap restrict mode disabled
>> > from pytransform import pyarmor_runtime
>> > pyarmor_runtime ()
>> > __armor_enter__
< built - in function __armor_enter__ >
> >> __armor_exit__
< built - in function __armor_exit__ > วิธีที่สองเริ่มต้นจากวิธีแรกเราฉีดสคริปต์ซึ่งได้รับวัตถุรหัสที่เรียกใช้ปัจจุบัน
ตอนนี้ความแตกต่างคือเราจะไม่ทิ้งมันเราจะ "แก้ไข" มัน โดยที่ฉันหมายถึงการลบ pyarmor ออกจากมันอย่างสมบูรณ์เพื่อให้เราได้รับวัตถุรหัสดั้งเดิม
เนื่องจาก Pyarmor มีตัวเลือกหลายอย่างเมื่อทำให้งงงวยฉันจึงตัดสินใจเพิ่มการสนับสนุนสำหรับตัวเลือกทั่วไปทั้งหมด
เมื่อตรวจพบสคริปต์มี __armor_enter__ อยู่ข้างในมันจะแก้ไขได้เพื่อให้วัตถุโค้ดส่งคืนทันทีหลังจากที่ __armor_enter__ ถูกเรียก
มี opcode POP_TOP หลังจากการเรียกใช้ฟังก์ชันสิ่งนี้ถูกใช้เพื่อให้ค่าส่งคืนของฟังก์ชั่นถูกลบออกจากสแต็กเราเพิ่งแทนที่ด้วย opcode RETURN_VALUE เพื่อให้เราสามารถรับค่าคืนของฟังก์ชัน __armor_enter__ และเพื่อให้เรามีวัตถุโค้ดถอดรหัสในหน่วยความจำ ดูตัวอย่างด้านล่าง
1 0 JUMP_ABSOLUTE 18
2 NOP
4 NOP
>> 6 POP_BLOCK
3 8 < 53 >
10 NOP
12 NOP
14 NOP
7 16 JUMP_ABSOLUTE 82
>> 18 LOAD_GLOBAL 5 ( __armor_enter__ )
20 CALL_FUNCTION 0
22 POP_TOP # we change this to RETURN_VALUE
9 24 NOP
26 NOP
28 NOP
30 SETUP_FINALLY 50 ( to 82 ) เนื่องจาก Pyarmor แก้ไขวัตถุรหัสในหน่วยความจำการเปลี่ยนแปลงจะยังคงอยู่แม้หลังจากที่เราออกจากวัตถุรหัส
ตอนนี้เราสามารถเรียกใช้ (EXEC) วัตถุรหัส ตอนนี้เราสามารถเข้าถึงวัตถุรหัสถอดรหัส สิ่งที่เหลืออยู่ในขณะนี้คือการลบการดัดแปลง Pyarmor ไปยังวัตถุรหัสซึ่งเป็นส่วนหัวและส่วนท้ายของ Wrap
หลังจากนั้นได้รับการทำความสะอาดเราต้องลบ __armor_enter__ และ __armor_exit__ ออกจาก co_names
เราทำซ้ำสิ่งนี้ซ้ำสำหรับวัตถุรหัสทั้งหมด
เอาต์พุตจะเป็นวัตถุรหัสต้นฉบับ มันจะเป็นเหมือน Pyarmor ที่ไม่เคยถูกนำไปใช้
ด้วยเหตุนี้เราสามารถใช้เครื่องมือที่เราชื่นชอบทั้งหมดได้เช่น Decompyle3 เพื่อรับซอร์สโค้ดดั้งเดิม
วิธีที่สามแก้ไขปัญหาสุดท้ายด้วยวิธี #2
ในวิธี #2 เรายังต้องเรียกใช้โปรแกรมและฉีด
นี่อาจเป็นปัญหาได้เนื่องจาก:
วิธีที่สามพยายามที่จะแกะ Pyarmor แบบคงที่ซึ่งฉันหมายถึงโดยไม่ต้องใช้โปรแกรมที่ทำให้งงงวย
มีสองสามวิธีที่คุณสามารถใช้งานได้อย่างไม่หยุดยั้ง แต่วิธีการที่ฉันจะอธิบายดูง่ายที่สุดในการใช้งานโดยไม่ต้องใช้เครื่องมือและ/หรือภาษาอื่น ๆ
เราจะใช้บันทึกการตรวจสอบบันทึกการตรวจสอบถูกนำไปใช้ใน Python ด้วยเหตุผลด้านความปลอดภัย ตอนนี้แดกดันเราจะใช้ประโยชน์จากบันทึกการตรวจสอบเพื่อลบความปลอดภัย
บันทึกการตรวจสอบบันทึกฟังก์ชั่น cpython ภายในเป็นหลัก รวมถึง exec และ marshal.loads ซึ่งทั้งสองอย่างนี้เราสามารถใช้เพื่อรับวัตถุรหัสหลักที่ทำให้งงงวยโดยไม่ต้องฉีด/เรียกใช้รหัส รายการบันทึกการตรวจสอบเต็มรูปแบบสามารถดูได้ที่นี่
Cpython เพิ่มสิ่งที่เป็นระเบียบที่เรียกว่า Audit Hooks ทุกครั้งที่บันทึกการตรวจสอบจะถูกเรียกใช้จะทำการโทรกลับไปยังเบ็ดที่เราติดตั้ง ตะขอจะเป็นฟังก์ชั่นที่ใช้ 2 อาร์กิวเมนต์ event arg
ตัวอย่างเบ็ดการตรวจสอบ:
import sys
def hook ( event , arg ):
print ( event , arg )
sys . addaudithook ( hook ) วิธีเดียวที่จะบันทึกวัตถุโค้ดลงบนดิสก์คือการทำมัน ซึ่งหมายความว่า Pyarmor ต้องเข้ารหัสวัตถุรหัส marshalled ดังนั้นโดยธรรมชาติแล้วพวกเขาจะต้องถอดรหัสเมื่อพวกเขาต้องการเข้าถึงมันใน Python
พวกเขาเช่นเดียวกับคนอื่น ๆ ส่วนใหญ่ใช้ Marshaller ในตัว แพ็คเกจนี้เรียกว่า marshal และเป็นแพ็คเกจในตัวที่เขียนใน C. เป็นหนึ่งในแพ็คเกจที่มีบันทึกการตรวจสอบดังนั้นเมื่อ Pyarmor เรียกมันว่าเราสามารถเห็นข้อโต้แย้งได้
วัตถุโค้ดจะยังคงมีการเข้ารหัส bytecode แต่เราได้จัดการเพื่อผ่าน "เลเยอร์" ครั้งแรกแล้วเราสามารถใช้วิธีการของเรา #2 ได้อีกครั้งจากขั้นตอนนี้เนื่องจากต้องจัดการกับวัตถุรหัสที่เข้ารหัส ความแตกต่างเพียงอย่างเดียวในตอนนี้คือวัตถุโค้ดทุกชิ้นจะถูกเข้ารหัสแทนที่จะเป็นวัตถุที่ปกติแล้วจะถูกเรียกใช้แล้วเช่นวัตถุรหัสหลัก
เพราะในวิธี #2 เราฉีดรหัสเราสามารถเข้าถึงฟังก์ชั่น pyarmor ทั้งหมดเช่น __armor_enter__ และ __armor_exit__ เนื่องจากเราพยายามที่จะแกะมันแบบคงที่เราจึงไม่มีความหรูหรา
ดังที่ฉันได้กล่าวไว้ข้างต้น Pyarmor มีโหมด จำกัด ฉันได้แสดงวิธีข้ามโหมด Bootstrap Contrict เนื่องจากสิ่งนั้นจะถูกกระตุ้นเมื่อเราเรียกใช้ฟังก์ชั่น pyarmor_runtime()
ตอนนี้เราต้องเรียกใช้ไฟล์ที่ทำให้งงงวยทั้งหมดซึ่งรวมถึงการโทร __pyarmor__ ฟังก์ชั่นนั้นกระตุ้นโหมด จำกัด อื่นดังนั้นเราจึงต้องข้ามผ่าน ก่อนอื่นฉันคิดว่าเราใช้วิธีการที่คล้ายกันโดยการแก้ไขมันโดยธรรมชาติ
เพื่อนช่วยด้วยสิ่งเหล่านี้เป็นขั้นตอนที่คุณสามารถทำได้เพื่อทำซ้ำ โปรดทราบว่าฉันพบวิธีที่ดีกว่าและง่ายขึ้น Pyarmor ตรวจสอบว่าสตริง Pyarmor มีอยู่ที่ที่อยู่หน่วยความจำเฉพาะใน __main__ เราต้องแก้ไขเช็คนี้ ดูภาพด้านล่าง
ตอนนี้วิธีที่ดีกว่าที่ฉันพบคือโหมด จำกัด ของ Pyarmor ไม่ได้ตรวจสอบว่าไฟล์หลักถูกเรียกใช้โดยตรงโดย Python หรือไม่หรือหากถูกเรียกใช้ดังนั้นเราจึงสามารถทำได้:
exec ( open ( filename )) แน่นอนหลังจากที่เราติดตั้งเบ็ดการตรวจสอบ
ปัญหาที่ฉันมีคือการตรวจสอบการตรวจสอบที่เรียกใช้ marshal.loads แต่หลังจากที่มันถูกกระตุ้นฉันต้องโหลดวัตถุโค้ดด้วยตัวเอง แต่นั่นก็จะเรียกใช้อีกครั้งดังนั้นฉันจึงเพิ่มการตรวจสอบเพื่อดูว่าไดเรกทอรี dumps อยู่หรือไม่ สิ่งนี้เป็นอันตรายเพราะหากยังมีโฟลเดอร์ dumps ทิ้งไว้ก่อนหน้านี้จะส่งผลให้สคริปต์ที่ได้รับการป้องกันโดยไม่หยุด เราต้องหาวิธีที่ดีกว่าในการทำเช่นนั้น
แก้ไข : ฉันเพิ่งค้นพบว่าฉันลืมเกี่ยวกับส่วนที่เราต้องแก้ไขการกระโดดแบบสัมบูรณ์ ส่วนนี้จะครอบคลุมว่า
เมื่อต้องการทำสิ่งนี้ในทั้งสองวิธี #2 และวิธี #3 เมื่อเราลบส่วนท้ายจะไม่มีการปะทะกับดัชนี เมื่อเราถอดส่วนหัวออก แต่มันจะทำให้ดัชนีเปลี่ยนตามขนาดของส่วนหัวดังนั้นเราจึงต้องวนรอบการกระโดดแบบสัมบูรณ์ทั้งหมดและลบขนาดของส่วนหัว ส่วนนั้นค่อนข้างง่าย
for i in range ( 0 , len ( raw_code ), 2 ):
opcode = raw_code [ i ]
if opcode == JUMP_ABSOLUTE :
argument = calculate_arg ( raw_code , i )
new_arg = argument - ( try_start + 2 )
extended_args , new_arg = calculate_extended_args ( new_arg )
for extended_arg in extended_args :
raw_code . insert ( i , EXTENDED_ARG )
raw_code . insert ( i + 1 , extended_arg )
i += 2
raw_code [ i + 1 ] = new_arg จากวิธี #3
เราวนซ้ำผ่าน bytecode และตรวจสอบว่า opcode เป็น opcode JUMP_ABSOLUTE หรือไม่ ถ้าเป็นเราจะคำนวณอาร์กิวเมนต์ (ทำให้ EXTENDED_ARG อยู่ในใจ) จากนั้นเราใช้ try_start ซึ่งเป็นขนาดของส่วนหัว (จริง ๆ แล้วเป็นดัชนีของ opcode ล่าสุดจากส่วนหัวนั่นคือเหตุผลที่เราเพิ่ม 2) และลบออกจากอาร์กิวเมนต์ของ Opcode JUMP_ABSOLUTE
ส่วนที่ยากที่สุดในการดำเนินการนี้คือการดูแล opcodes EXTENDED_ARG ที่เราอาจต้องเพิ่มเมื่อการโต้แย้งมีขนาดสูงสุด 1 ไบต์ (255) เราจัดการกับสิ่งนั้นใน calculate_extended_args
def calculate_extended_args ( arg : int ): # This function will calculate the necessary extended_args needed
extended_args = []
new_arg = arg
if arg > 255 :
extended_arg = arg >> 8
while True :
if extended_arg > 255 :
extended_arg -= 255
extended_args . append ( 255 )
else :
extended_args . append ( extended_arg )
break
new_arg = arg % 256
return extended_args , new_arg จากวิธี #3
ในการเขียนรหัสนี้ฉันต้องเข้าใจว่า EXTENDED_ARG ทำงานได้อย่างไร
บทความนี้ช่วยได้มากในการทำความเข้าใจกับรหัสนี้
คำแนะนำใน Python คือ 2 ไบต์ในเวอร์ชันล่าสุด (3.6+) หนึ่งไบต์ใช้สำหรับ opcode และหนึ่งไบต์สำหรับอาร์กิวเมนต์ เมื่อเราต้องการเกินหนึ่งไบต์เราจะใช้ EXTENDED_ARG มันใช้งานได้โดยทั่วไป:
arg = 300 # Let's say this is the size of our argumentเรารู้ว่าสูงสุดที่ได้รับอนุญาตคือ 255 ดังนั้นเราจำเป็นต้องใช้ extended_arg คุณจะคิดว่ามันจะเป็นเช่นนี้:
extended_arg = 255
arg = 45นั่นคือสิ่งที่ฉันคิดไว้ก่อน แต่หลังจากดูรหัสที่ Python สร้างขึ้นฉันสังเกตเห็นว่ามันเป็นเช่นนี้:
extended_arg = 1
arg = 44 ฉันสับสนมากว่าทำไมมันถึงเป็นเช่นนั้นเนื่องจากฉันไม่เห็นความสัมพันธ์ระหว่างสิ่งที่ฉันคาดหวังและความเป็นจริงบทความที่เชื่อมโยงข้างต้นอธิบายทุกอย่าง
Python จัดการ extended_arg ดังต่อไปนี้:
extended_arg = extended_arg * 256หลังจากเห็นสิ่งนี้ทุกอย่างชัดเจนเพราะมันหมายความว่า
extended_arg = 1 * 256
arg = 44
print ( extended_arg + arg ) จะส่งออก 300
ฉันใช้ตรรกะนั้นกับฟังก์ชั่นเพื่อให้ส่งคืนรายการของ opcodes extended_arg ที่จำเป็นและค่าอาร์กิวเมนต์ใหม่ (ซึ่งจะต่ำกว่าหรือเท่ากับ 255)
จากนั้นฉันเพียงแค่แทรก extended_arg ที่ดัชนีที่ถูกต้อง