Pwnstar

pwnable.kr crypto1 본문

Pwnable.kr/Rookiss

pwnable.kr crypto1

포너블처돌이 2020. 9. 15. 22:36

계속 암호화 어렵다고 미뤄뒀던 루키스의 마지막 문제 crypto1이다


이 문제를 풀기 전에 AES128-CBC 암호화 알고리즘에 대해서 공부를 조금 해 보았다.

  • 블록단위로 순차적으로 암호화
  • 한 개의 블록만 해독되면 나머지 블록들도 해독될 수 있음(브루트포스 등으로)
  • 평문의 각 블록은 이전 암호문과 xor 연산을 통해 암호화되고 첫번째 암호문은 initial vector가 암호문대신 사용된다.
  • 즉 이 initial vector를 알면 해독가능할듯 → 알 수가 없었다고 한다

소스코드

client.py

#!/usr/bin/python
from Crypto.Cipher import AES
import base64
import os, sys
import xmlrpclib
rpc = xmlrpclib.ServerProxy("http://localhost:9100/")

BLOCK_SIZE = 16
PADDING = '\x00'
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
EncodeAES = lambda c, s: c.encrypt(pad(s)).encode('hex')
DecodeAES = lambda c, e: c.decrypt(e.decode('hex'))

# server's secrets
key = 'erased. but there is something on the real source code'
iv = 'erased. but there is something on the real source code'
cookie = 'erased. but there is something on the real source code'

# guest / 8b465d23cb778d3636bf6c4c5e30d031675fd95cec7afea497d36146783fd3a1
def sanitize(arg):
	for c in arg:
		if c not in '1234567890abcdefghijklmnopqrstuvwxyz-_':
			return False
	return True

def AES128_CBC(msg):
	cipher = AES.new(key, AES.MODE_CBC, iv)
	return EncodeAES(cipher, msg)

def request_auth(id, pw):
	packet = '{0}-{1}-{2}'.format(id, pw, cookie)
	e_packet = AES128_CBC(packet)
	print 'sending encrypted data ({0})'.format(e_packet)
	sys.stdout.flush()
	return rpc.authenticate(e_packet)

if __name__ == '__main__':
	print '---------------------------------------------------'
	print '-       PWNABLE.KR secure RPC login system        -'
	print '---------------------------------------------------'
	print ''
	print 'Input your ID'
	sys.stdout.flush()
	id = raw_input()
	print 'Input your PW'
	sys.stdout.flush()
	pw = raw_input()

	if sanitize(id) == False or sanitize(pw) == False:
		print 'format error'
		sys.stdout.flush()
		os._exit(0)

	cred = request_auth(id, pw)

	if cred==0 :
		print 'you are not authenticated user'
		sys.stdout.flush()
		os._exit(0)
	if cred==1 :
		print 'hi guest, login as admin'
		sys.stdout.flush()
		os._exit(0)

	print 'hi admin, here is your flag'
	print open('flag').read()
	sys.stdout.flush()

아이디와 비밀번호를 입력받아 admin으로 로그인하면 flag를 볼 수 있는 간단한 형태처럼 보인다.

아이디와 비밀번호에는 '1234567890abcdefghijklmnopqrstuvwxyz-_' 문자들 이외에는 올 수 없고,

아이디와 패스워드 쿠키 값을 -로 연결해서 AES128-CBC로 암호화시킨다.

서버의 코드를 보자.

#!/usr/bin/python
import xmlrpclib, hashlib
from SimpleXMLRPCServer import SimpleXMLRPCServer
from Crypto.Cipher import AES
import os, sys

BLOCK_SIZE = 16
PADDING = '\x00'
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
EncodeAES = lambda c, s: c.encrypt(pad(s)).encode('hex')
DecodeAES = lambda c, e: c.decrypt(e.decode('hex'))

# server's secrets
key = 'erased. but there is something on the real source code'
iv = 'erased. but there is something on the real source code'
cookie = 'erased. but there is something on the real source code'

def AES128_CBC(msg):
	cipher = AES.new(key, AES.MODE_CBC, iv)
	return DecodeAES(cipher, msg).rstrip(PADDING)

def authenticate(e_packet):
	packet = AES128_CBC(e_packet)

	id = packet.split('-')[0]
	pw = packet.split('-')[1]

	if packet.split('-')[2] != cookie:
		return 0	# request is not originated from expected server
	
	if hashlib.sha256(id+cookie).hexdigest() == pw and id == 'guest':
		return 1
        if hashlib.sha256(id+cookie).hexdigest() == pw and id == 'admin':
                return 2
	return 0

server = SimpleXMLRPCServer(("localhost", 9100))
print "Listening on port 9100..."
server.register_function(authenticate, "authenticate")
server.serve_forever()

처음 암호화 알고리즘을 설명할 때에 블록단위로 암호화한다고 설명했는데, 코드를 보면 블록사이즈가 16바이트로 명시되어 있다.

아이디와 패스워드를 '-'로 구분하는데, id + cookie를 sha256으로 암호화 한 값과 pw가 같고, 아이디가 admin이면 admin으로 로그인할 수 있을 것 같다.

그렇다면 알아야할 것은 cookie의 값인데, 이 값을 어떻게 알 수 있을까...

생각하다가 아이디와 패스워드를 -로 구분한다고 했고 블록사이즈는 16바이트라면 이 16바이트에 cookie 값의 첫번째 글자를 암호화시킬 수 있지 않을까 생각해보았다.

아이디 : "a"*12

비밀번호 : "a"

이렇게 해서 보내면 첫번째 블록이

'aaaaaaaaaaaa-a-(cookie의 첫번째 문자)'로 구성될 것이다.

처음 이렇게 보내어 'aaaaaaaaaaaa-a-(cookie의 첫번째 문자)'를 암호화한 값을 구해놓고

그 다음 한문자씩 대입하여 보내줘서 비교한 값으로 cookie의 첫번째 문자를 알 수 있을 것이다.

이후 'aaaaaaaaaaaa-a-'에서 한문자씩 빼면서 비교하여 이론상 14번째까지는 알아낼 수 있을 것 같다.

근데 코드를 짜려면 'a'를 넣으면 좀 불편할 것 같다. '-'로 통일하자;;

코드를 짜서 한번 돌려봤다 어느정도 나오고 나서 안나오길래 마지막으로 보니까

뭔가 똥싸다 끊긴거같긴한데;;

그러면 이제 admin+cookie 값을 sha256 암호화 한 값을 pw로 주면 되고... cookie에는 이 값 그대로 넣어서 줘 보자

이렇게 시도해봤는데 안된다;;

아무래도 역시 쿠키값이 잘못된듯 아니 부족한듯 하다.

"-" 개수를 늘려서 한번 도전해보자...

하다보니 암호화 데이터가 128 ~ 160바이트정도 나오는데 정확히 쿠키값의 길이가 얼마나 될 지 몰라서 한 64바이트정도라고 가정하고 코드를 짰다.

from pwn import*

cookie = ""         ##"you_will_never~"
num = 0
while True:
    p = remote("localhost", 9006)

    cookie_list = '1234567890abcdefghijklmnopqrstuvwxyz-_'

    p.recvuntil("Input your ID\n")
    ID = "-"*(61 - num) + "\n"        #13
    p.send(ID)

    p.recvuntil("Input your PW\n")
    PW = "\n"
    p.send(PW)

    p.recvuntil("sending encrypted data (")

    endata = p.recv(128)

    for c in cookie_list:
        p = remote("localhost", 9006)
    
        p.recvuntil("Input your ID\n")
        ID2 = "-"*(63 - num) + cookie + c + "\n"
        p.send(ID2)
        
        p.recvuntil("Input your PW\n")
        PW2 = "\n"
        p.send(PW2)
        
        p.recvuntil("sending encrypted data (")
        
        comdata = p.recv(128)
        p.close()
        if endata == comdata:
            cookie = cookie + c
            log.info(cookie)
            break
    
    num = num + 1

이렇게 하다 보니

이 이후로는 값이 안찍히길래 이 값이 쿠키값이라고 생각하고 sha256으로 해쉬화를 한 다음에

id에 admin-hash값-cookie 이렇게 입력하고 비밀번호에 아무값도 넣지 않았더니

플래그를 딸 수 있었다ㅎㅎ


그런데 생각해보니 굳이 쿠키값까지 입력 안해도 패스워드 입력값을 제대로 주면 풀릴듯


'Pwnable.kr > Rookiss' 카테고리의 다른 글

Pwnable.kr loveletter  (0) 2020.07.30
Pwnable.kr note  (0) 2020.07.30
Pwnable.kr alloca  (0) 2020.07.29
Pwnable.kr rsa_calculator  (0) 2020.07.29
Pwnable.kr echo2  (0) 2020.07.28
Comments