2026/06/27

Fun with little project with LLMs and MiSTer FPGA

Over the last year I've been using the Claude, Codex, and Gemini in OSS Code (linux version of VS Code). I wanted to share a bit of my experiences. At first I used Claude to update the codebase of an open source project that I used to contribute to. At some point I had it almost completely updated to Python 3.x with the six and futurize over a span of couple of weekends. For contrast, with Claude, I was able to do it in a single evening, and then I didn't stop there. The next evening I created a couple experimental forks, replacing almost completely the ODM - mongoengine with pymongo. At that point, my production instance has been moth-balled for almost 3 years, so there was no incentive to take it further, and it seemed just too easy.


My next project involved something much more difficult. 


MiSTer FPGA - an open-source hardware project that uses programmable chips (Field Programmable Gate Arrays) to perfectly replicate classic video game consoles, arcade machines, and vintage computers. Instead of using software to emulate games, it configures the FPGA hardware to behave exactly like the original retro systems.


When I was growing up I owned a couple of models of Commodore Amiga, and before I decided to co-opt the LLMs to help me, I've already made a couple of contributions to fix some bugs in the Tobi Gubner's soft-core implementation of 68020 called tg68k.c. 


MiSTer had a pretty good implementation of the lower end Amigas, but there were two things that were missing.


One of them was an emulation of a network card, most computer cores on MiSTer are still using a kludgy setup from the modem era involving sneaker-neting the packets over the serial port, and feeding it to the TCP/IP stack. Quite slow and brittle, as the hardware CTS/RTS got broken at some point, and it would mostly work with xOFF/xON.

I ended up compromising on the lowest common denominator from the era, the ne2k based cards. Making the AmigaOS recognize the card was pretty quick and easy, I did it without the AI, just duplicating the existing sound card in the VHDL code.


Feeding the RTL8019 data sheets and prototyping the registers, and memory areas took a few weeks, while I was pivoting onto other projects. The Linux interfacing with the eth0 took a few tries. 


In the early days I was just using the chat window in the web browser to try to wedge into the existing networking interface and do what SLIRP used to do for the modem connections. 

The toughest nut to crack for me was getting the communication channels to work between the FPGA side and the Linux (HPS). It was rather poorly documented, and trying to re-use the existing plumbing was not optimal, but finally a project popped up that had a working implementation, and it took whole 30 minutes to make it working in my implementation.



For starters, I needed a quick and easy way for initial testing, so I had to add some code in the MiSTer Menu that runs on the Arm core and handles the core management.




The proper bridging will come a little later, as it will likely use the same approach as it is commonly used for the containers (macvlan). For now, I'm just using a rawsocket in promiscous mode, some custom filtering code, and cBPF to filter out the local broadcasts. 

The next choke point was a bit unexpected. I had the card working, but the download speeds were absolutely pathetic.


The breaktrough came when I was poking around with the ethtool, and noticed that the offload was enabled on the eth0 interface... A quick "ethtool -K eth0 gro off lro off gso off tso off" and the numbers improved by an order of magnitude! Then it was just one more testbench and a full rebuild of the RBF, and we got some decent numbers.


At least the networking is in usable state, the next step is making a generic scaffolding for other cores that could use networking for quality of life upgrade, such as ao486, and adding proper integration with kernel modules such as macvlan.


The other was a lack of the more advanced CPUs than MC68020. No MMU, so one could not use the more advanced debugging tools, nor run an OS like NetBSD. Having an FPU would also be a great quality of life upgrade, as it would allow to run some 3D rendering games or demos.  







So that’s where things stand.

Networking is no longer a blocker. The path toward a more complete Amiga  - MMU, FPU, and better system software support - is clearer than it was a year ago. It’s just slower, more tedious validation and debugging.

We're getting there, chipping away at night, weekend after weekend.

2017/06/16

Doing it the hard way...


The other week I took a moment to play Gh0st Network CTF at RVASec 2017. I decided to write about solving that problem the hard way. The problem was as follows:

da Vinci Crypto, LLC 400
There is a hi-tech cryptex at the organizer's table that might have some valuable information, but we do not know the pin to open it. The manufacturer provided us with a page that simulates (https://metactf.com/metactf2017/cryptex.html) the lock by generating random pins, and it checks whether or not the code is correct. Their simulator has more rings than ours, but they said that the pin to the cryptex we have is the first 5 digits of the cryptex on the webpage. Can you reverse engineer the page and unlock the cryptex? (To make sure that everyone gets a chance, you may not spend more than a minute with the lock if there are people waiting for it.)

Lock

The solution:

The long story short, looking at the code there's a function validate_lock() that takes a number and checks for quite a few constraints...

Having a limited time and only a laptop with me (no fancy GPUs), I wrote a  for-loop in JS to solve the combination for the lock. I figured that I should start from the end of the key space, and reducing the search space as much as possible, trying to skip unnecessary computations. Here's a glorified for-loop in JavaScript.

for(n=999999999999;n>=109+59;n--){
    if (n%109==59){
        if (n%83==70){
            if (n%71==45){
                if (n%59==15) {
                    if (n%41==14) {
                        if (n%13==6) {
                            if (n%11==1) {
                                if (n%7==4) {
                                    document.write("hit:" +n + "<br>\n");
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

After running it for a bit, it came back with the solution...


791459570661


P.S. The solution for the real cryptex is right on the picture... look at the very bottom... 79145...



2015/04/11

Fun with Symantec Endpoint Protection Local Quarantine Files

I've spent some time lately tearing apart the SEP Local Quarantine files, luckily they seem to be much less complicated than the SEP Central Quarantine files, but they seem to use the same basics in terms of structures used to store data and indicate field sizes. The big problem is that once you lift VBN files from the original device, Symantec's QExtract tool is basically useless, and there is no way SEP will extract it on another machine.

Many forensic blogs suggest doing XOR to obtain the binary, but in most cases the binaries are also split into 4k chunks with data structures starting with 0x09 followed by 4 bytes indicating the size of the chunk. I think Hexacorn blog(1) was the frst to point out the existence of such "chunk separators", but Shane King(2) was the one that figured out that the 4 bytes there stand for the size of the chunk.  Having a file peppered with such chunks might ruin your day if you are going to do anything serious with the extracted file. Since, I didn't want to limit the extraction to just PE binaries, I had to try to figure out some of these structures across a set of samples that I had available.

The other day, I was almost done with the extraction piece, when one of the more anomalous .VBN files turned out to contain two payloads. Luckily, it turned out to be the exactly the same binary as the first one, well at least the size and MD5 did match.

So the code is available on github:
https://github.com/frbapolkosnik/crits_services/blob/seplq_service/SEPLQ_service/SEPLQ.py

And you can either use it in CRITs or just import it into your little script, and save the payload the way you like.

(1) http://www.hexacorn.com/blog/2012/09/21/dexray-decrypting-vbn-files-part-2/
(2) http://dofir.net/post/81425257003/a-study-of-symantecs-vbn-file-format

2014/06/26

Lately, I was able to get my hands on a used/refurbished WZR-1750DHP. Amazon marketplace had it in virtually new condition for under $90. 512MB of memory and dual core SoC made me think that it could be worth it. 

The original Buffalo firmware, while nice, was a major PITA straight from the 90s; almost any config change required a reboot. Turns out that Buffalo has WZR-1750DHPD, which essentially is the same thing with a nicely done DD-WRT firmware. It turns out that it's relatively easy to flash the Buffalo-supported DD-WRT firmware onto WZR-1750DHP. 

After loading Buffalo-supported version, the router turns into a far superior device than almost anything I had before. The only con is lack of the external antennas for extended range.

2014/04/17

Some thoughts on coding in Python

I've been working on improving my Central Quarantine extractor script lately. One of the snags I hit was a stupid bug in zipfile module in Python 2.x-3.4.

It has to do with archive names not matching between two different places. When such mismatch occurs zipfile simply raises an exception and refuses to extract the files.

This mismatch thing is actually a feature in Zip format that allows the files to be effortlessly renamed without any need to re-pack the various files stored inside the ZIP file.

Since I was making sure that my code runs fine on both Python 2.7.6 and 3.4 the only point of contention was the bastardized zipfile module. The one from 2.7.6 would error out with Python 3.4 and the one from 3.4 would do the same whe running on 2.7.6...  I've added a conditional importing, but I've not tested it with other versions of Python...

After jumping through the hoops to ensure that my code runs properly on Python 3.4, I feel much better now that it also works perfectly well with 2.7.6.

2012/09/16

It seems that I'm breaking out of a coding coma lately. I've started fixing stuff in Kludge, and it is fun. The downside is that it is really time consuming, as nothing comes easy when one needs to dig into the details.

2012/05/24

CTF write-ups


I hope you'll enjoy the write-ups as much as I've enjoyed solving the challenges.

Crypto 1
Points: 25
Instructions: I read about this once... -- Submit in all lowercase no spaces
Category: Crypto/Stego
File: f3d0c9e33f9479a5109445d6c00f12559ea21e75

Solution:

$ file ./f3d0c9e33f9479a5109445d6c00f12559ea21e75.txt
./f3d0c9e33f9479a5109445d6c00f12559ea21e75.txt: gzip compressed data, from Unix, last modified: Fri Mar 23 13:06:29 2012

$ cat ./f3d0c9e33f9479a5109445d6c00f12559ea21e75.txt | tar -tzvf -
-rw-r--r-- jdm/jdm          76 2012-03-23 13:04 86d13ae80f15bda5818552afb669f86bb02af9a0

$ cat ./f3d0c9e33f9479a5109445d6c00f12559ea21e75.txt | tar -zxvf -

$ cat 86d13ae80f15bda5818552afb669f86bb02af9a0
DOHA JPWOLY'Z HSALYUHAL UHTL OPKLZ AOL MHJA AOHA PA PUCVSCLZ WSHFPUN JHYKZ?

Running it through Caesar cipher with offset of 7 it turns it into the following question:
WHAT CIPHER'S ALTERNATE NAME HIDES THE FACT THAT IT INVOLVES PLAYING CARDS?

Quick googling session reveals the answer.

Flag: pontifex

Crypto 2
Points: 75
Instructions: We got the password hashes from a system but don't know what to do with them. -- Crack the passwords and concatenate them alphabetically for the key. Submit in all uppercase, no spaces.
Category: Crypto/Stego
File: 2d2cda7a3f0b8f0b292c646f94bf9836073e94db

Solution:

$ file ./2d2cda7a3f0b8f0b292c646f94bf9836073e94db.txt
./2d2cda7a3f0b8f0b292c646f94bf9836073e94db.txt: gzip compressed data, from Unix, last modified: Sat May  5 17:07:27 2012

$ cat 2d2cda7a3f0b8f0b292c646f94bf9836073e94db.txt | tar -zxvf -
10896c1cb3d61b1c2d65690dc7c0d05e

$ file 10896c1cb3d61b1c2d65690dc7c0d05e
10896c1cb3d61b1c2d65690dc7c0d05e: ASCII text


$ cat 10896c1cb3d61b1c2d65690dc7c0d05e
Derp McDerp:1005:E94FB3E8775E0FE98B0EA5A7DF135B03:8EECDD4B8B43677BB162A15DBCB2C231:::
TehSuperUser:1006:AA1AB12D9BE8C0D126F8092A33DAAF05:76D202B312C8523D37FCDB4CD8288242:::
AnotherUser:1007:6F87CD328120CC55FF17365FAF1FFE89:B3EC3E03E2A202CBD54FD104B8504FEF:::
rockstar:1008:59FA69C37A475354FF7447F7A53FE0E0:2EAAE8097EDA4BE66FABB52A3EFDDB8E:::

Using invaluable http://www.objectif-securite.ch/en/products.php we can crack the lm hashes in seconds:

Hash: E94FB3E8775E0FE98B0EA5A7DF135B03
Password: DERPDERP
Hash: AA1AB12D9BE8C0D126F8092A33DAAF05
Password: SUPERPASS
Hash: 6F87CD328120CC55FF17365FAF1FFE89
Password: ABCD1234
Hash: 59FA69C37A475354FF7447F7A53FE0E0
Password: ZERGSTORM45

Flag: ABCD1234DERPDERPSUPERPASSZERGSTORM45


Crypto 3
Points: 175
Instructions: Crack the cipher, get the key ------------ The key to the cipher is the answer. Submit the key in all lower case, no spaces
Category: Crypto/Stego
File: 17ca6426ac08cef3641a5695667c921af89edb82

Solution:
$ file ./17ca6426ac08cef3641a5695667c921af89edb82./17ca6426ac08cef3641a5695667c921af89edb82: BinHex binary text, version 4.0

$ cat 17ca6426ac08cef3641a5695667c921af89edb82 | hexbin -l
This file is in "hqx" format.
name="809f9232ac55c50317c93753b6099a1d", type=    , author=    , 1 excess bytes ignored
data=2442, rsrc=0

$ hexbin -s 17ca6426ac08cef3641a5695667c921af89edb82 -u

$ cat 809f9232ac55c50317c93753b6099a1d.text | xxd -r -p
knviuatuwfrztrrcideixrgakbwalvisginwrpxppvvqqciojzlmphyqioeeipkmpoihwkepqfnctomjmlhwymglixgtzjwvhaestrfvaaihpmiccevqcnazbudjtmfbieczhlqatuedmjdwtixueedusiuwjuhfnguekvhzyudfypaaeyslbvqkcpjtqznhqstmhaswpnlvhqclkcatdwrqclwkprvtkvtzwgljefkatjxvlfspkbpneznfitqvtuxziyjvqeioyedfcxjmsvqvwswlgbtzwzeslwjxdvpzkrwlgzdvjffitwowjalttrdzglehmelfdwngpkqzbglrizdvxceudxjiidlzxchhczdbruhjdxqvvbiycajvandyxyeulwvmdmfccrwshmtkxyedccubpsjfzrvxuwuomjezpwhwglwkagkevxglwjeelkcqczxkprjvgmcksdeqciuatkeeljhvgpxuhvrfoeplhwvviqjvqexukuoxyjktaprxbuhypqklvjepqxclddrzvgrxjmlhmkiorlcxalwjaheytjhvjkhfnmvgioekenvxjmbpruogeiuaxlvraussqthheedipacaglqvuohvkvvhrrndtipbhasignnmpoesetiorgqqczseipkiuaqveidezydtxukkprdqqccaekebnluyjhvvmksspmcamrlelvmvtzwwmyompngvqvvfccuqslejxuhvgwuzmegjykdtpjogzrvwwztvrkhfpbvmckiukebwviauiivfdshbwlyeqihvumdmhrtbsijisuirzybfgkdtirnehlgvwlaraarxjqcngfmqciuatkekbuhlgigaswamwxjiikeisgkitmrhqvaqzmpbloiimgkifigrgfumofgvdtsimnqhuwblxyiorxqztalvshdrixgvkiandtwzilhwzbpxczcpwyeenpqcsjejmffsparpslsopwulxcmumqommmqleuspqqgzrbvpiefmpopismebyiplalwjjrdgjbwlgflpcshbwlhrzxvmndtygcovowjqhcmjqbqacahwlvrjnenihalfctkeuqcnpvrfempiaprvlgkikvclvjusqeemdmextbeivppagfnulmpmshpcbulriaxmeclusmpohjsltqeiewjuxvd

Plugging that into http://smurfoncrack.com/pygenere/pygenere.php and expanding the length of codewords to try to 20 yelds just slightly misspelled answer"INDECIPHERLBLECIPHER"

With the proper key it decodes to:
casessensoryinputwarpedwiththeirvelocityhismouthfilledwithanachingtasteofbluehiseyeswereeggsofunstablecrystalvibratingwithafrequencywhosenamewasrainandthesoundoftrainssuddenlysproutingahummingforestofhairfineglassspinesthespinessplitbisectedsplitagainexponentialgrowthunderthedomeofthetessierashpoolicetheroofofhismouthcleavedpainlesslyadmittingrootletsthatwhippedaroundhistonguehungryforthetasteofbluetofeedthecrystalforestsofhiseyesforeststhatpressedagainstthegreendomepressedandwerehinderedandspreadgrowingdownfillingtheuniverseoftadownintothewaitinghaplesssuburbsofthecitythatwasthemindoftessierashpoolsaandhewasrememberinganancientstoryakingplacingcoinsonachessboarddoublingtheamountateachsquareexponentialdarknessfellinfromeverysideasphereofsingingblackpressureontheextendedcrystalnervesoftheuniverseofdatahehadnearlybecomeandwhenhewasnothingcompressedattheheartofallthatdarktherecameapointwherethedarkcouldbenomoreandsomethingtorethekuangprogramspurtedfromtarnishedcloudcasesconsciousnessdividedlikebeadsofmercuryarcingaboveanendlessbeachthecolorofthedarksilvercloudshisvisionwassphericalasthoughasingleretinalinedtheinnersurfaceofaglobethatcontainedallthingsifallthingscouldbecounted

Flag: indecipherablecipher

Crypto 4
Points: 300
Instructions: Decrypt for the key ---------------------------- answer format is strupr(full unencrypted string)
Category: Crypto/Stego
File: 13930c973e7169c84c9c88211cbc5e54f32153cb

Solution:
A TIFF file presents the following ciphertext: RUCIQPQMMRNZEVONDT
running foremost against it reveals an jpg image with the following enigma key:
B,I,IV,III,14,18,20,AH,CK

One can decode using the enigma machinesimulator: http://startpad.googlecode.com/hg/labs/js/enigma/enigma-sim.html , then
after plugging the ciphertext into the enigma machine with the following settings:

Rotors: I-IV-III
Rotor Start: AAA
Rings: NRT
Plugboard: AH CK

the key is revealed.

Flag: THEKEYISALANTURING


Forensics 1
Points: 50
Instructions: Eye Heart FS! -- Submit they key as instructed
Category: Forensics
File: dc7f2a9830f5e8dad51c6f6dbc6edc8b3a7bc22f

hints.txt is inside the provided tar.gz file, after untaring/gunzipping it lists the following clue:

Answer these three questions and concatenate the answers for the key.
How large (in bytes) would a DOS3.3 "nibbled" image be?
What is the minimum journal size (in blocks) for a reiserfs disk?
What partition type (in hex) would a FAT16B partition have?

Quick google search for these items reveals that the mac specific DOS3.3 image was 232960 in length, ReiserFS' minimum journal size is 513 blocks. Finally, the hex code for Big FAT16 is 0x06

Flag: 2329605130x06

Forensics 2
Points: 99
Instructions: Ugh -- this is exactly why interns shouldn't do forensics. What a newb. -- Submit the key as found.
Category: Forensics
File: 94996036b13962bf8baaa324d998ed9476f620e3

$ file 94996036b13962bf8baaa324d998ed9476f620e3 
94996036b13962bf8baaa324d998ed9476f620e3: bzip2 compressed data, block size = 900k

$ cat 94996036b13962bf8baaa324d998ed9476f620e3 | tar xjvf -
dd_rtfm

$ file dd_rtfm 
dd_rtfm: data

When revieved through the "hexdump -C" or "strings" you can clearly see that the strings don't read quite right, it seems that the endianness is flipped.

to reverse that and get the key in a single liner we can do:

$ dd if=dd_rtfm conv=swab | strings | grep n00b

Flag: Silly n00b -- arguments are for l337!

Forensics 3
Points: 200
Instructions: You know that guy? The one who always hides his screen when you walk by? You're never quite sure what he's up to? Yeah -- you know him. Well we need to know what he's up to. We're moving his desk to the basement, and you remember what happened last time... We managed to get a memory dump and a file we think he was working with from his system, but we don't know what to do with it. -- Submit the key in ALL CAPS
Category: Forensics
File: 5748b64df3c691d1766e793e8dcf23b05383899d

Packet 1
Points: 25
Instructions: Who would cross the Bridge of Death must answer me these questions three, ere the other side he see. What should the Checksum always be for IPXWAN___ How many reserved bits in the TCP header, and what must it always be___ The code for a Point to Point Protocol Discard Request --------------------- Find the answer to these three questions, concat them for the first half of the answer, append with the color it creates (answers123Color)
Category: Packet
File: (none)
Flag: FF6011Orange

Packet 2
Points: 150
Instructions: What an odd way to send a secret message... ------------------------ answer format is md5(key including all spaces)
Category: Packet
File: 1c1d8c9c1f7cb39c6eb16efea9c0542447c2934d

Binhex decodes a pcap file, following the udp session reveals a sound file that transcribes into:
4d 61 63 68 30 4d 34 6e 20 73 41 89 35 20 3030 6f 48 20 59 33 61 68
which in ascii is: "Mach0M4n sAy5 00oH Y3ah"
The flag is md5 of that string (sans quotes).

Flag: c37947e01c8b338081e76a62ca5da88b

Potpourri 1
Points: 75
Instructions: LRN2 READ! -- Submit in all uppercase no spaces.
Category: Potpourri
File: b002d66a78882969f9d8eee555582509f01df944

Binhex decodes a zip file with an image of message written in Braile, which decodes into a question along the lines of  (the decode might be not exact):
"On November 2 1988 what worm was unleashed?"

Flag: MORRISWORM

Potpourri 2
Points: 125
Instructions: The PDF spec is so error tolerant, even the monkey from the infinite monkey theorem couldn't reproduce it! -- Submit the key as found
Category: Potpourri
File: cc7931560f8fc6d8df26f058afc6b8b03e31ae60

After decompressing a pdf with hamlet is revealed. One could search for the original pdf and diff it. The result of diff would indicate an object 368 being added. I've used my custom version of pdf-parser to dump the media files for me. Object 368 is an jpg image containing the key.

Flag: Z0MG you found 4n0th3r k3y!1!one1

Potpourri 3


Plugging the following into the sudoku solver


.7....69.35.7.........41..5....9.7..6..2..9.8.2.4..........4..7..91...2.5....7.8.

into http://www.sudokuwiki.org/sudoku.htm and clicking on "Solution Count" button gives the flag.

Flag: 471852693358769214296341875813596742647213958925478361162984537789135426534627189

Reversing 1
Points: 25
Instructions: Do you believe in magic? What tasty hex number was originally used to mark newly allocated (freed) memory prior to initialization? -- Submit the key as hex, all uppercase (so 0x[UPPERCASE])
Category: Reversing
File: (none)

Flag: 0xDEADBEEF

Reversing 3
Points: 175
Instructions: Blast from the not-so-distant past... in more ways than one. -- Submit the key as found
Category: Reversing
File: ad32c019ae35816ce196db2042d572e41875cd44

Another pdf challenge, the pdf is wrapped in a C64 disk image among other things. The pdf contains a javascript, which won't run unless it detects AdobeReader version 5, and then it pops up a question for the code. It turns out that there is a common multiplier to the algorithm used, once one extracts the code and runs it in a debugger or modifies the pdf. It's pretty easy to derive the multiplier by plugging 1, then 10. The result has to be rounded up with no decimal places. 

Flag: 996638400