Begin {}
TL;DR - This article describes my method for automatically updating a user's cloned PowerShell script using a custom PS1 update script, github, and some sneaky versioning techniques. Basically, I wanted to be able to have my PowerShell script read my github repo, pull down & install any updates, and ensure convenience to the user.
Luckystrike is a PowerShell-based pentesting tool used to generate malicious macro documents. It stores user created payloads and templates in a local sqlite database. This makes it convenient for the user to reuse the same payloads and templates across engagements.
The decision to use a sqlite database presented an interesting design challenge when it came to updates. How would I pull a new copy of the script down? What if I updated the sqlite schema? Would I delete/recreate the local db and force the user to have to re-add everything? I didn't want to do this because not only is it terribly inconvenient for the user, but I'm a developer and this seemed like a solvable problem! I saw several options that allowed me to do simple updates, but nothing comprehensive like I wanted, so I decided to code my own solution (I now think Chocolatey might be able to do this, but didn't know it existed at the time).
Process {}
The update process is as follows:
- When launched, luckystrike verifies an internet connection.
- It then checks a file in the repo, currentversion.txt, and compares that value to its local hardcoded value.
- If the repo value is greater, update.ps1 is downloaded from the repo and saved to the running directory.
- Luckystrike fires update.ps1 is fired in a new process, then exits itself.
- The update script calls the garbage collector (critical for clean sqlite work), downloads a new version of the schema, creates a new db based off the schema, performs db transfers, then grabs a new copy of luckystrike.ps1
- Bob's your uncle.
The bulk of the work happens in two functions, UpdatesAvailable() and Process-Updates() (please, no comments about "proper" function naming. I'm working on it :-).
I'm posting screenshots here to keep things easy. All the code is available on github in luckystrike.ps1
Except for my janky version comparison method, the UpdatesAvailable() method is pretty straightforward. Download the string for the HTTP path in my $githubver (in this case "2.0", retrieved from https://raw.githubusercontent.com/curi0usJack/luckystrike/master/currentversion.txt), then compare it against my $version var, in this case "1.1.7". While I'm sure there are far better ways to do it, the version comparison method allows for major, minor, and incremental versioning. It simply returns a boolean indicating if updates are available or not.
This method is consumed by the Process-Updates() method:
Pretty simple. This method checks for an internet connection, downloads the update file (https://raw.githubusercontent.com/curi0usJack/luckystrike/master/update.ps1), runs that update file in a new process, then exits.
Update.ps1 performs all the customized database work I need, ensuring the process is painless to the user. Once it's done and the user launches luckystrike again, update.ps1 is deleted.
Here's what the update process looks like to the user:
The nice thing about this is that everything is controlled by currentversion.txt. You can test the entire update process, even after push your changes to master, without impacting the users in case you find a bug. Once you push all your changes, simply set your local version variable to something lower than the currentversion.txt file on github and launch. You'll be prompted to update and you can do your testing. Once everything is working and all pushes are done, the last thing to update is currentversion.txt. Only then will users be prompted to update!
If you got this far, hopefully you found some value out of this. I'm sure there are much easier ways of going about solving this problem, but this worked (and continues to work) for me!
@curi0usJack
I'm posting screenshots here to keep things easy. All the code is available on github in luckystrike.ps1
Except for my janky version comparison method, the UpdatesAvailable() method is pretty straightforward. Download the string for the HTTP path in my $githubver (in this case "2.0", retrieved from https://raw.githubusercontent.com/curi0usJack/luckystrike/master/currentversion.txt), then compare it against my $version var, in this case "1.1.7". While I'm sure there are far better ways to do it, the version comparison method allows for major, minor, and incremental versioning. It simply returns a boolean indicating if updates are available or not.
This method is consumed by the Process-Updates() method:
Pretty simple. This method checks for an internet connection, downloads the update file (https://raw.githubusercontent.com/curi0usJack/luckystrike/master/update.ps1), runs that update file in a new process, then exits.
Update.ps1 performs all the customized database work I need, ensuring the process is painless to the user. Once it's done and the user launches luckystrike again, update.ps1 is deleted.
Here's what the update process looks like to the user:
End {}
The nice thing about this is that everything is controlled by currentversion.txt. You can test the entire update process, even after push your changes to master, without impacting the users in case you find a bug. Once you push all your changes, simply set your local version variable to something lower than the currentversion.txt file on github and launch. You'll be prompted to update and you can do your testing. Once everything is working and all pushes are done, the last thing to update is currentversion.txt. Only then will users be prompted to update!
If you got this far, hopefully you found some value out of this. I'm sure there are much easier ways of going about solving this problem, but this worked (and continues to work) for me!
@curi0usJack