This machine, whether to be called a virtual machine, emulator, or bytecode interpreter, had some (std :: vector containing code that was slightly obfuscated along with another (std :: vector which, when indexed using an opcode, contains a function pointer corresponding to that opcode. Data is simply deobfuscated by performing offset – program [offset] . Eventually, the opcode would become
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]
, 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: Here’s doing addition but with extra recursion:
( Multiplication by multiplying by 2 and then later dividing by 2:
This machine, whether to be called a virtual machine, emulator, or bytecode interpreter, had some (std :: vector 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] , 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: Here’s doing addition but with extra recursion:
( Multiplication by multiplying by 2 and then later dividing by 2:
Arithmetic that doesn’t do anything:
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 
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
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: // github.com/ChrisMiuchiz/PLASM-Disassembler . 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
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
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
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:
However, that function was called by reference, and returned to one of the emulator’s SYSCALL implementations:
UNKNOWN?
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.
Accomplishment
Now we know everything we need to know in order to reverse this process so that the server can generate encrypted data.
Excerpt from server.py:
Excerpt from encryption.py:
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
C: ProgramData Picroma Plasma config
and (C: ProgramData Picroma Plasma settings
. 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
: 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
C: ProgramData Picroma Plasma settings
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
. 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
. 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.
Accomplishment
Now we know everything we need to know in order to reverse this process so that the server can generate encrypted data.
Excerpt from server.py:
Excerpt from server.py:
Excerpt from encryption.py:
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.
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
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 C: ProgramData Picroma Plasma config
and (C: ProgramData Picroma Plasma settings
. config

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
C: ProgramData Picroma Plasma settings
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
. 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
. 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.
GIPHY App Key not set. Please check settings