{"id":18927,"date":"2022-05-03T08:40:04","date_gmt":"2022-05-03T16:40:04","guid":{"rendered":"https:\/\/www.palada.net\/index.php\/2022\/05\/03\/news-12660\/"},"modified":"2022-05-03T08:40:04","modified_gmt":"2022-05-03T16:40:04","slug":"news-12660","status":"publish","type":"post","link":"http:\/\/www.palada.net\/index.php\/2022\/05\/03\/news-12660\/","title":{"rendered":"Unpacking Python Executables on Windows and Linux"},"content":{"rendered":"<div class=\"aem-Grid aem-Grid--12 aem-Grid--default--12\">\n<div class=\"raw-import aem-GridColumn aem-GridColumn--default--12\">\n<div class=\"text-container\"><\/div>\n<\/p><\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p><b>Affected Platforms:<\/b>\u00a0Windows, Linux\/Unix <\/p>\n<p>Traditional programs written in the python programming language are distributed as source code and the python interpreter is used to run them. This is easy if one runs their own python code; however, it is rather cumbersome to deliver commercial products this way. To help with that, a couple of projects were created that can bundle a python program with all its dependencies into an executable file: Portable Executable (PE) on Windows and Executable and Linkable Format (ELF) on Linux\/Unix.<\/p>\n<p>Python malware is also distributed as such packed executables. And if we talk about malware, the question always come up, \u201chow can we unpack and decompile the malware to look at its python source code?\u201d I discussed this topic in a\u00a0<a href=\"https:\/\/www.youtube.com\/watch?v=jmC-FKNRdvk\" target=\"_blank\">video<\/a>\u00a0I created two years ago. But since then, new python versions have come out and the unpacking techniques have changed.<\/p>\n<p>In this blog post, we are going to go through the following topics:<\/p>\n<ul>\n<li>Packing<\/li>\n<li>Unpacking and decompiling on Windows below python version 3.9<\/li>\n<li>Unpacking and decompiling on Linux after python version 3.9<\/li>\n<\/ul>\n<p>Differentiating between the older and newer python versions is important since a lot changed after python 3.9, both in how python bytecode is generated and how (and whether) the source code can be recovered.<\/p>\n<h2>Packing<\/h2>\n<p>First of all, let\u2019s discuss what python packaging is\u2014and specifically, PyInstaller. (Note: I use the terms packaging, packing, and bundling interchangeably.) The goal of packaging a python program is to create an executable that can run independently on an operating system. We should not confuse this with general malware packing, where the goal is to hide malicious code from analysts and security tools. Python packaging does not intend to provide any security or obfuscation. It is only a side-effect of the packaging. When we bundle a python program, the tool that we use for packaging, such as PyInstaller, does the following:<\/p>\n<ul>\n<li>Compiles all <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">.py<\/span><\/span> source files to python bytecode (<span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">.pyc<\/span><\/span> files)<\/li>\n<li>Collects all python compiled source code and python dependencies<\/li>\n<li>Includes the operating system-dependent python interpreter (i.e.; <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">libpython3.9.so.1.0<\/span><\/span> on Linux or <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">python37.dll<\/span><\/span> on Windows)<\/li>\n<li>Bundles all this with a stub that first unpacks these files to disk or memory and then executes the original python code with the included interpreter.<\/li>\n<\/ul>\n<p>While there are a few projects that can create such packaged executables, the most well-known is\u00a0<a href=\"https:\/\/pyinstaller.org\/en\/stable\/\">PyInstaller<\/a>.<\/p>\n<p>To understand how packaging works, we create a packed python executable on Windows. Figure 1 shows an extremely sophisticated example program that requires a master\u2019s degree in computer engineering and around 10 years of experience to create.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image.img.png\/1651513920792\/ing1.png\" alt=\"Test program \u201cevil_program.py&#34;\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 1 &#8211; Test program \u201cevil_program.py\u201d<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>We can easily run this program in a Windows terminal, as shown in Figure 2.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_1395578215.img.png\/1651514578056\/img2.png\" alt=\"Example of program running evil_program.py\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 2 &#8211; Running evil_program.py<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>To turn this python program into a packaged EXE file we can use PyInstaller, which I installed in a python virtual environment (Figure 3).<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_195560302.img.png\/1651514613939\/img3.png\" alt=\"Figure 3 - Creating an EXE with PyInstaller\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 3 &#8211; Creating an EXE with PyInstaller<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>It is worth your time to scroll through the logs since they give some insight into what PyInstaller does under the hood. Once finished, the newly created <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">evil_program.exe<\/span><\/span> is listed under the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">dist<\/span><\/span> folder. Figure 4 shows that we can run this executable and get the same result as directly running the code. The big difference is that we can now move this EXE file to another Windows machine and it should run standalone without any python dependency.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_615940248.img.png\/1651514655615\/img4.png\" alt=\"Example of program running the newly created EXE file\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 4 &#8211; Running the newly created EXE file<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<h2>Unpacking python &lt; 3.9 on Windows<\/h2>\n<p>Now that we have a packed EXE file, we can try to revert it back to python source code. In a real reverse engineering scenario, the first question is usually, \u201chow do we find out that the analyzed binary is a packed python program?\u201d The most common clue is that we will see a lot of strings starting with <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">py<\/span><\/span> (Figure 5).<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_1986090252.img.png\/1651514681872\/img5.png\" alt=\"Figure 5 - Searching for &#39;py&#39; in the binary&#39;s strings\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 5 &#8211; Searching for &#39;py&#39; in the binary&#39;s strings<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>Specifically to PyInstaller, we will also see the string <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">MEIPASS\u00a0<\/span><\/span>in the binary.<\/p>\n<p>The second question is, \u201cwhich python version is used by the program?\u201d The easiest way to find this out is to run the program and monitor what files are created in the operating system\u2019s (OS) temporary folder. That\u2019s because PyInstaller first unpacks all files in the temporary folder. By monitoring the filesystem activity, we can see that the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">python38.dll<\/span><\/span> (Figure 6) is saved in the temporary folder. This tells us that python 3.8 was used to create the packed program and therefore we will need the same python version for all further analysis.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_1293048958.img.png\/1651514718358\/img6.png\" alt=\"Figure 6 - Monitoring filesystem activity\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 6 &#8211; Monitoring filesystem activity<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>To recover the source code, we have to tackle two challenges:<\/p>\n<ol>\n<li>Unpack all files from the EXE file. That will give us compiled python bytecode (<span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">.pyc<\/span><\/span>) files<\/li>\n<li>Decompile the interesting <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">.pyc<\/span><\/span> files<\/li>\n<\/ol>\n<p>The process of unpacking the EXE file will be similar in all versions of python under all operating systems. The bigger challenge is decompiling the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">.pyc<\/span><\/span> files, because that changes in every python version and tools only work with specific versions.<\/p>\n<p>For unpacking this EXE file, we will use\u00a0<a href=\"https:\/\/github.com\/extremecoders-re\/pyinstxtractor\">pyinstxtractor<\/a>. Just download the pyinstxtractor.py to the folder where you want to work with it (Figure 7 shows how to do that). An important detail to note is that the python interpreter used must be the same version as the packed <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">python\u00a0<\/span><\/span>program.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_549681519.img.png\/1651514750492\/fig7.png\" alt=\"Figure 7 - Unpacking evil_program.exe\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 7 &#8211; Unpacking evil_program.exe<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>Another important detail is that pyinstxtractor also provides hints as to which files could be the main file of the python program. There are often some false positives, but this is still a huge help if the analyzed project is big. In this case, we know that the main file is <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">evil_program.pyc<\/span><\/span>. The EXE is unpacked into the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">evil_program.exe_extracted<\/span><\/span> folder (Figure 8).<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_295314790.img.png\/1651514782052\/img8.png\" alt=\"Figure 8 - Extracted .pyc files\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 8 &#8211; Extracted .pyc files<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>The next step is to decompile the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">evil_program.pyc<\/span><\/span>. For that, we will use a tool called\u00a0<a href=\"https:\/\/github.com\/rocky\/python-uncompyle6\">uncompyle6<\/a>. Again, this is a point where one must be conscious about the python version and consult the documentation of the tool being used for decompilation. Uncompyle6 only supports up to python 3.8. After that, you will have to look for another tool (which we will discuss in the next section). The decompilation process is shown in Figure 9.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_149720058.img.png\/1651514813164\/img9.png\" alt=\"Figure 9 - Decompiling the evil_program.pyc\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 9 &#8211; Decompiling the evil_program.pyc<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>With that, we have reached our goal and recovered the source code of this simple packed python program.<\/p>\n<h2>Unpacking python &gt;= 3.9 on Linux<\/h2>\n<p>In this section, we are going to go through the same process under Linux using a newer python version. The file we will analyze is a real malware sample that we found on VirusTotal during our recent threat hunting. More information about this binary can be found on\u00a0<a href=\"https:\/\/www.virustotal.com\/gui\/file\/b7b4482f87ca0e532014392f7c099716e4ace647c9f4f8b500839a2d083c3792\">VirusTotal<\/a>. Once I reverse engineered the file, I thought it would be interesting to write a blog post about the unpacking process. We are not going to focus on the analysis of the sample in this post.<\/p>\n<p>To unpack the sample, we again use pyinstxtractor, but with a\u00a0<a href=\"https:\/\/github.com\/extremecoders-re\/pyinstxtractor\/wiki\/Extracting-Linux-ELF-binaries\">twist<\/a>. Figure 10 shows that the sample is a 64-bit ELF binary. We cannot use pyinstxtractor directly on the ELF binary. So, we first need to dump the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">pydata<\/span><\/span> section of the file into a separate file and run pyinstxtractor on that.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_1519285623.img.png\/1651514843377\/img10.png\" alt=\"Figure 10 - Dump of the pydata section\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 10 &#8211; Dump of the pydata section<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>The unpacking is shown in Figure 11. Again, we need to be conscious of using the correct python version, which in this case is 3.9.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_2005610563.img.png\/1651514874245\/img11.png\" alt=\"Figure 11 - Unpacking the pydata.dump\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 11 &#8211; Unpacking the pydata.dump<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>The fact that there is a <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">RansomWare.pyc<\/span><\/span> in the unpacked data makes it obvious what we are dealing with.<\/p>\n<p>With python 3.9 we can no longer use <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">uncompyle6<\/span><\/span>. Instead, we can use a tool like\u00a0<a href=\"https:\/\/github.com\/zrax\/pycdc\">Decompyle++<\/a>, which is a very promising project that uses a different, more generic, approach to decompilation. However, building the project is not very well explained on the website, so Figure 12 shows you how to download and build it.\u00a0<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_1000799724.img.png\/1651514903748\/imh12.png\" alt=\"Figure 12 - Building Decompile++ pycdc\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 12 &#8211; Building Decompile++ pycdc<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>To call the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">pycdc<\/span><\/span> command from anywhere, we can also run <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">sudo make install<\/span><\/span>.<\/p>\n<p>The <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">pycdc<\/span><\/span> command is the decompiler, so we use that to recover the source code of the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">RansomWare.pyc<\/span><\/span><span style=\"font-size: 11.0pt;\"><span style=\"font-family: Calibri , sans-serif;\">,<\/span><\/span> as shown in Figure 13.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_1947649241.img.png\/1651514934513\/img13.png\" alt=\"Figure 13 - Decompiling the RansomWare.pyc\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 13 &#8211; Decompiling the RansomWare.pyc<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>With that, we have reached our goal of recovering most of the original source code of <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">RansomWare.py<\/span><\/span><span style=\"font-size: 11.0pt;\"><span style=\"font-family: Calibri , sans-serif;\">.<\/span><\/span> Unfortunately, we may also see functions like the one in Figure 14, where decompilation failed at some point.\u00a0<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_1552259133.img.png\/1651514987135\/img14.png\" alt=\"Figure 14 - Failed to decompile the write_key() function\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 14 &#8211; Failed to decompile the write_key() function<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>This also happens in Java and .Net when we decompile the bytecode. Sometimes, the decompiler fails and we only get partial code. In such cases, we need to find other ways to determine what happened in that function, such as dynamic analysis. In this case, we can use the <span style=\"font-family: &quot;Courier New&quot;;\"><span style=\"font-size: 10.0pt;\">pycdas<\/span><\/span> command to recover the \u2018disassembled\u2019 bytecode. There we can look up the functions, where the decompilation failed. Figure 15 shows the bytecode disassembly of the <span style=\"font-family: &quot;Courier New&quot;;\">write_key()<\/span>\u00a0function.<\/p>\n<\/p><\/div>\n<div class=\"cmp cmp-image aem-GridColumn--default--none aem-GridColumn aem-GridColumn--default--8 aem-GridColumn--offset--default--2\">               <noscript data-cmp-image=\"{&#34;smartImages&#34;:[],&#34;smartSizes&#34;:[],&#34;lazyEnabled&#34;:true}\">             <img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image_501327335.img.png\/1651515015993\/img15.png\" alt=\"Figure 15 - Bytecode disassembly of the write_key() function\"\/>         <\/noscript>          <span class=\"cmp-image--title\">Figure 15 &#8211; Bytecode disassembly of the write_key() function<\/span>         <\/div>\n<div class=\"cmp cmp-text aem-GridColumn aem-GridColumn--default--12\">\n<p>At the beginning of this section, I mentioned that we won\u2019t be analyzing the sample. It looks like ransomware, the python code was written for Windows but packed as an ELF executable, which usually runs on Linux\/Unix systems. This may indicate that the sample is intended for the Windows Subsystem for Linux (WSL). But that\u2019s a story for another blog post.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this blog post we covered how to unpack and decompile python programs packaged with PyInstaller. We also discussed the following scenarios:<\/p>\n<ul>\n<li>Windows<\/li>\n<li>Linux<\/li>\n<li>Python versions greater or equal to 3.9<\/li>\n<li>Python versions lower or equal to 3.8<\/li>\n<\/ul>\n<p>Reverse engineering python malware can be very useful because we can analyze it at a source code level, which of course is much more efficient.<\/p>\n<h2>Fortinet Protection<\/h2>\n<p>The analyzed ransomware sample in this blog is detected by the following (AV) signature:<\/p>\n<p>ELF\/Filecoder.IG!tr<\/p>\n<p>Since PyInstaller writes the unpacked files on the disk before executing it, FortiEDR is also able to identify the malicious content.<\/p>\n<h2>IOCs<\/h2>\n<p>For the discussed ransomware sample:<\/p>\n<p><i>Hxxps:\/\/images[.]idgesg[.]net\/images\/article\/2018\/02\/ransomware_hacking_thinkstock_903183876-100749983-large[.]jpg<br \/>  lynrx_at_protonmail[.]com<br \/>  fernet_key.txt<br \/>  EMAIL_ME.txt<\/i><\/p>\n<p><i>Learn more about Fortinet\u2019s <a href=\"https:\/\/www.fortinet.com\/fortiguard\/labs?utm_source=blog&amp;utm_campaign=fortiguard-labs\">FortiGuard Labs<\/a> threat research and intelligence organization and the FortiGuard Security Subscriptions and Services <a href=\"https:\/\/www.fortinet.com\/fortiguard\/labs?tab=security-bundles&amp;utm_source=blog&amp;utm_campaign=security-bundles\">portfolio<\/a>.<\/i><\/p>\n<\/p><\/div>\n<div class=\"raw-import aem-GridColumn aem-GridColumn--default--12\">\n<div class=\"text-container\">\n<div id=\"om-b2dxtopzidsdt3fkzfsv-holder\"><\/div>\n<\/div><\/div>\n<\/p><\/div>\n<p><a href=\"https:\/\/www.fortinet.com\/blog\/threat-research\/unpacking-python-executables-windows-linux\" target=\"bwo\" >http:\/\/feeds.feedburner.com\/fortinet\/blog\/threat-research<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p><img decoding=\"async\" src=\"\/blog\/threat-research\/unpacking-python-executables-windows-linux\/_jcr_content\/root\/responsivegrid\/image.img.png\/1651513920792\/ing1.png\"\/><br \/>FortiGuard Labs provides a deep dive on unpacking Python executables on Windows and Linux. Read to learn more about packing, unpacking, and decompiling on these operating systems.<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"colormag_page_container_layout":"default_layout","colormag_page_sidebar_layout":"default_layout","footnotes":""},"categories":[10424,10378],"tags":[],"class_list":["post-18927","post","type-post","status-publish","format-standard","hentry","category-fortinet","category-security"],"_links":{"self":[{"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/posts\/18927","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/comments?post=18927"}],"version-history":[{"count":0,"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/posts\/18927\/revisions"}],"wp:attachment":[{"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/media?parent=18927"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/categories?post=18927"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.palada.net\/index.php\/wp-json\/wp\/v2\/tags?post=18927"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}