프로그램 분석




이번 문제는 바이너리 없이 client, host 에서 돌고 있는 파이썬 스크립트를 주어지는데
주어진 스크립트에는 중요 변수 내용(iv, key, cookie) 값이 없는 상태이다.



주요 코드를 살펴보면  client에선 ID, PW, COOKIE 값을 (ID-PW-COOKIE)를 암호화한 값을 전송하며
서버에선 이 값을 복호화해 - 를 기준으로 split해 값을 판별하는 것을 알 수 있다.




취약점 탐색

이번 문제를 보면서 역시나 시스템적인 취약점이 있지 않을까? 하면서 여기 저기 값을 "A"*100.... 넣어봤는데 취약점은 발동하지 않았다.

문제의 의도대로 AES - CBC 암호화에 대해 알아야 풀 수 있는 것 같아 암호화 알고리즘에 대해 공부했다.
참고 자료에서 보면 자세히 알 수 있지만 간단히 말하자면
plain text를 block size 에 따라 나눠 암호화하고, plain text (xor) initial vector 한 값을 암호화해 나온 암호화 block 값을 다음 암호화에 사용하는 게 핵심적인 내용이다.

여기서 중요한 것은 plain text를 block size로 나눠 암호화 하며, 첫 번째 block은 initial vector 값에 의존해 암호화되어 결과값이 나온다는 것 2가지이다.



위 코드에서 보듯, block size는 16byte 이므로 16byte씩 끊어서 암호화를 진행하게 된다

이 때 위의 그림을 보면 id, pw, cookie 값을 - 문자를 기준으로 split 하는 것이 중요한 취약점이 될 수 있다.

사용자의 입력을 받는 id, pw 같은 경우 - 문자가 들어갈 수 있으므로 (입력 가능한 문자 아래 그림 참조)




사용자가 만약 id를 a-b-c- 로 입력 시 프로그램은 a를 ID, b를 PW, C를 COOKIE 값으로 판별하게 된다.

즉 사용자의 의도대로 ID, PW, COOKIE 값을 변조할 수 있는 것인데 서버 프로그램의 코드를 살펴보면



사용자가 전송한 COOKIE값이 일치하지 않을 경우 0을 return해 인증되지 않은 사용자라는 출력을 해주고 프로그램은 종료된다.
결국 COOKIE 값을 맞춘 후, hash값만 일치시키면 admin 사용자로 인증할 수 있게 된다.



COOKIE 값을 구하기 위한 Idea 고찰

cookie값은 어떤 값, 방식을 통해 구할수 있을까? 고민하다가 

우선 id, pw, cookie 값이 - 로 구분되는 것첫 번째 암호화된 block의 변수는 plain block1 값과 iv 값 두 가지 인것을 이용하기로 했다.

즉 iv의 값이 고정일 경우 plain block1 의 값이 변해야 encyrpted block1 의 값이 변하는 것이므로 다음과 같은 아이디어를 생각했다.


ID : '-' * 12, PW : '-' 를 입력한다면 ?

암호화 되기 전 평문은  ---------------COOKIEVALUE.... 이런식으로 구성 될텐데 이 때 블럭 사이즈가 16 이므로 

---------------C |  OOKIEVALUE...... | ......
   block 1.              block 2.             block 3 ....

위 평문을 암호화한 암호문의 첫번째 블락의 값을 구하면 ---------------C, 즉 쿠키의 첫번째 글자를 포함한 암호화 값을 알 수 있다.



또 우리는 ID, PW에 '-' 문자를 포함할 수 있으므로 

ID : '-'*15 + ?      , PW : anything

위 물음표 자리에 문자열을 하나씩 대입해 나오는 암호문의 첫번째 블락 값을 위 쿠키의 첫번째 글자를 포함한 암호화 값비교하면

쿠키의 첫번째 글자의 값을 알 수 있다.

이와 같이 한글자씩 쿠키의 값을 알아낼 수 있다.



COOKIE 값 구하는 코드

코드 돌리면서도 계속 헷갈려서 자동화는 안하고 손으로 맞춰보면서 돌렸다.
한마디로 발로 짜서 개판인 코드 허허...

from pwn import *


context(arch = 'amd64', os = 'linux')

local=False


# first  

p = remote("pwnable.kr", 9006)


print p.recvuntil('ID\n')

p.sendline('-'*12)

print p.recvuntil('PW\n')

p.sendline('-')

data = p.recvuntil('user\n')

enc = data[data.index('(')+1:data.index(')')]

print len(enc)


enc = enc[0:32]

print enc

print len(enc)


# brute

for i in '1234567890abcdefghijklmnopqrstuvwxyz-_ABCDEFGHIJKLMNOPQRSTUVWXYZ' :

        p = remote("pwnable.kr", 9006)

        p.recvuntil('ID\n')

        p.sendline('-'*15+i)

        p.recvuntil('PW\n')

        p.sendline('-')

        data = p.recvuntil('user\n')

        try_ = data[data.index('(')+1:data.index(')')]

        try_ = try_[0:32]

        if enc == try_ :

                print "FIND!! first one is " + i

                break

 

 




Flag 얻기

cookie값을 알았으므로 나머지는 간단히 처리할 수 있다.



위 코드를 보면 ID가 admin 이며, PW의 hash 값이 맞는 경우 서버로부터 2를 return 받고 flag 값을 알 수 있다. 

다음과 같이 'admin' + cookie 값의 hash 값을 구해서

print hashlib.sha256("admin"+COOKIE_VALUE).hexdigest()



ID :  admin-HASH_VALUE-COOKIE_VALUE
PW : anything

을 전송하면 정상적으로 flag 값을 확인 할 수 있다.








===============================================================================================



참고자료 1. (출처:https://wikidocs.net/64)


3.5. 축약함수(Lambda)

오늘은 람다 형식과 그것을 이용하는 여러가지 함수들에 대해서 알아보겠습니다. 당장 완벽하게 소화하실 필요는 없을 것 같구요, 가벼운 마음으로 이런 것이 있다는 정도만 아셔도 되지 않을까 합니다. 람다 형식은 인공지능 분야나 AutoCAD라는 설계 프로그램에서 쓰이는 Lisp 언어에서 물려받았다고 하는데요, 함수를 딱 한 줄만으로 만들게 해주는 훌륭한 녀석입니다. 사용할 때는 아래와 같이 써주면 되지요.

lambda 인자 : 표현식

다음은 두 수를 더하는 함수입니다.

>>> def hap(x, y):
...   return x + y
...
>>> hap(10, 20)
30

이것을 람다 형식으로는 어떻게 표현할까요?

>>> (lambda x,y: x + y)(10, 20)
30





참고자료 2. (https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29)


암호 블록 체인 방식 (CBC)[편집]

암호 블록 체인 (cipher-block chaining, CBC) 방식은 1976년 IBM에 의해 개발되었다.[4] 각 블록은 암호화되기 전에 이전 블록의 암호화 결과와 XOR되며, 첫 블록의 경우에는 초기화 벡터가 사용된다. 초기화 벡터가 같은 경우 출력 결과가 항상 같기 때문에, 매 암호화마다 다른 초기화 벡터를 사용해야 한다.

Cbc encryption.png

Cbc decryption.png

CBC 방식은 현재 널리 사용되는 운용 방식 중 하나이다. CBC는 암호화 입력 값이 이전 결과에 의존하기 때문에 병렬화가 불가능하지만, 복호화의 경우 각 블록을 복호화한 다음 이전 암호화 블록과 XOR하여 복구할 수 있기 때문에 병렬화가 가능하다.



'Pwnable.kr > Rookiss' 카테고리의 다른 글

[pwnable.kr] loveletter - 50pt  (0) 2018.09.07
[pwnable.kr] brain fuck - 150pt  (0) 2017.04.16
[pwnable.kr] md5 calculator - 200pt  (0) 2017.01.02
[pwnable.kr] fsb - 20pt  (0) 2016.11.17
[pwnable.kr] echo2 - 50pt  (0) 2016.11.10





정적라이브러리

- 동적(공유)라이브러리에 비해 실행 속도가 빠르고 배포에 제약이 없음

- 다만, 해당 라이브러리를 필요로 하는 모든 경우 같은 정적 라이브러리가 링크되기 때문에 배포 파일들의 사이즈가 커짐

- 그러므로 하드디스크 공간도 더 차지하고 메모리도 더 많이 차지함

- 그러나 유닉스 시스템의 경우 그때그때 필요한 부분만 메모리에 로딩하는 demand paging을 사용하기 때문에 정적인 라이브러리의 메모리 사용률과 공유 라이브러리의 메모리 사용률의 차이가 크지 않음


시스템에서 응급시에 필수로 쓰이는 유틸리티와 실행속도를 극대화해야 하는 몇몇 서버를 제외하고는 대부분 동적 라이브러리를 사용한다.



*shared library와 dynamic link library는 다른 개념이다

그러나 대부분의 경우 dynamic link library는 shared library를 만들 때 사용됨



확장자별 라이브러리 종류

*.a: 리눅스/정적 라이브러리

*.so: 리눅스/동적라이브러리

*.lib: 윈도우/정적라이브러리

*.dll: 윈도우/동적라이브러리




정적 라이브러리를 가지고 동적라이브러리로 만들기

a파일(정적라이브러리)에서 o파일(오브젝트)을 뽑아낸 다음 ld로 so타겟으로 해서 만들면된다.

$ar x abc.a

 a.o, b.o, c.o

$gcc -shared -o abc.so *.o //다시 o를 lib로 묶는다



라이브러리와 헤더파일

1. 다르다.

라이브러리는 기계어로 번역된 라이브러리이고,

헤더파일은 컴파일 하기 전의 즉, 프로그래머가 이해할 수 있고 문법에 맞게 작성되어 있는 선언들의 집합이다.


헤더가 여러개 모이는 것 != 라이브러리

컴파일된 산물인 *.o(오브젝트)파일을 여러개 모아 놓은 것 == 라이브러리


라이브러리를 사용하기 위해서 해당 라이브러리의 헤더파일이 있어야한다. 링커가 알아먹을 수 있는 심볼네임을 가지고 라이브러리를 뒤져서 링크를 하게 된다.

컴파일러가 이런 헤더파일을 가지고 심볼네임을 만들어서 오브젝트 파일에 넣어주면 링커가 해당 심볼네임을 가지고 라이브러리를 뒤져서 링크를 하게 된다.



공유와 동적의 차이

- 두개의 차이를 특별히 나누지 않는다. 다만 동적lib를 공유방식으로 사용하느냐, 독립적으로 사용하느냐는 메모리에 load할 때 결정된다. 'man dlopen'참조



출처: http://ndlessrain.tistory.com/entry/라이브러리-a-파일-so-파일 [ndlessrain]

'관련TIP' 카테고리의 다른 글

커널 모듈의 관리  (0) 2018.02.07
정규표현식 정리  (0) 2017.12.23

문제 푸느라, 사실 삽질하느라 날아간 일요일을 기리며 허허...

주말저녁에 문제 풀고 writeup 쓰고 있는게 싱숭생숭하면서도 보람차네요(사실 풀고나면 별거 아니지만요)


======================================================


보호 기법 확인(checksec)




CANARY, NX 확인, 서버 환경 상 ASLR enable.




프로그램 동작 분석



x86 binary, 실행 시 사용자의 입력을 받고 프로그램 종료.

겉보기엔 모르니 리버싱을 해봅시다




바이너리 리버싱






main() 에서 사용자 입력을 받아 한 글자씩 파싱. do_brainfuck() 함수 내부에서 각 case에 알맞는 기능을 처리


여기서 p 변수를 살펴보면 main() 에서 p = &tape 로 초기화하고 이때 값은 0x804a0a0 .


이 함수에서 p 의 값을 이용해 ++, --, putchar ... 등등 처리를 해주므로 이것을 이용해 볼 수 있겠다.




Exploit 전략 구성


0x804a0a0 값을 기준으로 >, < 를 이용해 메모리 값을 변경할 수 있고 getchar(), putchar() 명령을 사용할 수 있다.


다만 BOF나 다른 취약점이 아니므로 stack을 마음대로 바꾸지 못하고, parameter로 넘어가는 값을 쉽게 조작할 수 없다.


system(/bin/sh) 를 실행하기 위해 어떻게 할까



- puts() 이용 ?



puts의 GOT를 system으로 덮어쓰고 "[ and ] not supported." 를 overwrite 하려고 했는데 당연하게도 위 string의 위치는 쓰기 권한 없음


위 영역에 어떻게 접근해서 써야하나 고민 많이 했는데 당연히 안됨. 실패.


- putchar() 이용 ?



위와 비슷하게 _putchar의 GOT를 system으로 덮고 파라미터로 넘어가는 ds:p를 조작해 /bin/sh string 주소를 넘김


하지만 넘기는 값이 byte ptr [eax] 이므로 메모리 주소가 정상적으로 넘어가지 않음.


코드를 좀더 꼼꼼히 봤어야 했는데.. 실패.



- setvbuf() 이용 !


main() , do_brainfuck() 에서 이용하는 함수 중 첫번째 parameter를 control할 수 있는 함수가 뭐가 있을까 살펴보다가 




setvbuf 호출 시 첫번째 parameter는 stdin 으로




해당 값은 bss 영역으로 Read/Write 권한이 있다.


이를 이용해 다음과 같이 system(/bin/sh) 을 위한 전략을 구상했다.


1 - setvbuf() GOT 를 system() 으로 overwrite. ( 문제에서 libc도 제공하므로 두 함수 사이의 offset 계산 가능 )

2 - stdin overwrite

0x804a040  :  0x804a044

0x804a044  :  /bin/sh\x00

3 - puts() GOT 를 main() 내 setvbuf() 호출하는 코드 주소로 overwrite ( 위의 경우는 0x80486b9 )

4 - '[' 입력으로 puts() 호출.


정상적으로 쉘 획득하는 것을 확인함.




공격 코드





'Pwnable.kr > Rookiss' 카테고리의 다른 글

[pwnable.kr] loveletter - 50pt  (0) 2018.09.07
[pwnable.kr] crypto1 - 120pt  (0) 2017.05.20
[pwnable.kr] md5 calculator - 200pt  (0) 2017.01.02
[pwnable.kr] fsb - 20pt  (0) 2016.11.17
[pwnable.kr] echo2 - 50pt  (0) 2016.11.10

+ Recent posts