사이트 내 전체검색
아래한글 2.X 암호 해독 사건
로빈아빠
https://cmd.kr/server/876 URL이 복사되었습니다.

본문

아래한글 2.X 의 암호 크랙(CRACK) 사건 개요

슈퍼컴퓨터로도 1백30년이상 걸려야 겨우 풀 수있다는  아래아한글의
문서암호를 27세된 한 청년 컴퓨터전문가가 간단히 풀어냈다.

  아래아한글 제작사인 한글과컴퓨터(한컴)사는 그간 "문서잠금 암호기
능이 42억9천4백96만7천2백95개의 숫자를 조합해 만든 것이기에 어떤  전
문가도 감히풀 수 없다"고 장담해온 터였다.

  컴퓨터를 이용한 전통적인 암호해독법은 코드 자동입력 소프트웨어를
써서 각가지 경우의 수를 빠른 속도로 무작위 입력시켜 보는 방법에 의존
하는 것이었다.

  그러나 화제의  이승욱씨(27.서울대 대학원 컴퓨터공학과 졸)가 행한
방법은  전혀달 랐다.

  이씨는 아래아한글 문서파일이 「HWP 2.1..XXXXXX」로 시작되는 머리
부분(해더)에 암호가 들어가며 이어 엔터(ENTER)키 코드가 들어가고 본문
내용은 그뒤에 따라오는 사실을 알았다.

  그는 문서파일의 이같은 규칙성에 주목했다.

  만일 프로그램이  문서파일에 처음 나오는 앤터키 코드값(16진법으로
는 0D)을 찾아내 이것 이전부분에 있는 숫자나 문자(즉 암호)는 무시하고
그 뒷부분만 읽어들이면 원래의 암호는 해독할 필요조차 없어질 것이다.

  결국 암호 자체를 해독하는 것이 아니라 아예 건너뛰어버리는 기발한
"발상의 전환"이 바로 해답이었다.

  그는 원래 한컴이 제공하는 AT급이하  구형컴퓨터용 아래한한글(꼬마
아래아한글)실행파일을 역엔지니어링(디스어셈블링)과 디버깅 기법을  이
용, 이처럼 문서암호 체제를 아예 무시하고 문서파일을 읽어들이도록  개
조한 'HAN21.EXE' 프로그램을  만들었다.

  이씨는 이같은 방법의 구상이 떠오르까지는 수개월이 걸렸지만  정작
프로그램개조를 하는데는 단지 며칠밖에 걸리지 않았다고 말했다.




암호화 기법

  암호작성 시스템은 메시지를 어떠한 분석에도 견뎌낼 수 있는 암호문으로 바
  꾸도록 설계하는  것이다. 여기에는 코드 시스템  또는 암호화시스템 방법을
  이용하고 있다. 코드시스템은 일반적  문자, 단어, 문장 등을 모르스 부호와
  같은 특정  기호로 표시하는 방법이다. 이에  반해 암호시스템은 알고리즘과
  키로 구성되어 있으며, 키는  알고리즘의 원칙에 따라 일반문장이 짜여진 문
  장 기호 또는 숫자의  순차적 표이다. 암호화 알고리즘은 간단하게 일반문장
  의 각 문자를 알파벳  순서에 따른 숫자로 표시하고 암호판(Key te.t)에서의
  알파벳 문자가 위치한  번호를 읽은 다음 이  두개를 합한 것이 가법군이다.
  암호해독은 메시지를  송신하는 사람과 수신하는 사람이 동시에
  같은 암호판을 가지고 있기 때문에 수신된 암호문을 암호작성 순서의 역으로
  찾아가면 일반문장으로 해독할  수 있다. 예를들어 "착륙지점은 B"라는 뜻의
  영문(Meet at landing zone(LZ) bravo)을 암호화하면 UGJUGBNECXIXT가 된다.
  이것은 일반문장의 영문을 알파벳 순으로 배열하고 5자리의 반복되는 숫자82
  516 82516 825와 합한 숫자를  표시한 후(이때 두 숫자의 합이 26을 넘는 경
  우 26을 빼고 계산한다. 예를들어  30은 26을 빼면 4가 되고 이것은 영문 알
  파벳으로 표기하면 D가 되는  것이다) 이 숫자를 영문 알파벳으로 표기한 것
  이 암호문이 되고 이 과정을 역으로 시행하면 암호를 해독할 수 있다.
  컴퓨터 네트워크의 보안은 블록  암호와 스트림 암호를 사용하고 있다. 블록
  암호는 일반문장을 암호문장으로 만들기 위해 키 텍스트의 블록과 대응할 수
  있도록 일정한 크기의 블록으로  분할한다. 합리적 보안을 위해 블록의 크기
  를 다소 크게 잡는다. 전형적으로 블록의 크기는 64비트이다. 스트림 암호는
  키 텍스트 문자열로부터 각각의 일반문장 요소를 하나로 조합하여 생성된 일
  반문장에서 운용된다. 스트림 암호는 통신 애플리케이션에 매우 적합한 것으
  로 일반문장에다 비밀암호 알고리즘을 더한 키 텍스트 문자열을 만들기 위해
  통상적으로 블록 암호화방법을  사용한다. 네트워킹 애플리케이션 이점은 커
  뮤니케이션의 파이프 라인에  나타나는 암호화의 각 요소는 모든 선행요소에
  달려 있다는 것이다.


  데이터 암호화 표준 알고리즘(DES)은  지난 77년 비밀로 취급되지 않은 정보
  의 전송과 저장에 대한 미 연방 정보처리표준(Federal Information ProcessS
  tandard:FIPS)으로 인정받았다. DES  알고리즘은 ANSI X. 3 106 1983으로지
  난 83년에 미국표준기구(ANSI)에 의해 공식적으로 인가받았다.

 기본적인 암호화  엔진은 64비트의 일반문장을  64비트 블록의 암호문장으로
  변형시키는 56비트 키를 사용하는  블록 암호로 운용된다. 각각의 64비트 일
  반문장의 블록은 절반인  32비트로 나누어지고 결과가 재결합되기까지 약 16
  개의 과정을  거친다. 이것을 ECB(Electronic Code  Book)모드라고 한다. 이
  모드를 사용하여 암호문장의 각 블록은 다른 모든 암호문장과 완전히 독립되
  어 침입의 위협에서 벗어날  수 있다. 암호 블록 연결(Cipher Block Chainin
  g:CBC)모드에서는 마지막 암호화작업에 의한 암호문장은 다음 블록의 비암호
  화된 일반문장과  연결된다. 이러한 방법으로 암호문장의  각 블록은 그것에
  선행되는 모든 문장과 연결된다.

  64비트의 일반문장 블록이 입력되      56비트의 키가 무작위로 선택되면 7×10
  의 16승에 해당하는 조합에 의해 만들어진 DES방어는 주먹구구식 끼워맞추기
  방법에 의한 침입을 충분히 막을 수 있다.

  DES는 대칭적 암호시스템으로 알려져 있다. DES 보안시스템의 근본적 단점은
  메시지를 송신하는 쪽과  수신하는 쪽 모두가 키를  가지고 있어야 하며, 이
  키는 자주 변경되어야 하고  키의 배포가 안전하게 보호되어야 한다. 이와는
  대조적으로 비대칭 암호시스템은 암호화와 암호해독을 하는 데 있어 공용 키
  와 개인 키를 기본으로 한다. 만약 A가 B에게 메시지를 송신하려면 A는 반드
  시 B의 공용 키를 알아야 하고 그 공용 키를 이용하여 송신 메시지를 암호화
  한다. 이 메시지는 오로지 B의  개인 키를 이용하여 B만 읽을 수 있다. 가장
  보편적으로 사용되고 있는  비대칭 알고리즘은 RSA 알고리즘인데 RSA 데이터
  보안회사가 특허와 사용허가를 가지고 있다. 비대칭 알고리즘의 단점은 계산
  주기가 길다는 것이다. 이러한  이유 때문에 대칭 알고리즘은 송신내용의 보
  호를 위해 많이 사용되고, 비대칭 알고리즘은 송/수신자의 확인을 위해 주로
  사용되고 있다. PC 베이스의 메시지 전송 애플리케이션용으로 사용이 가능한
  DES 소프트웨어 상품으로는  에베레트사의 "프라이비트 라인"과 센트럴 포인
  트 소프트웨어사의 "PC 시큐어"등이 있다.

  지난 75년 이후 전자우편의 확산으로 메시지 교환제품의 안정적인 사용을 주
  도할 표준 개발에 불이  붙었다. 이것은 메시지를 송신하는 사람과 수신하는
  사람이 다양한 플랫폼과 네트워크 아키텍처에 구애받지 않고 서로 통신할 수
  있도록 하는 모델을 규정하려는 것이다.

  전자우편용 표준으로는 X.400과 X.500이 채택되고 있다. X.400은 국제전신전
  화 자문위원회(CCITT)에 의해 개발된 OSI 추천의 전자 MHS 요소이며, 이외에
  송/수신자의 이름과 주소를 정의한다.

  X.500은 디렉토리를 조회하기  위해 사용되는 2개의 프로토콜(대화에 필요한
  통신규약으로 메시지의 처음과 끝에 붙임. DAP와 DSP가 있음)과 분산된 전체
  디렉토리의 구조를 정의한다.

  비대칭적 공공의 암호  시스템을 효과적으로 사용하기 위해서는 사용자가 반
  드시 송/수신용 키를 쉽게 획득할  수 있어야 한다. 만약 송/수신용 키가 공
  공의 X.500 디렉토리에 저장되는 경우 사용자와 시스템간 충돌이 일어나거나
  공공의 디렉토리  내용이 변경될 수 있다.  X.500 디렉토리는 보안이나 사설
  서비스가 아니기 때문에  디렉토리로부터 읽어낸 공공의 키를 확인하기 위해
  서 디렉토리가 사용자들에게  제공되어야 한다. 메시지 송신자의 확인은 OSI
  방어의 필수적 장치이다.


암호화 기법의 대표적 종류

일반 PC급에서 사용하는 암호화기법을 사용한 프로그램의 대표적인 예를 들겠다.
거의 난공불락이라는 DES 기법을 적용한 암호화이다.
하지만 아래한글 2.X 는 이제 크랙이 되었다.


1) 아래한글 2.5의 암호체계 ------------ Cracked
 실제는 이미 오래전에 Crack되었다고 알려졌다.
 단지 이번의 이승욱씨에 대한 이번 관심은 언론의 스타만들기였다.



2) Pkzip 2.04g의 암호체계 ------------- Cracked already
  완전하지는 않다지만 이미 93년말에 Crack 성공사실이 보고되었다는 문이다.


3) NU's DISKREET의 암호체계 ----------- Crackable but not yet
 아직 수금이 가는 증거로 Crack 했다는 소식을 들어보지 못했다.

4) PCtools' PC-Secure의 암호체계 ------ almost Crackable
 불가능하지 않다.


2,3,4번은 모두 DES의 변형을 사용한다.

특히 PKZIP은 압축과 암호화를 동시에 하기 때문에
정말로 정말로 풀기가 힘들다.
NORTON의 DISKREET를 쓰면
안전성이 다소 떨어진다는 소문도 있지만 편리하고 안전하게 암호를 걸 수 있다.
이것 역시 미국 정부 표준 암호화 방식을 지원하기 때문에
개인적인 보완을 위해서는 전혀 하자가 없다.
PKZIP과 DISKREET -- 피씨 툴즈의 PC-Secure도 있다. -- 의
암호화는 지금까지는 거의 못 푸는 방식으로 정평이 난
암호화 방식을 사용하고 있기 때문에 안심해도 될 것이다.

4-2-2 간단한 암호화 기법의 적용

암호화 저장 방법에 가장 간단하면서 쓸모있는것이 키값을 XOR 연산하는것이다.

< 소스 >


#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define MAXKEY 7

static KEY[MAXKEY] = { 0x32 , 0x23 , 0xf2 , 0xeb , 0x43 , 0x0c,0x1b };
static unsigned int count =0;

void code(char *in, char *out);

void code(char *in, char *out)

{

 FILE *fp,*fp2;
 char ch;

 if (( fp=fopen(in,"rb"))==NULL) {
        printf("cannot open input FILE [%s]\\n",in);
        exit(1);
        }

if(fp2=fopen(out,"wb"))==NULL) {
      printf("cannot open output FILE [%s]\\n",out);
            exit(1);
          }

do {

        ch=getc(fp);
        ch^= KEY[ count % MAXKEY ];
        if(feof(fp) ) break;
        putc(ch,fp2);
        count ++;

} while(!ferror(fp) && !ferror(fp2));

close(fp);close(fp2);
}

void main(int argc,char *argv[])
{

printf("CODE CHANGE V1.0 by ddn2\\n");
if(argc!=3){
  printf("\\a\\aUsage : codech <input file> <output file>\\n");
  exit(1);
}

if(!strcmp(argv[1],argv[2])) {
          printf("\\nFileNAME must be like !\\n");
          exit(0);
}
code(argv[1],argv[2]);
printf("\\n Complete Coding !!\\n");

}




간단한 암호화 XOR
암호화 시키고 싶은 문서를 메모리에 올려놓은뒤에 간단히 한바이트식 XOR 시키면 된다.

데이타 주소가  ds:0100 이라 할때..

  mov cx,45h <<== 데이타 크기
  mov di,100h <<== 데이타 주소
start:
  mov ah,[di]
  XOR ah,12h <<== 12h 로 XOR
  mov [di],ah
  inc di
  loop start


해제 방법도 똑같다.






암호화 프로그램 예제


#include <stdio.h>
#include <conio.h>
#include <string.h>

#define kcode 0x5f
main(int argc,char *argv[]){
  FILE *fp1,*fp2;
  int ch;
  textmode(C80);
  clrscr();
  textbackground(BLU );
  textcolor(CYAN);
  highvideo();
  window(20,10,60,20);
  clrscr();
  if(argc!=3){
      cprintf("Usuage:amho file1.nam file2.nam\\r\\n");
      printf("\\n\\n\\n\\n\\n\\n\\n\\n\\n      KIM's secret-number program");
      exit(1);
      }
  cprintf("\\r\\n\\n\\n\\n\\nNow,your progiam is LOCKKING\\r\\nFor secret-number\\r\\n
\\n\\n\\n");
  fp1=fopen(argv[1],"rb");
  fp2=fopen(argv[2],"wb");
  do{
      ch=getc(fp1);
      if(feof(fp1))break;
      ch^=kcode;
      putc(ch,fp2);
      }while(!ferror(fp1)&&!ferror(fp2));
  printf("Complete.........");
  fclose(fp1);
  fclose(fp2);
}



다음은 암호화나 컴퓨터 관련 정보를 얻을수 있는 대표적 사이트 들이다.

*컴퓨터 서비스
이름:biz.comp.services
자원종류:유즈냇
상업적인 서비스 사업소.

*통신
이름:comp.dcom.mobems
자원종류:유즈냇
데이타 통신,하드웨어와 소프트웨어.

*암호학
이름:crypto-l
자원종류:리스트서브
주소:crypto-l@jpntuvm0.bitnet
암호학 논의 진행중인 연구 정보교환등
수학과 관련된 정보 유포
구독요청주소.
info-pgp-request@lucpul.it.luc.edu

이름:info-PGP
지원종류:리스트
주소:info-pgp@lucpul.it.luc.edu
공개 키 암호 소프트웨어 패키지 PGP에 대한토론.

이름:info-PGP
자원종류:리스트
주소:info-pgp-request@lucpul.it.luc.edu
MS-DOS,UNIX,SPARC,VMS,애트리,아미가,그리고
다름 플랫폼을 위한 PHIL ZIMMER MAN & CO`S PRETTY
GOOD PRIVACY(PGP)공개 키 암호 프로그램을 위한 토론.

*사이버공간
이름:cyber-l
자원종류:리스트서브
주소:cyber-l@marist.bitnet
사이버공간 현상에 관한것.

이름:virtu-l
자원종류:리스트서브
주소:virtu-l@vmd.cso.uiuc.edu
유즈넷 단체인 sci.virtual-worlds의 한 게이트웨어
이며 가상현실에 대한 토론.

*연방정부
이름:fedjobs
자원종류:리스트서브
주소:fedjobs@dartcmsl.dartmouth.edu




아래아 한글에서의 코드 저장법

여러 다른 프로그램들처럼 아래아 한글도 한글 코드는 조합형 코드를 쓴다. 그러나
아래아 한글 화일을 일반 에디터로 뷰 해본 사람은 문서 자체는 전혀 나타나지 않는것
을 보았을 것이다. 아래아 한글 화일에서는 2Byte코드를 하위,상위의 순으로 나타내기
때문이다. 조합형 한글 "가"의 코드는 88h,61h인데 아래아 한글에서는 이것이 61h,88h
로 저장되는 것이다. 저장 뿐 아니라 아래아 한글의 모든 글자 처리는 그와 같은 방법
을 쓴다. 2Byte문자가 아닌것..예를 들자면 "A"의 경우는 아래아 한글에서는 2Byte코
드로 변환하여 처리한다 즉 A는 코드가 41h인데 아래아 한글에서는 0041h 즉, 41h,00h
로 바꾸어 처리하게 된다.

아래아 한글 암호체계에 대한 간단한 설명

아래아 한글 암호체계는 입력받은 암호를 가지고 여러 연산과정을 거쳐서 코드를 만
들고 그 코드로 문서를 암호화하는 방법으로, 이 방법은 원래 암호체계를 완전히 이해
하고 설사 아래아 한글의 소스를 입수했다고 치더라도 풀기가 거의 불가능한 방법이다.
입력받은 암호를 알아야 문서를 푸는 키 코드를 구할 수 있기 때문에, 소스를 입수한
다고 해도 암호체계를 이해하는 정도에서 그치게 된다. 그러나 그런 암호화 기법이 최
근 깨졌다. 이론적으로 깨기가 거의 불가능하다고 하는 암호가 깨진 이유는 무엇이며
그 방법은 무엇일까? 이 단원에서는 아래아 한글의 암호체계와 그것을 푸는 방법을 설
명한다. 아래아 한글 암호체계에서 쓰이는 코드는 두가지가 있는데, 편의상 이것들을
비교 코드, 키 코드라고 부르자.

비교코드란?

비교코드는 입력받은 암호가 문서의 암호와 같은지를 비교하는데 쓰이는 것으로 문서
의 해독 자체에는 관여하지 않는다. 아래아 한글은 문서에 암호를 담아 저장할때에 우
선 입력받은 암호의 아스키 코드 혹은 조합형 한글 코드를 가지고 사칙연산과 비트연
산을 섞어 그 결과를 화일에 저장한다. 그리고 암호가 걸린 문서를 불러올때는 암호를
입력받아 같은 계산을 하여 나온 결과와 화일에 저장된 결과를 비교하여 같을 경우 문
서를 푸는 작업으로 넘어가고 틀릴 경우에 암호가 틀리다는 메시지를 출력하고 문서읽
기를 취소한다.
이 코드의 목적은 단순히 불러올때 입력받은 암호와 저장할 때 입력받은 암호를 비교
하는 것이지만 그렇다고 없어어는 안되는 코드이다. 가령, 문서를 푸는 코드를 가지고
암호를 비교한다고 하면 문서를 푸는 코드가 화일에 저장되어 있어야 하는 것이다. 그
러면 문서 안에 저장된 코드를 가지고 그 문서화일을 풀 수가 있게 된다. 그러므로 비
교하는 코드와 문서를 푸는 코드를 따로 하여 비교하는 코드만 화일에 저장하는 것이
다.

비교코드의 구성

비교코드는 16bit, 즉 2바이트의 정수로 이루어져있다. 수로 친다면 0~65535의 값이
다. 그럼 이 비교코드는 어떻게 만드는가? 비교코드를 구하는 루틴을 c로 나타내면 다
음과 같이 된다. 여기서는 위에서 설명한 코드를 사용한다.

unsigned int password[100]={...} /*패스워드의 2Byte코드들... 0이면 끝을 나타냄*/

unsigned int get_comparing_code()
{
 unsigned int cc=0,offs=0;
 while(password[offs]==0) {
  cc<<=3;
  cc~=password[offs];
  cc+=0xa5;
  offs+=2;
 }
 cc%=0xff00;
 cc+=0x55;
 return(cc);
}

그럼 이 비교코는 어디에 저장되는가? 아래아 한글 2.1의 HWP문서화일의 offset 126
에 2Byte가 저장되어있다. 물론 암호가 없을때는 00h,00h 이다.
암호를 넣어 저장한 후에 위와 같은 식으로 비교코드를 계산하여 만든 후 그곳에 저
장된 수치를 보면 그 둘은 일치할 것이다.

해독코드란?

해독코드는 문서의 암호화를 푸는 데 쓰이는 코드로 가장 중요한 코드라고 할 수 있
다. 이 해독 코드는 문서 화일에 저장되지 않는다. 이 해독코드는 역시 암호를 입력받
아 그 아스키 코드 혹은 조합형 코드를 가지고 여러 연산을 하여 만들어지며, 암호를
넣어서 저장할때, 혹은 암호를 입력받아 불러올때 계산을 하여 암호화하여 화일을 저
장하거나 암호화된 화일을 풀어 읽어올때 잠시만 생성된다. 이 해독코드를 생성하는
과정은 불러올때의 경우 입력받은 암호로 만든 비교코드와 화일에 저장된 비교코드를
비교하여 같을 때 실행된다.


해독코드의 구성

해독코드는 비교코드와 마찬가지로 16bit, 2바이트의 정수로 이루어져있다. 역시 수
로 친다면 0~65535의 값이 된다. 해독코드의 제작법도 비교코드를 만드는 법과 매우
비슷하다. 단 해독코드를 만들때는 비트 연산 중 회전(Rotate)라는 연산을 쓰는데 많
은 고급언어들이 이 연산은 지원을 하지 않기 때문에 여기서는 다른 연산들을 섞어서
회전연산을 할 수 있도록 할 것이다.

unsigned int password[100]={...} /*패스워드의 2Byte코드들... 0이면 끝을 나타냄*/

unsigned int get_decoding_code()
{
 unsigned int dc=0,offs=0;
 while(password[offs]==0) {
  dc=(dc>>3)|((dc&7)>>13); /* dc를 오른쪽으로 3비트 회전한다 */
  /* 위의 문장 대신
    asm mov cl,3
    asm ror dc,cl
    으로 바꿀 수 있다.
  */
  dc~=password[offs];
  dc+=0xa5;
  offs+=2;
 }
 return(dc);
}



해독코드로 자료 해독하기

그럼 해독코드는 어떻게 하는가? 말그대로 문서를 해독하는 열쇠의 역할을 하는 것이
다. 그러면 문서는 어떤 원리로 풀게 되는 것인가? 우선 문서의 암호화에 대해 알아볼
필요가 있다. 아래아 한글의 문서 화일은 앞의 헤더 부분을 제외한 모든 부분이 문서
데이타 저장 부분으로 모든 부분이 2Bytes단위로 되어 있다. 그리고 암호화는 이 문서
데이타 부분의 전 부분에 하게 된다. 문서에서 사용되는 글의 코드 역시 먼저 설명했
던 방식으로 저장이 된다.
이 문서데이타 부분을 푸는 부분을 c언어로 표현하자면 다음과 같이 된다.

unsigned int document_data[SIZE]={...} /*문서 데이타의 2Byte 코드들...0이면 끝*/

void decode_document(int dc) /* 해독 코드가 입력으로 들어간다 */
{
 unsigned int ax,dx,offs=0,si;
 unsigned char ah,al,cnt,tmp;
 si=0;
 while(document_data[offs]==0) {
  ax=document_data[offs];
  cnt=6;
  do {
    dx=(dc>>cnt)+(dc&((1<<cnt)-1)>>(16-cnt); /*dx를 오른쪽으로 cnt만큼 회전*/
    tmp=ax>>8;
    ah=tmp;
    al=ax&255;
    ah^=(dx&255);
    ah^=al;
    al=tmp;
    ax=(ah<<8)+al;
    cnt-=2;
  } until cnt<6
  ax^=si;
  si++;
  document_data[offs]=ax;
 }
}

해독코드를 펑션에 넣고 암호화된 문서 데이타를 document_data 배열에 넣어 펑션을
호출하면 document_data에 해독된 코드가 담겨질 것이다.


아래아 한글 암호체계의 구멍

아래아 한글사에서는 아래아 한글 2.1의 암호체계는 32bit암호체계이며, 그에 따라
키를 무작위로 만들어 풀어보는 방식으로는 2의 32승인 약 4억2천번의 계산을 거쳐야
한다고 주장했다. 그러나 비교코드 16bit에 해독코드 16bit가 있다고 암호가 32bit라
고 계산한 것은 잘못이다. 실제로 암호를 푸는데 있어서는 해독코드만 필요한 것이다.
그러면 무작위로 대조해서 푸는 최악의 방법으로 푼다 하더라도 2의 16승인 약6만5천
번의 계산이면 되는 것이다. 6만5천번이면 엄청나게 많은 횟수이라 생각될 수 있으나
그렇지 않다. 컴퓨터란 말그대로 계산기이며 엄청나게 빠른 계산을 할 수 있다. 그리
고 위에서 알아보앗듯이 아래아 한글의 암호를 해독하는데 사용되는 연산은 비트 회전
(Shift/Rotate)나 And 등 덧셈 계산보다 기계가 빨리 할 수 있는 연산에 +,*,/가 약간
섞인 정도이다. 게다가 계산되는 수 역시 2Byte 정수형으로 하드웨어적으로 16Bit XT
이상이면 몽땅 연산이 가능한 수이다. 이렇게 되었을때 어셈블리어로 일일히 대입해보
는 프로그램을 짠다고 하면 짧게는 수십 초에서 길어도 수 분이면 암호가 풀리게 될
것이다.

어떻게 풀 수 있는가?

위에서 말했던 결점을 이용하면 암호체계는 풀릴 수 있을 것이다. 그러나 또 하나의
관문이 있다. 아래아 한글에서는 해독코드를 입력받아서 암호를 푸는 것이 아니다. 암
호 문자열을 받아 연산을 거쳐 비교코드를 만든 후에 비교코드와 비교하고 그 후에 해
독코드로 문서를 풀게 되어 있어서 해독코드만 알게 되면 비교코드를 모르게 되어 암
호가 틀렸다는 메시지를 받을 것이고 비교코드만 알게 된다면 해독코드로 풀을 때 문
서가 잘못 풀어져 "문서가 손상되었습니다."라는 에러가 나오게 될 것이다. 또한 두
코드를 한번에 모두 구한다고 했을때는 4억 2천번을 모두 돌려봐야 구할 수가 있는 것
이다.
그러나 이 문제 역시 생각을 어느정도 해보면 해결이 가능하다. 우선 해독코드를 구
하게 되었을때 문자열을 어떻게든간에 무작위로 만들어서 그 문자열로 해독코드를 구
했을 때 맞아 떨어지는 문자열을 기억한 후에 그 문자열로 비교코드를 계산으로 구하
여 아래아 한글 화일의 헤더 부분의 비교코드가 있는 곳에 써 넣으면 되는 것이다.
그런 후에 아래아 한글에서 문서를 읽을때 구한 문자열을 입력하면 암호는 풀리게 되
는 것이다. 또는 문서 부분을 아예 프로그램상에서 풀어서 암호없이 저장하는 방법도
있을 수 있다.
그런데 해독코드를 구하는 것 역시 문제가 있을 수 있다. 해독코드를 일일히 만들어
서 문서 데이타를 풀어나갈때 풀린 문서 데이타가 맞는 것인지를 알아낼 방법이 없는
것이다.
이것 또한 생각을 해보면 알 수 있다. 아래아 한글의 정상적인 코드는 한글일 때 조
합형 코드를 뒤바꾼 코드라는 것을 앞에서 언급했다. 문서 화일의 대부분의 첫 부분에
는 한글이 있을 것이고 코드를 풀었을 때 조합형 한글 코드가 되는 코드가 몇 퍼센트
가 있는지 세어서 어느 기준 이상이면 맞다고 치면 대부분의 문서가 풀리게 된다. 조
합형 한글 코드가 되는지 알아보는 방법은 조합형 한글의 원리만 알면 쉽게 알 수 있
다. 그러나 문서 중에는 앞부분에 표 또는 그림, 외국어 등이 많이 있는 경우가 잇다.
이 경우는 전자의 방법으로 풀 수는 없다. 이경우는 아래아 한글 문서 화일의 문서 데
이타 영역의 구조를 알아야 한다. 해독코드가 틀렸을 경우 전체적으로 틀리게 번역되
므로 그 구조 또한 오류가 발생하게 된다. 이 것을 알아보는 것이 가장 확실하나 아래
아 한글 화일의 구조는 공개되어있지 않으므로 구조를 알아내는 작업을 해야 할 것이
다.





다음은 HWP21NUM.EXE 의 C 소스이다.

HWP21NUM은 아래 한글 2.1 의 숫자화된 암호를 풀기위한 프로그램이다.
문제가 되고있는 CODE21 과는 달리 사용에 법적 하자가 전혀없다.


/* FileName: HWP21NUM.C

  Description: HWP 2.1 형식의 문서파일의 숫자 암호 해독기

  Programming: Lim Hyoung-Taek

  Compile: tcc -B hwp21num
*/

#include <io.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>

/* 설명: 여기서는 입력 키의 값을 최대 5개까지 잡아서 프로그래밍을 했다.
        만약 키 값을 더 많이 잡아야 하는 경우는 프로그램을 고쳐서
        사용하기 바란다. C를 조금 하시는 분이라면 쉽게 수정할 수
        있을 것이다.

        참고로 만약 암호가 123456과 같이 6자리인 경우는 암호 해독시
        23456과 같이 뒤에서 5개 까지만 해독한다. 이 경우 숫자는 0~9
        까지 이므로 키 하나씩 늘일때 마다 10배의 시간이 소요된다.

        사실 해독 프로그램이라기 보다는 패스워드 어택 프로그램과
        비슷하다고 할 수 있다.

        알고리즘 보다는 생산성 위주로 빨리 작성했기 때문에 소스를
        보면 다소 비효율적인 부분이 있을텐데, 좀 더 빠른 속도가
        나올 수 있도록 고쳐서 사용해도 좋다.

        제가 시간이 없어서 그런데, 담에 시간이 나면(거의 안날겁니다: 시간나면
        놀아야죠...) 강좌해 드리죠...

        사실 이 프로그램과는 다른방법으로... 실제로 한.컴에서는 32bit라고는
        하지만 16bit의 해독용키를 사용하므로 해독용 키를 어택해서 문서파일을
        정상적인 코드가 나올때까지 구하면 단 수십초면 쉽게 해독용 키를 구할
        수 있습니다. 일단 해독용키만 구하면 나머지는 쉽게 할 수 있죠..
        실제로 code21이 이런 방법일 겁니다...

        근데.. 한글 3.0에서는 64bit를 사용한다고 하니 물론 32bit의 해독키를
        사용하겠지만요...  이때는 정말 어택해도 42억번이상을 어택해야 하니
        (물론 중간에 구할수도 있지만요.) 이런 방법으로는 거의 불가능하다고
        할 수 있고요... 새로운 방법을 연구해야 할겁니다. 일단 zip의 암호화가
        32bit des라고 하니 이를 깨보는 것도 좋은 방법일겁니다. 실제로 아직
        깨지지 않고 있습니다. 일본에서 누군가 des를 깼다는 얘기도 있더군요...

        그러나 3.0에서도 숫자암호에서는 이 프로그램에서 알고리즘만 바꾸면
        그래도 적용할 수 있습니다.

        이런 방식으로 영문이나 한글 암호를 알아내는건 시간이 너무너무 엄청나게
        걸리므로 실제로 백년이 넘게(그 이상이 될수도: 키 갯수에 따라서 달라지
        므로..) 걸릴 수도 있습니다..

        하여간 참고하셔서 더 좋은 알고리즘을 연구하시기 바랍니다.
*/


/* 배열에 들어온 입력된 키(암호)를 이용해서 확인용 키를
  구하는 함수. 역으로 확인용 키에서 입력된 키를 구할 수는 없다.
  알고리즘을 잘 살펴보면 역으로는 불가능하다는 걸 알 수 있을
  것 이다. */
unsigned GetConfirmKey(unsigned InpKey[])
{
  int i = 0;

  _BX = InpKey[i];
  asm  xor  si, si
  asm  xor  ax, ax
  asm  cmp  bx, +00
  asm  jnz  _1e0f
  exit(0);
_1dfa:
  asm  mov  ax, si
  asm  mov  cl, 3
  asm  shl  ax, cl
  asm  push ax
  _BX = InpKey[i];
  asm  pop  ax
  asm  xor  ax, bx
  asm  add  ax, 0a5h
  asm  mov  si, ax
  asm  inc  WORD PTR i
_1e0f:
  asm  push ax
  _BX = InpKey[i];
  asm  pop  ax
  asm  cmp  bx, +0
  asm  jnz  _1dfa
  asm  mov  ax, si
  asm  mov  bx, 0ff00h
  asm  xor  dx, dx
  asm  div  bx
  asm  add  dx, +55h
  asm  mov  ax, dx

  return(_AX);
}

/* 배열에 들어온 입력된 키(암호)를 이용해서 해독용 키를
  구하는 함수. 역으로 해독용 키에서 입력된 키를 구할 수는 없다.
  알고리즘을 잘 살펴보면 역으로는 불가능하다는 걸 알 수 있을
  것 이다. */
unsigned GetDecodeKey(unsigned InpKey[])
{
  int i = 0;

  _BX = InpKey[i];
  asm  xor  si, si
  asm  xor  dx, dx
  asm  jmp  _0016;
_0007:
  asm  mov  cx, 3
  asm  ror  dx, cl
  asm  push ax
  _BX = InpKey[i];
  asm  pop  ax
  asm  xor  dx, bx
  asm  inc  WORD PTR i
_0016:
  asm  push ax
  _BX = InpKey[i];
  asm  pop  ax
  asm  cmp  bx, +00
  asm  jnz  _0007
  asm  mov  ax, dx

  return (_AX);
}

main(int argc, char* argv[])
{
  char path[80];
  unsigned ConfirmKey;
  unsigned InpKey[6];
  int handle;
  int PasswordCount  = 1;
  int fFoundPassword = 0;

  InpKey[5] = 0x0000;

  printf("┌───────────────────┐\\N");
  printf("│                                      │ \\n");
  printf("│    HWP 2.1 Numeric password decoder│\\n");
  printf("│                                      │\\n");
  printf("│  Production: 1995,  Lim Hyoung-Taek │\\n");
  printf("│                                        │\\ n");
  printf("└────────────────────┘\\n");

  if (argc < 2)
  {
    printf("Usage: HWP21NUM [pathname][filename][.hwp]\\n");
    exit(1);
  }

  strcpy(path, argv[1]);
  if (strrchr(path, '.') == NULL)
    strcat(path, ".HWP");

  handle = open(path, O_RDONLY);
  if (handle == -1)
  {
    printf("\\7Error: cannot find such directory or filename\\n");
    exit(1);
  }
  lseek(handle, 0x7eL, SEEK_SET);  /* 오프셋 7eh로 이동 */
  read(handle, &ConfirmKey, 2);    /* 확인키 읽기 */
  close(handle);

  printf("Wait a moment ...\\n");

  for (InpKey[4]=0x0030;InpKey[4] <= 0x0039; InpKey[4]++)
  {
    printf(".");
    for (InpKey[3]=0x0030;InpKey[3] <= 0x0039; InpKey[3]++)
    {
      if (InpKey[4] == 0x0030)
        InpKey[4] = 0;
      for (InpKey[2]=0x0030;InpKey[2] <= 0x0039; InpKey[2]++)
      {
        if (InpKey[3] == 0x0030 && InpKey[4]==0x0000)
          InpKey[3] = 0;
        for (InpKey[1]=0x0030;InpKey[1] <= 0x0039; InpKey[1]++)
        {
          if (InpKey[2] == 0x0030 && InpKey[3]==0x0000 &&
              InpKey[4] == 0x0000)
            InpKey[2] = 0;
          for (InpKey[0]=0x0030;InpKey[0] <= 0x0039; InpKey[0]++)
          {
            if (InpKey[1] == 0x0030 && InpKey[2]==0x0000 &&
                InpKey[3] == 0x0000 && InpKey[4]==0x0000)
              InpKey[1] = 0;

            if (GetConfirmKey(InpKey) == ConfirmKey)
            {
                printf("\\n\\n\\7Success: found password %d\\n", PasswordCount++);
                printf("\\nConfirm key = %xh", ConfirmKey);
                printf("\\nDecode  key = %xh", GetDecodeKey(InpKey));
                printf("\\nPassword    = %c%c%c%c%c\\n", InpKey[0],
                  InpKey[1], InpKey[2], InpKey[3], InpKey[4]);

                fFoundPassword = 1;
            }
          }
          if (!InpKey[1])
            InpKey[1] = 0x0030;
        }
        if (!InpKey[2])
          InpKey[2] = 0x0030;
      }
      if (!InpKey[3])
        InpKey[3] = 0x0030;
    }
    if (!InpKey[4])
      InpKey[4] = 0x0030;
  }

  if (! fFoundPassword)
    printf("\\n\\7Failure: password not found");
  printf("\\n");

}

댓글목록

등록된 댓글이 없습니다.

1,139 (6/23P)

Search

Copyright © Cmd 명령어 3.17.154.243