initial commit. just the decrypt tool so far

This commit is contained in:
Gered 2021-03-19 13:21:32 -04:00
commit 8629a3988a
4 changed files with 275 additions and 0 deletions

25
.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
*.o
*.exe
*.so
*.dylib
*.dSYM
*.a
/build
# CMake
/cmake-build-debug
/cmake-build-release
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
# IDE stuff
/.idea

13
CMakeLists.txt Normal file
View file

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.16)
project(psogc_quest_tools C)
set(CMAKE_C_STANDARD 99)
include_directories(/usr/local/include)
find_library(SYLVERANT_LIBRARY sylverant REQUIRED)
# decrypt_packets
add_executable(decrypt_packets decrypt_packets/main.c)
target_link_libraries(decrypt_packets ${SYLVERANT_LIBRARY})

100
decrypt_packets/README.md Normal file
View file

@ -0,0 +1,100 @@
# PSO Ep 1 & 2 (Gamecube) Client/Server Packets Decrypter Tool
This is a tool that will take raw binary PSO client and server packet data dumps generated from a packet capture tool
(such as Wireshark) and display the decrypted packet data.
I put this tool together for myself to help further my understanding of PSO's network communication. More specifically,
to help me troubleshoot why my attempts at setting up Sylverant's open source [login_server](https://github.com/Sylverant/login_server)
to serve up quests for download was resulting in unusable quest files on Gamecube memory cards. Understanding the
quest download communication better by analyzing the packets being sent from a working implementation and comparing it
to what my local login_server instance was sending proved invaluable to me.
## Network Protocol
After the initial `0x17` packet sent from the server to the client (which contains the client and server encryption
keys), all subsequent communication between the server and client is encrypted. When you have the full set of packets,
beginning with the `0x17` packet, it is pretty trivial to decrypt the entire set of data.
```text
'Welcome' packet. id=17, flags=0, size=276
0000 | 17 00 14 01 44 72 65 61 6D 43 61 73 74 20 50 6F | ....DreamCast Po
0010 | 72 74 20 4D 61 70 2E 20 43 6F 70 79 72 69 67 68 | rt Map. Copyrigh
0020 | 74 20 53 45 47 41 20 45 6E 74 65 72 70 72 69 73 | t SEGA Enterpris
0030 | 65 73 2E 20 31 39 39 39 00 00 00 00 00 00 00 00 | es. 1999........
0040 | 00 00 00 00 6B 81 4B 4F 01 A2 65 78 54 68 69 73 | ....k.KO..exThis
0050 | 20 73 65 72 76 65 72 20 69 73 20 69 6E 20 6E 6F | server is in no
0060 | 20 77 61 79 20 61 66 66 69 6C 69 61 74 65 64 2C | way affiliated,
0070 | 20 73 70 6F 6E 73 6F 72 65 64 2C 20 6F 72 20 73 | sponsored, or s
0080 | 75 70 70 6F 72 74 65 64 20 62 79 20 53 45 47 41 | upported by SEGA
0090 | 20 45 6E 74 65 72 70 72 69 73 65 73 20 6F 72 20 | Enterprises or
00A0 | 53 4F 4E 49 43 54 45 41 4D 2E 20 54 68 65 20 70 | SONICTEAM. The p
00B0 | 72 65 63 65 64 69 6E 67 20 6D 65 73 73 61 67 65 | receding message
00C0 | 20 65 78 69 73 74 73 20 6F 6E 6C 79 20 69 6E 20 | exists only in
00D0 | 6F 72 64 65 72 20 74 6F 20 72 65 6D 61 69 6E 20 | order to remain
00E0 | 63 6F 6D 70 61 74 69 62 6C 65 20 77 69 74 68 20 | compatible with
00F0 | 70 72 6F 67 72 61 6D 73 20 74 68 61 74 20 65 78 | programs that ex
0100 | 70 65 63 74 20 69 74 2E 00 00 00 00 00 00 00 00 | pect it.........
0110 | 00 00 00 00 | ....
server_key = 0x4f4b816b
client_key = 0x7865a201
```
Note, sometimes the `0x17` packet will contain significantly less text than what is shown above. The above output is
from a Fuzziqer [newserv](https://github.com/fuzziqersoftware/newserv) I was testing with.
Also of note is that Sylverant's login_server currently seems to always use identical server and client keys (I believe
this is a bug in libsylverant's usage of its random number generator library). This does not cause problems, but it is
weird to see when you first notice it.
Some relevant reading regarding PSO's network protocol:
* [Network Protocol](http://web.archive.org/web/20171201191557/http://sharnoth.com/psodevwiki/net/protocol)
* [Network Protocol Messages](http://web.archive.org/web/20171201191532/http://sharnoth.com/psodevwiki/net/messages)
* ["Detailed" Message Flow](http://web.archive.org/web/20171201191527/http://sharnoth.com/psodevwiki/net/message_flow)
Currently, [libsylverant](https://github.com/Sylverant/libsylverant) has the cleanest and easiest to use PSO encryption
API, and that is what is used by this tool.
**Note that the PSO encryption method (and thus, the `CRYPT_` API provided by libsylverant) is stateful**. That is, you
cannot just use it to arbitrarily decrypt any single random packet and expect it to result in readable data. To
correctly decrypt any individual packet from either client or server, you need to work through the full sequence of
packets (for either client or server) beginning with the very first client or server packet (**after** the `0x17`
packet) up to the packet(s) you really wanted, decrypting all of it along the way.
## Usage
### Capturing Packets from PSO
This is probably easiest if you already have Dolphin set up to run PSO with a working network configuration. In such
a configuration, you can capture from your local computer right away.
I do not have this set up and I cannot be bothered to figure out the janky "Tap" set up that Dolphin requires. Mostly
because I am lazy. And because I have a router running [OpenWrt](https://openwrt.org/) which allows me to easily set up
packet mirroring with a special iptables kernel module loaded so that I can capture packets directly from my Gamecube.
I'm not going to go into details here on setting up either method. If you're knowledgeable enough to be considering
doing packet capture analysis of any sort in the first place, then you should be able to set up either method yourself.
### Dumping PSO Server/Client Communication Data Dumps with Wireshark
It is easy to generate packet data dumps containing _just_ the PSO packet data we are interested in with Wireshark.
After taking a capture of a PSO server/client session, find the TCP packet sent from the server to the client that
contains the `0x17` packet. This should be easy enough to find as it will be one of the first TCP packets sent from the
server to the client and contains the clear-text string `DreamCast Port Map. Copyright SEGA Enterprises. 1999` (for
non-BB clients anyway, this tool is aimed at GC anyway so that is all I will be covering ...).
Once you've found this packet, right-click it from the top packet list and select "Follow" then "TCP Stream". This will
bring up a window that shows the raw data, colour-coded to show data originating from the client and server. Use the
drop-downs and buttons at the bottom of this window to save "Raw"-format data for the client and server in
**individual** files.
### Decrypting
Assuming you saved the data to two files, `server.bin` (containing server-to-client packets) and `client.bin`
(containing client-to-server packets), you can run the tool like so:
```text
decrypt_packets /path/to/server.bin /path/to/client.bin
```

137
decrypt_packets/main.c Normal file
View file

@ -0,0 +1,137 @@
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <sylverant/encryption.h>
void* read_file(const char *filename, uint32_t *out_file_size) {
if (!out_file_size)
return NULL;
FILE *fp = fopen(filename, "rb");
if (!fp)
return NULL;
fseek(fp, 0, SEEK_END);
*out_file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
uint8_t *result = malloc(*out_file_size);
uint32_t read, next;
uint8_t buffer[1024];
next = 0;
do {
read = fread(buffer, 1, 1024, fp);
if (read) {
memcpy(&result[next], buffer, read);
next += read;
}
} while (read);
return result;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("Usage: pso_decrypt server-packet-data.bin client-packet-data.bin\n");
return 1;
}
const char *server_packet_file = argv[1];
const char *client_packet_file = argv[2];
uint32_t server_data_size = 0;
uint32_t client_data_size = 0;
uint8_t *server_data = read_file(server_packet_file, &server_data_size);
if (!server_data) {
printf("Error reading server packet data file: %s\n", server_packet_file);
return 1;
}
uint8_t *client_data = read_file(client_packet_file, &client_data_size);
if (!client_data) {
printf("Error reading client packet data file: %s\n", client_packet_file);
free(server_data);
return 1;
}
uint32_t pos;
uint8_t pkt_id, pkt_flags;
uint16_t pkt_size;
uint32_t server_key, client_key;
// read client & server crypt keys from the "Welcome" packet the server sends right away. always unencrypted.
pos = 0;
pkt_id = server_data[pos];
pkt_flags = server_data[pos+1];
pkt_size = *((uint16_t*)&server_data[pos+2]);
printf("'Welcome' packet. id=%x, flags=%x, size=%d\n", pkt_id, pkt_flags, pkt_size);
CRYPT_PrintData(&server_data[pos], pkt_size);
printf("\n");
// NOTE: sylverant login_server currently always has these identical to each other. fuzziqer does not exhibit this.
// looks like a bug within libsylverant, or more specifically with it's custom random number generator lib?
// either way, it does not pose a problem ...
server_key = *((uint32_t*)&server_data[pos+68]);
client_key = *((uint32_t*)&server_data[pos+72]);
printf("server_key = 0x%x\nclient_key = 0x%x\n\n", server_key, client_key);
pos += pkt_size;
// set up crypt functionality using those keys, so we can read the rest of the server and client packet data
// (all of the rest of it will be encrypted)
CRYPT_SETUP server_cs, client_cs;
CRYPT_CreateKeys(&server_cs, &server_key, CRYPT_GAMECUBE);
CRYPT_CreateKeys(&client_cs, &client_key, CRYPT_GAMECUBE);
// display remainder of server packets first
printf("**** SERVER -> CLIENT PACKETS ****\n\n");
while (pos < server_data_size) {
CRYPT_CryptData(&server_cs, &server_data[pos], 4, 0);
pkt_id = server_data[pos];
pkt_flags = server_data[pos+1];
pkt_size = *((uint16_t*)&server_data[pos+2]);
CRYPT_CryptData(&server_cs, &server_data[pos+4], pkt_size-4, 0);
printf("id=%x, flags=%x, size=%d\n", pkt_id, pkt_flags, pkt_size);
CRYPT_PrintData(&server_data[pos], pkt_size);
printf("\n");
pos += pkt_size;
}
// now display the client packets
printf("**** CLIENT -> SERVER PACKETS ****\n\n");
pos = 0;
while (pos < client_data_size) {
CRYPT_CryptData(&client_cs, &client_data[pos], 4, 0);
pkt_id = client_data[pos];
pkt_flags = client_data[pos+1];
pkt_size = *((uint16_t*)&client_data[pos+2]);
CRYPT_CryptData(&client_cs, &client_data[pos+4], pkt_size-4, 0);
printf("id=%x, flags=%x, size=%d\n", pkt_id, pkt_flags, pkt_size);
CRYPT_PrintData(&client_data[pos], pkt_size);
printf("\n");
pos += pkt_size;
}
return 0;
}