LoginSignup
0
0

More than 5 years have passed since last update.

HarekazeCTF

Last updated at Posted at 2018-02-11

divN

$ cat foo.c
long long div(long long x) {
return x / N;
}
$ gcc -DN=$N -c -O2 foo.c
$ objdump -d foo.o

foo.o: file format elf64-x86-64

Disassembly of section .text:

0000000000000000
:
0: 48 89 f8 mov %rdi,%rax
3: 48 ba 01 0d 1a 82 9a movabs $0x49ea309a821a0d01,%rdx
a: 30 ea 49
d: 48 c1 ff 3f sar $0x3f,%rdi
11: 48 f7 ea imul %rdx
14: 48 c1 fa 30 sar $0x30,%rdx
18: 48 89 d0 mov %rdx,%rax
1b: 48 29 f8 sub %rdi,%rax
1e: c3 retq

$ echo “HarekazeCTF{$N}” > /dev/null
(Rev, 100 points)

foo.c を N=$N と定義したうえで、最適化してコンパイルした(-O2)もののアセンブリを読み解いて、$Nの値を探る問題。

アセンブラの記法は、%があることから判断してAT&T。つまり、movだったら左から右に移動している。

mov %rdi,%rax で、引数としてわたってきた %rdi が %rax にコピーされる。
movabs $0x49ea309a821a0d01,%rdx で、%rdx(データレジスタ)に ‭5326122949484875009‬ が入る。
sar $0x3f,%rdi で、%rdiには最上位1bitが最下位1bitとなり、それ以外はもともとの最上位1bitと同じになる。
imul %rdx で、%raxの値(つまり、引数で渡されたx)と%rdxの値を乗算したものの、上位を%rdxに、下位を%raxに格納する。
sar $0x30,%rdx で、%rdxの上位16bitが下位16bitとなり、上位48bitは最上位のbitと同じになる。
mov %rdx,%rax で、%rdxを%raxにコピーする。
sub %rdi,%rax で、%raxから%rdiを引いた値を%raxに格納する。

‭Nを求めるには、
‭(2**112) // ‭5326122949484875009‬ +1
をする。
(+1がなんで必要か正直分かってない。。)

fight

import random
import base64
import key

def xor(msg, key):
    return bytes([ch1^ch2 for ch1, ch2 in zip(msg, key)])

def gcd(x, y):
  while y != 0:
    r = x % y
    x = y
    y = r
  return x

def gen_seed(n):
  seed = 0
  for k in range(1,n):
    if gcd(k,n)==1:
      seed += 1
  return seed

s = 1
for p in b"Enjoy_HarekazeCTF!!":
  s *= p
seed = gen_seed(s)
random.seed(str(seed).rstrip("0"))

flag = key.FLAG
key = bytes([random.randint(0,255) for _ in flag])

enc = xor(flag, key)
print(base64.b64encode(enc).decode('utf-8')) #7XDZk9F4ZI5WpcFOfej3Dbau3yc1kxUgqmRCPMkzgyYFGjsRJF9aMaLHyDU=

いろいろ暗号化したものをBASE64エンコードしたら、
7XDZk9F4ZI5WpcFOfej3Dbau3yc1kxUgqmRCPMkzgyYFGjsRJF9aMaLHyDU=
になる。
そのひとつ前で enc = xor(flag, key)とあり、暗号化されたビット配列は、flagとkeyの排他的論理和であることが分かる。
そのため、既知である暗号化されたbit配列と、keyの排他的論理和を取れば、flagが分かる。

与えられたソースコードを少しいじって、keyを出そうと試みた。


import random
import base64

def xor(msg, key):
    return bytes([ch1^ch2 for ch1, ch2 in zip(msg, key)])

def gcd(x, y):
  while y != 0:
    r = x % y
    x = y
    y = r
  return x

def gen_seed(n):
  seed = 0
  for k in range(1,n):
    if gcd(k,n)==1:
      seed += 1
  return seed

s = 1
for p in b"Enjoy_HarekazeCTF!!":
  s *= p
# print(s)
seed = gen_seed(s)
random.seed(str(seed).rstrip("0"))
print(seed)
key = bytes([random.randint(0,255) for _ in range(45)])

print(key)

しかし、gen_seedに渡す引数が
4529255040439033800342855653030016000
と膨大であるため、for文を素直に回していたら間に合わない。

gen_seed でやっていることは、
1~4529255040439033800342855653030015999
のうち、
4529255040439033800342855653030016000
との最大公約数が1である数の個数を返している。

4529255040439033800342855653030016000
を素因数分解すると以下のよう

import sympy

print(sympy.factorint(4529255040439033800342855653030016000))
{2: 10, 3: 8, 5: 3, 7: 2, 11: 5, 19: 2, 23: 1, 37: 1, 53: 1, 61: 1, 67: 1, 97: 2, 101: 2, 107: 1}

包除原理を用いて、
4529255040439033800342855653030016000
と互いに素であるものの数を数える。
https://ja.wikipedia.org/wiki/%E5%8C%85%E9%99%A4%E5%8E%9F%E7%90%86

import itertools
import functools
import operator
import random
import base64

base64_ans = "7XDZk9F4ZI5WpcFOfej3Dbau3yc1kxUgqmRCPMkzgyYFGjsRJF9aMaLHyDU="
# print(base64.b64decode(base64_ans))

# 最後のBASE64化された文字列のビット配列
enc_flag_bit_array = ""
for tmp in base64.b64decode(base64_ans):
    enc_flag_bit_array += bin(tmp)[2:].zfill(8)

# 最終的な暗号化されたフラグのビット文字列
# print(enc_flag)

import sympy

# Sはランダム関数の種を生成する際に使う数
S = 4529255040439033800342855653030016000

factors = sympy.factorint(S)

# 包除原理を用いて解く
prime_factors_list = [p for p, _ in factors.items()]
# print(prime_factors_list)

# for tup in itertools.combinations(prime_factors_list,2):
#     print(tup)

# Sと互いに素にならない数の個数
not_tagainiso = 0

for i in range(1, len(prime_factors_list) + 1):
    for tup in itertools.combinations(prime_factors_list, i):
        if i & 1:
            not_tagainiso += S // functools.reduce(operator.mul, tup)
        else:
            not_tagainiso -= S // functools.reduce(operator.mul, tup)
seed = S - not_tagainiso
random.seed(str(seed).rstrip("0"))
key = bytes([random.randint(0, 255) for _ in range(45)])

key_bit_array = ""
for i in key:
    key_bit_array += bin(i)[2:].zfill(8)

ans_bit_array = ""
for e, k in zip(enc_flag_bit_array, key_bit_array):
    ans_bit_array += str(int(e) ^ int(k))

ans = ""
for j in range(0, len(enc_flag_bit_array), 8):
    ans += chr(int(ans_bit_array[j:j + 8], 2))

print(ans)

HarekazeCTF{3ul3rrrrrrrrr_t0000000t1nt!!!!!}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0