LFSR: Linear Feedback Shift Register
"AFSR"의 작동 방식과 관련 데이터를 역으로 추적하여 FLAG
를 복구하는 것이 목표?
AFSR
클래스는 특정 상태(state)와 기반 값(base)을 사용해 난수를 생성. 주요 메소드는:
num
만큼의 비트를 생성FLAG
를 숫자로 변환한 값을 AFSR
의 base로 설정AFSR.getNbytes(200)
을 호출해 난수를 생성하고, 결과를 출력AFSR.getNbytes(200)
으로 생성된 200 바이트의 데이터FLAG
값을 유추해야 한다...AFSR의 동작 역추적:
output.txt
를 활용해 FLAG
(base)를 찾는 역공격을 수행.AFSR.shift()
는 base와 state를 기반으로 동작하기 때문에, 생성된 난수를 분석하면 base를 도출할 수 있다FLAG 복구:
long_to_bytes
로 다시 변환하면 원래 플래그를 복구할 수 있다output.txt의 데이터를 이용해 base를 추정한 뒤 FLAG를 복구
from Crypto.Util.number import long_to_bytes
class AFSR:
def __init__(self, base):
self.state = 1
self.base = base
def shift(self):
self.state <<= 1
output = 0
if self.state >= self.base:
self.state -= self.base
output = 1
return str(output)
def getNbits(self, num):
output = ''
for _ in range(num):
output += self.shift()
return output
def getNbytes(self, num):
output = b''
for _ in range(num):
o = self.getNbits(8)
o = int(o, 2)
output += bytes([o])
return output
# output.txt에서 읽은 값 (16진수 문자열)
leaked_hex = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003bfc4bf526d43ebf96ff37cd060ee98c64cc5d97abf89c4e668378ccfc9484a00fda30c8d455c3c2c8c6141272fc8ffb2c5c0c1ac6fb51dc15650ac621e8e978b928957a3f6b36c5312b76d7394e3f34248a5b828ce634d19a4e07ac27ba57ead12b18dff1f1851f266827f5b051f6533c2ed55a0dce683e3b840d0a82b07776dbfd50ab7"
leaked_bytes = bytes.fromhex(leaked_hex)
# Base 추정 및 플래그 복구
for base_candidate in range(1, 2**32): # base의 범위 추정 필요
afsr = AFSR(base_candidate)
generated = afsr.getNbytes(200)
if generated == leaked_bytes:
print("Base Found:", base_candidate)
flag = long_to_bytes(base_candidate)
print("FLAG:", flag)
break
엄청 오래 걸린다... 얼마 전 SECCON의 악몽이 여기에서도...
shift()
의 동작 특징:
self.state <<= 1
: 상태를 좌측으로 1비트 이동.self.state >= self.base
: state
가 base
이상이면, state -= base
를 수행하고 출력 비트는 1.이 구조는 state가 base보다 작아질 때까지 반복적으로 base를 뺀다는 점에서 특정 패턴이 발생할 가능성이 있다
state
의 초기값:
1
이므로, base
값에 따라 state
의 변동 패턴이 달라진다출력 패턴 분석:
base
의 특성(예: 크기, 비트 길이)에 따라 출력 비트의 분포가 달라질 수 있다.초기 출력 확인
output.txt
의 초반 부분이 모두 0
으로 채워져 있다.base
값이 매우 크다는 것을 암시state
는 1
이므로, state
가 base
보다 커질 때까지 출력 비트는 0
base의 비트 길이 추정
1
이 되기 전에 몇 번의 shift()
가 수행되었는지 확인하면, base의 비트 길이를 유추할 수 있다from Crypto.Util.number import long_to_bytes
class AFSR:
def __init__(self, base):
self.state = 1
self.base = base
def shift(self):
self.state <<= 1
output = 0
if self.state >= self.base:
self.state -= self.base
output = 1
return str(output)
def getNbits(self, num):
output = ''
for _ in range(num):
output += self.shift()
return output
def getNbytes(self, num):
output = b''
for _ in range(num):
o = self.getNbits(8)
o = int(o, 2)
output += bytes([o])
return output
# output.txt에서 읽은 값 (16진수 문자열)
leaked_hex = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003bfc4bf526d43ebf96ff37cd060ee98c64cc5d97abf89c4e668378ccfc9484a00fda30c8d455c3c2c8c6141272fc8ffb2c5c0c1ac6fb51dc15650ac621e8e978b928957a3f6b36c5312b76d7394e3f34248a5b828ce634d19a4e07ac27ba57ead12b18dff1f1851f266827f5b051f6533c2ed55a0dce683e3b840d0a82b07776dbfd50ab7"
leaked_bytes = bytes.fromhex(leaked_hex)
# 1. 초기 출력의 연속된 0의 개수 확인
initial_zeros = 0
for byte in leaked_bytes:
if byte == 0:
initial_zeros += 8 # 1 byte는 8비트
else:
# 마지막 0이 아닌 비트를 포함한 패턴 분석
bits = bin(byte)[2:].zfill(8) # 8비트로 표현
initial_zeros += bits.index('1')
break
print(f"초기 0의 개수: {initial_zeros}")
# 2. base의 범위 추정
# state가 1에서 시작하므로, 2^(initial_zeros-1) <= base < 2^(initial_zeros)
base_min = 2 ** (initial_zeros - 1)
base_max = 2 ** initial_zeros
print(f"base 범위: {base_min} ~ {base_max}")
# 3. base 추정 및 플래그 복구
for base_candidate in range(base_min, base_max):
afsr = AFSR(base_candidate)
generated = afsr.getNbytes(200)
if generated == leaked_bytes:
print("Base Found:", base_candidate)
flag = long_to_bytes(base_candidate)
print("FLAG:", flag.decode())
break
noah@DESKTOP-GI79V9I ~/ctf/DH_S6R12/AFSR master /home/noah/.pyenv/ver
sions/3.8.13/bin/python /home/noah/ctf/DH_S6R12/AFSR/solve.py
초기 0의 개수: 542
base 범위: 7198262071269114212496861612297570974191515389283066612961208916178940129074380592510465097766225371439873457013633432197133225688790879502413624289384262168215552 ~ 14396524142538228424993723224595141948383030778566133225922417832357880258148761185020930195532450742879746914027266864394266451377581759004827248578768524336431104
이거 맞나...?
state
는 초기값이 1
이며, 매번 왼쪽으로 쉬프트(<<= 1
)state >= base
일 때만 state -= base
가 실행되고 출력 비트가 1
즉, AFSR
은 base를 이용해 특정 패턴의 비트열을 생성하는 단순한 결정론적 알고리즘
output.txt
의 초반에는 대부분 0
으로 채워져 있음1
)가 base보다 작기 때문에 발생하며, base의 크기가 상당히 크다는 것을 암시1
이 발생하기 시작state
가 base 이상으로 커지는 순간을 의미shuffle shuffle shuffle
noah@DESKTOP-GI79V9I ~/ctf/DH_S6R12/many-shuffle master file many-shuffle
many-shuffle: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8e261a0fff21890df80c145ef4e840b6137acf85, for GNU/Linux 3.2.0, stripped
noah@DESKTOP-GI79V9I ~/ctf/DH_S6R12/many-shuffle master file flag
flag: ASCII text, with no line terminators
many-shuffle
stripped
상태이므로, 심볼 테이블이 제거되어 디버깅이 어려움flag
many-shuffle
실행 파일의 동작 분석many-shuffle
실행 파일이 어떤 방식으로 flag
파일을 처리하는지 확인해야 함strace
, ltrace
등을 사용해 시스템 호출 및 라이브러리 호출을 추적flag
파일에 접근하거나 데이터를 처리하는 방식이 확인되면, 이를 분석many-shuffle
실행 파일의 내부 동작을 분석flag
복구many-shuffle
이 flag
데이터를 섞거나 암호화했다면, 복구 알고리즘을 구현noah@DESKTOP-GI79V9I ~/ctf/DH_S6R12/many-shuffle master ./many-shuffle
Random String Generated! Now Shuffle...
Shuffled String: LLTDTNSFVSPNMTTM
Original String?:
main 함수 디컴파일
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned int v3; // eax
size_t v4; // rax
int i; // [rsp+1Ch] [rbp-A4h]
int j; // [rsp+20h] [rbp-A0h]
int k; // [rsp+24h] [rbp-9Ch]
char *lineptr; // [rsp+28h] [rbp-98h] BYREF
size_t n; // [rsp+30h] [rbp-90h] BYREF
FILE *stream; // [rsp+38h] [rbp-88h]
char s[64]; // [rsp+40h] [rbp-80h] BYREF
char dest[32]; // [rsp+80h] [rbp-40h] BYREF
char s2[24]; // [rsp+A0h] [rbp-20h] BYREF
unsigned __int64 v15; // [rsp+B8h] [rbp-8h]
v15 = __readfsqword(0x28u);
sub_1664(a1, a2, a3);
v3 = time(0LL);
srand(v3);
for ( i = 0; i <= 15; ++i )
s[i] = rand() % 26 + 65;
s[16] = 0;
puts("Random String Generated! Now Shuffle...");
v4 = strlen(s);
strncpy(dest, s, v4);
for ( j = 0; j <= 15; ++j )
{
for ( k = 0; k <= 15; ++k )
{
if ( (j & 1) != 0 )
dest[byte_4020[16 * j + k]] = s[k + 32];
else
s[byte_4020[16 * j + k] + 32] = dest[k];
}
}
printf("Shuffled String: %s\n", dest);
printf("Original String?: ");
fgets(s2, 18, stdin);
s2[strcspn(s2, "\n")] = 0;
if ( !strcmp(s, s2) )
{
lineptr = 0LL;
n = 0LL;
stream = fopen("./flag", "r");
getline(&lineptr, &n, stream);
printf("Match! Here's your flag: %s", lineptr);
free(lineptr);
fclose(stream);
}
else
{
puts("Wrong...");
}
return 0LL;
}
문자열은 16자리의 임의의 알파벳 배열
# byte_4020 배열 값 (256개 값, 16x16 매트릭스)
byte_4020 = [
0x0B, 0x08, 0x03, 0x04, 0x01, 0x00, 0x0E, 0x0D, 0x0F, 0x09, 0x0C, 0x06, 0x02, 0x05, 0x07, 0x0A,
0x0F, 0x04, 0x08, 0x0B, 0x06, 0x07, 0x0D, 0x02, 0x0C, 0x03, 0x05, 0x0E, 0x0A, 0x00, 0x01, 0x09,
0x04, 0x0C, 0x0E, 0x05, 0x0D, 0x06, 0x09, 0x0A, 0x01, 0x00, 0x0B, 0x0F, 0x02, 0x07, 0x03, 0x08,
0x0F, 0x03, 0x04, 0x06, 0x00, 0x0B, 0x01, 0x0D, 0x09, 0x07, 0x05, 0x02, 0x0C, 0x0E, 0x08, 0x0A,
0x0E, 0x03, 0x0C, 0x0D, 0x00, 0x05, 0x04, 0x08, 0x07, 0x09, 0x04, 0x0B, 0x05, 0x06, 0x0F, 0x08,
0x00, 0x03, 0x01, 0x0A, 0x0D, 0x02, 0x0E, 0x0C, 0x07, 0x0A, 0x0E, 0x09, 0x07, 0x08, 0x0D, 0x03,
0x0B, 0x0C, 0x0F, 0x02, 0x00, 0x04, 0x05, 0x06, 0x01, 0x05, 0x04, 0x0D, 0x01, 0x00, 0x02, 0x09,
0x0B, 0x0C, 0x07, 0x08, 0x0A, 0x06, 0x0E, 0x0F, 0x03, 0x04, 0x08, 0x05, 0x02, 0x0A, 0x0F, 0x0B,
0x07, 0x00, 0x01, 0x0C, 0x03, 0x0E, 0x06, 0x09, 0x0D, 0x0D, 0x0E, 0x0F, 0x0B, 0x00, 0x02, 0x0A,
0x04, 0x07, 0x06, 0x09, 0x01, 0x05, 0x03, 0x08, 0x0C, 0x0E, 0x02, 0x03, 0x05, 0x0A, 0x01, 0x07,
0x00, 0x09, 0x0D, 0x0C, 0x0B, 0x04, 0x06, 0x0F, 0x08, 0x03, 0x0B, 0x0E, 0x0A, 0x06, 0x04, 0x07,
0x01, 0x02, 0x0D, 0x0F, 0x00, 0x0C, 0x09, 0x05, 0x08, 0x0D, 0x0F, 0x01, 0x02, 0x0C, 0x0A, 0x03,
0x07, 0x09, 0x06, 0x08, 0x05, 0x00, 0x04, 0x0B, 0x0E, 0x00, 0x0E, 0x04, 0x0D, 0x06, 0x01, 0x0A,
0x05, 0x03, 0x0C, 0x07, 0x0B, 0x0F, 0x02, 0x08, 0x09, 0x0B, 0x02, 0x08, 0x07, 0x05, 0x03, 0x09,
0x0D, 0x04, 0x0F, 0x00, 0x01, 0x06, 0x0C, 0x0E, 0x0A, 0x0B, 0x01, 0x08, 0x00, 0x0C, 0x0D, 0x04,
0x0E, 0x0A, 0x06, 0x0F, 0x07, 0x09, 0x05, 0x03, 0x02
]
for ( j = 0; j <= 15; ++j )
{
for ( k = 0; k <= 15; ++k )
{
if ( (j & 1) != 0 )
dest[byte_4020[16 * j + k]] = s[k + 32];
else
s[byte_4020[16 * j + k] + 32] = dest[k];
}
}
noah@DESKTOP-GI79V9I ~/ctf/DH_S6R12/p-rho/deploy master checksec ./prob
[*] '/home/noah/ctf/DH_S6R12/p-rho/deploy/prob'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No