튜기's blogggg

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

활동하기