in ,

ChrisMiuchiz / Plasma-Writeup, Hacker News

ChrisMiuchiz / Plasma-Writeup, Hacker News



on Picroma’s YouTube channel .

Picroma / Wollay is also the author the game Cube World , and as a result, the game’s GUI is constructed using Plasma graphics. I became interested in these Plasma graphics files in , as I was beginning to get into reverse engineering and programming, and I had just finished building a converter for another, much simpler, undocumented image file format. Until this point, no one else had managed to make any useful modifications to Cube World’s image files.

The years went on, and with the help of my friend Andoryuuta , I created PLXML , a tool that could convert the Plasma graphics files (known as .PLX, .PLD, or .PLG) to and from an XML file format. However, since these are vector graphics, it’s more difficult to create an editor for them, and there was no GUI based editor except Plasma itself.

Plasma was Picroma’s first (and probably, in their eyes, their primary) product, but only one release was ever created, and it was in . It used an authentication server which eventually went down, so when it stopped working, most people just got rid of the software and moved on. It was not April (th, 4774 that the installer from 3133 resurfaced and we could get to work on making this old art tool work again. Note: The installer is PicromaPlasmaSetup.exe (SHA (F) (A).

So, where do we start? When Plasma is launched, it wants to contact the authentication server at using HTTP, and if you try to login or activate the product, it does not work:

Assembly Okay. The most obvious next step is to find the code responsible for “activating” Plasma and just patch some code to make it always activate, no matter what the authentication server does. There are a couple places in the binary that reference a few data members in order to determine whether the program has been activated. However, any attempts to patch these jumps or write affirmative values ​​to these data members will result in the program crashing. Usually, this is in the construction of a Sheet Assembly object (Note that RTTI was in this binary, so some class names can be recovered), the parent class of (DesignSheet , but patching around that just creates more exceptions. It is not possible to patch the program in this manner in order to get it to work. Let’s take a look at the code which handles bad (or a lack of) responses from the server: This entire function is dedicated to error handling. I set up my own server with Flask to play with how it responds to various inputs, but I was not able to get anything interesting to happen other than change which error message it displayed. It can attempt to access any of the following paths: / LS / Activation / Logout / / LS / Activation / Activate / / LS / Activation / Login / / LS / Activation / Validate / / LS / Activation / Deactivate /

It also seems to be able to contact / Download / Plasma / release.xml for updates, but this is not yet valuable to me. I noticed that it was also sending some parameter called (id) so so: / LS / Activation / Activate /? Id=A (D) (C) B [var4] (B) (D) (D) (BE) (C) (D) (E6A) (D) (DE) (BA9AC) D

(CD) (F) CDF (D) (CB) (BB) FA

(DF1E8AFCB) (B) EE (C) (AB) (C2) At this point, I had no idea what to do with it, but I assumed it represented the machine and serial in some way. Let’s look for xrefs to this error handling function to see what triggers it.

raised while handling the response. I started setting breakpoints around to see which function raised the exception, and I found it happening in quite a peculiar function .. .

I recognize this code from reverse engineering Cube World to create PLXML . This is code to parse a Plasma graphics file. It seems that, for some reason, Plasma is expecting a Plasma graphics file to be sent back from the authentication server. Since it is failing to parse my response, it is triggering the error handling code.

The function (which calls both the parser and error handler) receives data using an std :: vector Assembly which is passed to it:

However, examining the data contained within the vector reveals nonsense, even when my server gives it a valid PLX file. The vectors coming into this function are the correct size to be the data being sent by my server, but the data is not what I sent. Upon more experimentation with small buffers, it seemed to be (de) obfuscated by performing addition / subtraction on the bytes and swapping them around somehow, so it didn’t seem like a particularly strong obfuscation algorithm.

For now, I decided to just overwrite the vector in memory with untouched data at this point in execution. This effectively bypasses whatever obfuscation algorithm is happening. After implanting this valid PLX data, I stopped getting error messages when attempting to authenticate, but it caused exceptions instead. This ended up being due to elements being missing from my constructed PLX file and causing null pointer dereferences. A lot of these issues are caused by looking for an object by name which does not exist in the PLX:

Could not connect I worked through these exceptions until I constructed a PLX file using PLXML with enough of the bare minimum requirements to load up one of Cube World’s PLX files.

Around this time, I started looking at what the domain used to point to. I didn’t find much of interest on, but … The domain was now available after all these years, and I bought it.

No Need For a Patch

With ownership of the domain, in theory I could make Plasma work without even touching the binary or modifying the hosts file. Just, vanilla copies of Plasma would suddenly start working again one day. The thought of this was so cool to me that I halted my pursuit of creating a better sheet PLX file and started a deeper investigation into how the response was getting (de) obfuscated by the client. Understanding this would hopefully allow me to have my server send well-formed obfuscated responses that an unmodified client could understand.

I had previously not been able to understand anything about how the deobfuscation algorithm was actually implemented. I found the last bit of code that seemed to touch it before it was transformed: However, setting breakpoints on bytes inside this buffer to see what algorithm was touching it did not lead me to anywhere useful. I tried to work my way up the stack to find the loop that was altering the buffer, but I was always led to a function that did not seem to be responsible for deobfuscating a buffer, and it also did make very much sense to me since it was constantly calling functions by reference. What was happening here seemed over my head. I’d seen this same type of code pattern in Cube World before, and was unable to figure out what was going on.

Apparently Andoryuuta was less oblivious, because he noted he was not able to figure out how the virtual machine in Cube World worked . Then I realized that’s what I was looking at.

An Embedded Emulator

When framing it in the perspective of some kind of bytecode interpreter, emulator, or virtual machine, and given that I have created an emulator for obscure hardware before, this code suddenly made a lot more sense. So, with that bit of knowledge, I started working on the decompilation of this strange loop function:

This machine, whether to be called a virtual machine, emulator, or bytecode interpreter, had some (std :: vector Assembly 0x0C , and the emulation would stop, so this is likely a HALT or RET opcode. Each piece of data in this machine is 4 bytes long. Each instruction contains 1 opcode and 1 argument. When the function corresponding to an opcode is called, it can return a value that is added to the emulator’s program counter. This is used for branching and in case an instruction needs to read more than just its argument.

I dumped this strange bytecode from memory, and knowing that data=offset – program [offset] Assembly, I was able to deobfuscate the entire thing, but that isn’t much use without some kind of disassembler.

With that, I decided I had to figure out what each opcode did. It is worth noting at this point that I tried to see if this bytecode belongs to any existing CPU or language, but my search came up short. The constructor of this emulator conveniently assigned all the functions for me to analyze, but the list went on and on …

I painstakingly assigned meaning to every opcode function that I could, and I identified that this was a completely stack-based machine which did all its operations using the stack. It’s worth noting that every element on the stack is itself a vector. This allows any element to hold arbitrary data, such as pointers, strings, or simple integers. Here’s an example of addition:

It takes two elements off the stack, adds them together, and then pushes the result back to the stack.

Unfortunately, you can see a hint of something that became far too common while analyzing these opcodes. It makes one element negative before subtracting it. This is part of a larger pattern of fairly weak attempts to confuse a reverse engineer that made it frustrating to figure out what all the opcodes did, and there were many duplicate opcodes that were just implemented in different ways. Here’s addition implemented by multiplying the result with some number and its reciprocal:


Arithmetic that doesn’t do anything:

Could not connect

Warming up the PUSH machine by pushing and popping to and from the stack a few times first:

The list goes on. There was one instruction that stopped me from blindly trying to disassemble every pair of dwords. It was the only one which was variable in length, so it caused the Push PopAssembly alignment to change. It’s an instruction to push a string literal to the stack, and it’s formatted . With this knowledge, I decided to take a different approach. I suspected that only a few of the implemented opcodes were actually used, so I parsed through the bytecode, accounting for alignment changes due to the PUSHSTR opcode, and generated a set of (opcodes that were actually used: [0x05, 0x0C, 0x12, 0x13, 0x14, 0x17, 0x1B, 0x24, 0x2C, 0x3A, 0x3B, 0x43, 0x44, 0x4A, 0x56, 0x5C, 0x63, 0x69, 0x71, 0x73, 0x74, 0x75, 0x86, 0x89] This is a much more manageable amount of work.

See Appendix A – Opcodes for more detail on individual opcodes.

Reverse engineering these left me with a table of all the opcodes I’d need to know:

This is just what I needed to move on to disassembling the bytecode.

(Disassembling) a New Instruction Set

I had been working on a disassembler while reverse engineering the opcodes, but now that I have a complete description of the opcodes this program uses, I can disassemble the bytecode and begin to make a serious attempt at understanding its contents. The source for my disassembler is available at https: // . To find which function was obfuscating the PLX, I set a breakpoint on the data, and once it was written to, I set another breakpoint inside the emulator so that I could identify the program counter at the next virtual instruction.

Note: The comments documenting these syscalls were added later. My strategy for figuring out what each one did mostly came down to simply stepping through to see what functions each one called. Syscalls are not OS syscalls; they are syscalls unique to the implementation of this emulator and are used to interact with x 142 code. I had arrived in the middle of the obfuscation process. However, I wanted to make sure I started from the beginning. This function starts at address , so I looked for instances of CALL $ . There were a few usages of it, but I had an idea of ​​what part of code I was looking for, so eventually I was led to subroutine (0ED1) .

I started by reverse engineering all the functions that this function called. My strategy for doing this was to look at every chunk of code that resulted in a call, syscall, or variable write, and convert it into a line of higher level pseudocode. For example, this is what the beginning of the Assembly subroutine looked like after I had taken my notes on it:

The decompilation, by hand, of my first function looked like this: (sub _) (arg0, arg1) {   var2=arg0.length;   var3=arg1.length;   arg0.resize (var2 var3);   var4=0;   while (var4

It appends the data held by arg1 to the data held by arg0. I replaced all calls to this with CALL EXTEND_ARRAY . I did similar renaming for future functions as well. I moved onto the next required function at (0) (F)

A nice, short function which easily decompiled to the following:

(sub_0) (F (arg0, arg1, arg2) {   return (arg2 [arg0 % arg2.length] arg0)% arg1; }

This function’s purpose was confusing at first, but later, in context, I learned that it was used to generate an index to swap bytes with in order to scramble the PLX. Rather, in this case, it’s supposed to be (unscrambling) the PLX, but since it’s already unscrambled, it ruins the PLX. It takes an index, the length of the buffer to be used for, and an array of values ​​for use as a key.

I used the same strategies to decompile : (sub _) (arg0, arg1) {   var2=arg0.length;   var3=var2 – 1;   while (var3>=0) {     var4=sub_0 F (var3, var2, arg1); // Get swap index     arg0 [var3]=arg0 [var3] – var3;     var5=arg0 [var3];     var6=arg0 [var4];     arg0 [var3]=var6;     arg0 [arg4]=var5;     var3–;   }   return 0; }

This, combined with the previous function finally begins to expose the (de) obfuscation algorithm, which is actually a form of weak symmetrical encryption. It takes a buffer and a key, goes through all the indices in reverse (since this is for decryption), performs arithmetic on a byte and swaps it with another byte using said key.

I could move on to the top-level function now, (0ED1) .

A key is constructed at runtime to decrypt your machine’s “id” which is already on the stack, and then the id is used to decrypt the PLX.

Finally, we have arrived at the answer for how the decryption algorithm works, and it’s trivial to run it in reverse to derive the encryption algorithm that our authentication server will need. However, this takes an encrypted version of our id, and it looks nothing like the hexdump that is being provided as a parameter. Even when decrypted, it looks something like this: This is all ASCII: (C) FEC (EFBC) (A) (B) FFA 5D1A (EEA1EE) (D) (CEFEF) (DE) (C9E) (D) (E) (E) D (D) (E) D (D0CF) (D) (CCF4C) DFCA

For my purposes, I don’t need to know how the id itself is generated (though, strings in this bytecode reveal That it uses WQL to get serial numbers and such from hardware components), but I do need to know how to go from the parameter being sent to the server to this plaintext form so I can encrypt the PLX with it. I found where the parameter version of the id was used:

Could not connect

However, that function was called by reference, and returned to one of the emulator’s SYSCALL implementations:

Assembly UNKNOWN? Assembly chunk actually fills var1 with your serial. As a result, this concatenates your serial with your machine id. Once this concatenated string is created, it encrypts it using the new key, dumps it as hex, and sends it to x 124 code to be used as a parameter for the server.


Now we know everything we need to know in order to reverse this process so that the server can generate encrypted data.

Excerpt from

Excerpt from

This works. Using this code to generate responses from our server allows Plasma to successfully decrypt the traffic. The product activates. We haven’t made any changes to the program nor our hosts file. It’s a crackless crack.

Finishing up

We have a working authentication server, but the PLX file we are sending still doesn’t work quite right. It doesn’t look like the sheet displayed in those Picroma videos, and I’ve had trouble actually adding shapes to the PLX. The worst problem is that sometimes it still crashes Plasma.

I tracked the crashing issue down to attempting to deallocate uninitialized memory. With some experimentation, I realized that in my PLX file, I had 2 plasma :: Nodes associated with 1 (plasma :: Widget

. This was probably causing an attempt to destruct the widget twice. Removing the widget from one of the nodes fixed the issue. Associating a new widget with one of the nodes causes an infinite loop, so I couldn’t do that.

While fixing crashing issues, I noticed that if a PLX file that crashes Plasma was sent, Plasma would likely crash next run without even connecting to the server. It turned out that Plasma saves data to Assembly C: ProgramData Picroma Plasma config Assembly and (C: ProgramData Picroma Plasma settings Assembly. config

contains your serial and a value representing the state of your activation. settings contains the last encrypted PLX which Plasma received. Additional, less interesting data is stored in the Assembly: C: Users AppData Local Picroma Plasma directory.

I'm not sure how sheets are supposed to work in Plasma, but they seemed to have been rectangles around (x) pixels, with a drop shadow behind them, upon which artwork would exist while being edited . For now, I made them slightly larger rectangles to account for modern monitors. I'm not sure if they perfectly replicate the original functionality, but they seem perfectly usable for now. I will revisit them if I need to. The most desirable solution would be to get the original sheet PLX. Although unlikely, since Plasma saves the encrypted file to Assembly C: ProgramData Picroma Plasma settings Assembly upon an activation attempt, It could be possible to recover it from an old installation and attempt to decrypt it. A zero byte file will be saved if authentication fails, but the encrypted data may still exist on the hard disk since the data was not strictly overwritten, so recovery may still be possible. In order to ensure that any remaining copies from 02201 do not accidentally destroy the data by authenticating with my server, I am limiting valid serials to and PlasmaXGraphics Assembly. The latter is used by Picroma to save and obfuscate their PLX files, so it is convenient to be able to use that serial. It is possible to use non-numeric serials by modifying : ProgramData Picroma Plasma config Assembly. This section will be revised if an encrypted copy of the file is recovered. I'm no artist, so to show it off, here's recoloring Cube World's logo.

(Read More) (Full coverage and live updates on the Coronavirus (Covid -) )

What do you think?

Leave a Reply

Your email address will not be published.

GIPHY App Key not set. Please check settings

SSH Tips & Tricks, Hacker News

SSH Tips & Tricks, Hacker News

Hubble Space Telescope captures sharpest images of the disintegration of the comet Atlas – Firstpost,

Hubble Space Telescope captures sharpest images of the disintegration of the comet Atlas – Firstpost,