{"id":24903786,"url":"https://github.com/cleasbycode/jpws","last_synced_at":"2025-07-02T20:34:54.081Z","repository":{"id":271773875,"uuid":"692256514","full_name":"CleasbyCode/jpws","owner":"CleasbyCode","description":"Embed a PowerShell script within a JPG image to create a tweetable JPG-PowerShell polyglot file.","archived":false,"fork":false,"pushed_at":"2025-06-16T10:34:09.000Z","size":17685,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-16T11:38:40.382Z","etag":null,"topics":["hacking","icc-profile","image","infosec","invoke-psimage","iwr","jpeg","jpg","polyglot","polyglot-files","powershell","powershell-script","pwsh","pwsh-scripts","script","tweet","tweetable","twitter","x-platform","x-twitter"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CleasbyCode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-09-15T23:51:45.000Z","updated_at":"2025-06-16T10:34:13.000Z","dependencies_parsed_at":"2025-03-19T21:34:49.908Z","dependency_job_id":"ee66394b-602a-41d9-9e63-7575d23622fa","html_url":"https://github.com/CleasbyCode/jpws","commit_stats":null,"previous_names":["cleasbycode/jpws"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/CleasbyCode/jpws","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleasbyCode%2Fjpws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleasbyCode%2Fjpws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleasbyCode%2Fjpws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleasbyCode%2Fjpws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CleasbyCode","download_url":"https://codeload.github.com/CleasbyCode/jpws/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleasbyCode%2Fjpws/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263211282,"owners_count":23431287,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacking","icc-profile","image","infosec","invoke-psimage","iwr","jpeg","jpg","polyglot","polyglot-files","powershell","powershell-script","pwsh","pwsh-scripts","script","tweet","tweetable","twitter","x-platform","x-twitter"],"created_at":"2025-02-01T22:31:10.312Z","updated_at":"2025-07-02T20:34:54.069Z","avatar_url":"https://github.com/CleasbyCode.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jpws\n\nEmbed a ***PowerShell*** script within a ***JPG*** image to create a tweetable ***JPG-PowerShell*** polyglot file.  \n\n![Demo Image](https://github.com/CleasbyCode/jpws/blob/main/demo_image/jpws_46112.jpg)  \n***Credits:  \n{Image \"Rainbow Dragon\" [Duncan Crombie / @theartofweb](https://x.com/theartofweb) PowerShell \"text-sine.ps1\" [Darren Shaw / @gierrofo](https://x.com/gierrofo)}***  \n  \n## Usage (***Linux***)\n\n```console\n\nuser1@linuxbox:~/Downloads/src$ sudo apt-get install libturbojpeg0-dev\nuser1@linuxbox:~/Downloads/src$ g++ main.cpp -O2 -lturbojpeg -s -o jpws\nuser1@linuxbox:~/Downloads/src$ sudo cp jpws /usr/bin\n\nuser1@linuxbox:~/Desktop$ jpws\n\nUsage: jpws [-alt] \u003ccover_image\u003e \u003cpowershell_script\u003e  \n       jpws --info\n\nuser1@linuxbox:~/Desktop$ jpws dragon.jpg sinewave.ps1\n\nSaved \"PowerShell-embedded\" JPG image: jpws_10247.jpg (121098 bytes).\n\nComplete!\n```\nhttps://github.com/user-attachments/assets/f5b87dbf-885e-4cb5-a70c-5879c82f7e20\n\n## How It Works\n\n***PowerShell comment blocks!***\n\nAn open comment-block is required as near to the beginning of the image file as possible.  \n\nThis is achieved by writing the two byte comment-block \"**\u003c#**\" (*0x3C, 0x23*) within the ***JFIF*** segment (***FFE0***).  \n\nThese bytes within the ***JFIF*** segment are conveniently preserved by ***X/Twitter***.  \n\n![JFIF Image](https://github.com/CleasbyCode/jpws/blob/main/demo_image/first_block.png) \n\n***When downloading images from X/Twitter, always click the image in the post to FULLY EXPAND it before saving. This ensures you get the original size image with all the embedded data.***\n\nThe ***PowerShell*** script is stored at the end of the colour profile data of the ***JPG*** image, which is also preserved. We use the first ***PowerShell*** open comment-block within the ***JFIF*** segment to ignore the ***ICC*** profile segment ***FFE2*** along with the colour profile data.  \n\nWe then have a close comment-block at the end of the colour profile data, followed by the ***PowerShell*** script, which now gets interpreted. At the end of the script (*still within the colour profile segment*) we use another open comment-block so that ***PowerShell*** ignores the remaining contents of the image file.  \n\nFinally, for comment-block compliance, we need a close comment-block as near to the end of the image file as possible. \n\nOf course, things are never as straightforward as we would like them to be. The title for this section should probably of been \"***How It Sometimes Works***\".  \n\n## Compatibility, Issues and Limitations.\n\nUsing the *libjpeg-turbo library*, ***jpws*** will first re-encode the cover image as a ***progressive*** encoded ***JPG***.  \n\nThese images are identified by the segment marker \"***FFC2***\" (*Start of Frame 2/Progressive DCT*).  ***X/Twitter*** uses this method for encoding ***JPG*** images posted on its platform.\n\nA progressive encoded image posted on ***X/Twitter***, *within file \u0026 dimension size limits*, will *not be re-encoded.  \n\nWhat you post will be the same as what you download.  **Note: ***X/Twitter*** will re-encode/repair sections of the image if bytes of the compressed image data are corrupted/modified. More on that later*. \n\nThe cover image ***must not*** contain any occurrence of the ***PowerShell*** close comment-block string \"***#\u003e***\" (*0x23, 0x3E*), apart from the ones inserted by ***jpws***, as this will break the ***PowerShell*** script.  \n\nUnfortunately, with the close comment-block string length being only two bytes, the probability that this character sequence will appear somewhere within the cover image is obviously quite high. The larger the image, the greater the probability of multiple comment-block character sequences. ***jpws*** has a maximum size limit of ***2MB*** for your cover image. \n\nIt is often possible to remove these comment-block character sequences by using the following procedure.  \n\nAfter initially re-encoding the cover image as a progressive JPG with a 97 quality value setting, ***jpws*** will search the image file for the two byte comment-block sequence. If found, the image is re-encoded (*using libjpeg-turbo \u0026 stb_image*) with the width \u0026 height dimension size being reduced by one pixel. Image quality scale only starts to decrease by 2 every 15th decrease cycle.\n\nThe modified image is searched again for the two byte sequence and the process is repeated, incrementing the dimension size reduction value with each cycle, until no comment-block sequences are found. There is a default of ***three hundred*** decrease attempts before ***jpws*** gives up and requests you either use a different image or manually reduce the current image's dimensions (scale) using an editor, such as ***GIMP***, then retry this program. \n\nFor the final close comment-block, we overwrite the last eleven bytes of image data with a default string (*0x00, 0x00, 0x20, 0x20, 0x00, 0x00, ***0x23, 0x3E, 0x0D, 0x23***, 0x9e*).  \n\nTo have any chance of getting this to work, we have no choice but to overwrite bytes (using the above string) within a section of the image that is compressed/encoded. While this technically corrupts the image, most show no visible signs of distortion (miniscule for those that do) and no program refuses to display the image. ***X/Twitter*** allows it to be posted without complaint.  \n\nThe program ***ImageMagick*** does inform us about this corruption when we use the ***identify -verbose*** option. \"*Corrupt JPEG data: premature end of data segment*\".\n\nThe change of bytes triggers ***X/Twitter*** to re-encode/repair this small section of data (see images below).  ***ImageMagick*** no longer reports corruption when we check one of these downloaded images.\n\nThe first six bytes of the string can help with the encoding and are expendable, as it does not matter if they are changed or removed, but the following four bytes (***0x23, 0x3E, 0x0D, 0x23***) are crucial and need to be preserved by ***X/Twitter*** for the ***PowerShell*** script to work after tweeting the image.  \n\nFor many images, these four bytes (even the whole string) are preserved by ***X/Twitter***, but occasionally they are partially or completely removed for some images, which will cause the embedded ***PowerShell*** script to fail.\n\nWe can only find out which images initially work after tweeting them.  It is recommended that you test tweet the output image to make sure the crucial bytes are preserved before intentionally sharing the image with others. \n\nIf an image fails to correctly preserve the crucial four bytes, you can retry ***jpws*** using the ***-alt*** option:  \n\n```console\nuser1@linuxbox:~/Desktop$ jpws -alt cover_image23.jpg fibo.ps1\n```\n\nWith this option selected, ***jpws*** will use a slightly different eleven byte string, that often works for images that have failed with the default string. Manually reducing the image dimensions (scale) using ***GIMP*** and retrying ***jpws*** is another option. \n\nThe first image below shows the default eleven byte close comment-block string from a ***JPG-PowerShell*** polyglot image ***before*** it has been tweeted.  \n\n![BEFORE Image](https://github.com/CleasbyCode/jpws/blob/main/demo_image/before_tweet.png) \n\nThe second image shows the same section of the file, this time ***after*** being tweeted. You can see that the string with the four crucial bytes has been preserved. You can also see (within the orange border) some extra bytes that have been generated by the ***X/Twitter*** re-encoding/repair process.  \n\n![BEFORE Image](https://github.com/CleasbyCode/jpws/blob/main/demo_image/after_tweet.png) \n\nThis repo contains a number of ready to use compatible images, should you want to save time in finding a working cover image. \n\nA compatible image is a ***JPG*** that does not contain any occurrence of the close-comment block string (***#\u003e***) and preserves the four crucial bytes (***0x23, 0x3E, 0x0D, 0x23***) near the end of the file, after the ***JPG-PowerShell*** polyglot image has been tweeted.\n\nAs mentioned earlier, when downloading an image from ***X/Twitter***, make sure to first click the image in the post to fully expand it, then save it, so that you get the full original size image. \n\n***X/Twitter*** has a maximum size limit of ***10KB*** for the colour profile data segment (***0xFF 0xE2 0x28 0x00***), in which we are storing the ***PowerShell*** script. With the overhead of the profile data, you will have about ***9.5KB*** available for your ***PowerShell*** script.\n\n***PowerShell*** scripts that use a top \"script-level\" ***param(...) block*** will ***not work*** when embedded within an image. The param block enforces strict parsing at the start of the script. The only things allowed before the param block are comments or blank lines (and sometimes a #requires statement). But having certain binary bytes before param will break parsing. A param block inside a function, rather than at the top of the script, should work fine. \n\n## Executing The Embedded PowerShell Script\n\nThe easiest way to download the image from ***X/Twitter*** and run the embedded ***PowerShell*** script, is to use ***wget*** for *Linux and ***iwr*** for Windows.\n**Make sure ***PowerShell*** is installed on your Linux PC.*  \n\nYou will first need to get the image link address from ***X/Twitter***, after you have posted the embedded image.\n\nClick the image in the post to fully expand it, then ***right-click*** on the image and select \"***Copy image address***\" from the menu.\n\nLinux: \n```console\nwget -O game.jpg \"https://pbs.twimg.com/media/GhZTR8BXgAACc9Q?format=jpg\u0026name=medium\";pwsh game.jpg\n```\n\nWindows:\n```console\niwr -OutFile Game.ps1 \"https://pbs.twimg.com/media/GhZTR8BXgAACc9Q?format=jpg\u0026name=medium\";.\\Game.ps1\n```\n\nAlternatively, you can just manually download the image from ***X/Twitter*** (remember to click on the image within the post to fully expand it before saving).\n\nTo run the script embedded within the image using Linux, just enter the following command within a terminal. There is no need to change the file extension with Linux. \n\n```console\n$ pwsh jpws_85681.jpg\n```\nFor Windows, after downloading the image from ***X/Twitter***, you will need to rename the ***.jpg*** file extension to ***.ps1***, also, depending on the Windows/PowerShell execution policy,\nyou will probably need to unblock the file before you can run the embedded script. You can see why the ***iwr*** option is the most convenient. \n\n```console\nG:\\demo\u003e ren jpws_85681.jpg jpws_85681.ps1\nG:\\demo\u003e Unblock-File jpws_85681.ps1 (Only required for downloaded images or images taken from another machine).\nG:\\demo\u003e .\\jpws_85681.ps1\n```\nhttps://github.com/user-attachments/assets/2e49ea69-7e33-4b43-bcba-cb0a9678a4f7\n\n## Third-Party Libraries\n\nThis project makes use of the following third-party libraries:  \n\n[stb_image](https://github.com/nothings/stb) by Sean Barrett (“nothings”)  \n\nlibjpeg-turbo (see [***LICENSE***](https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/LICENSE.md) file)  \n{This software is based in part on the work of the Independent JPEG Group.}\n##\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcleasbycode%2Fjpws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcleasbycode%2Fjpws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcleasbycode%2Fjpws/lists"}