티스토리 뷰

Security/시스템 해킹

쉘 코드(/bin/sh

on1ystar 2019. 1. 16. 02:48
728x90
반응형

본 글의 목적은 제가 공부한 내용을 바탕으로 정리하면서 저와 같이 공부하시는 분 들을 위함입니다. 때문에 부족한 부분이 있을 수 있고, 잘못된 부분이 있을 수 있습니다. 만약 있을 경우, 생각을 고칠 수 있도록 저에게 알려주시면 정말 감사하겠습니다 !!



Ø  shell code 작성


ü   execve() 함수

#incldue(unistd.h)

int execve( const char *filename, char *const argv[], char *const envp[]);

                    파일이름,    파일 인자의 포인터,  환경 변수의 포인터

execve()filename이 가리키는 파일을 실행합니다. filename은 바이너리 실행 파일이거나 #! interpreter [arg]와 같은 라인으로 시작하는 스크립트 파일이어야 합니다. argv envp는 포인터 배열로 마지막에 NULL 문자열을 저장해야 합니다. (솔직히 무슨 소리인지 자세하게는 모르겠습니다…)

중요한 점은 이 함수를 통해 /bin/sh 프로그램을 실행시킬 수 있다는 점입니다 !

 

ü  C 작성한 /bin/sh 실행 코드


execve 첫 파라미터 값으로 실행할 프로그램의 문자열(위치)를 주었고, 다음으로 배열의 주소 값, 그리고 환경 배열에는 NULL 을 줍니다.

이를 컴파일 한 후 실행시켜보면


이렇게 shell을 사용할 수 있게 됩니다.

이때 정적으로 바이너리 파일 내에 포함되어 컴파일하는 -static 옵션을 주면 함수의 내부까지 어셈블리어로 자세히 볼 수 있습니다.

gdb를 이용해 main 함수를 disassemble한 내용입니다.


우리가 자세히 봐야 할 것은 execve함수 이기 때문에 execve함수를 disassemble해봅니다.


Syscall_error 라는게 뜨네요자세히 보니까 위에 main함수에서도 stack_chk_fail이라는 게 뜹니다. 이유를 알고 싶은데 찾다가 도저히 모르겠어서 일단 비슷하게 코딩을 한 분의 디버깅을 가져와 봤습니다.

ü gdb disassembling


첫 번째와 두 번째 코드는 execve함수를 스택에 넣는 과정이고, 세 번째 코드부터 call 이전 명령어 전 까지가 인터럽트 호출을 위해 레지스터에 인자를 할당하는 내용입니다.

세 번째 코드는 execve함수의 세 번째 인자인 NULLEDX에 저장하고, 네번 째 코드는 앞으로 인자로 사용될 EBX를 보존하는 단계입니다.

다섯번 째 코드는 execve함수의 두 번째 인자 주소를 ECX에 저장하고, 여섯번 째 코드는 첫 번째 인자 파일 이름을 EBX에 저장합니다.

이제 execve를 호출할 준비가 되었기 때문에 execve함수의 시스템 콜 넘버인 11(0xb)EAX에 저장해 줍니다.


(시스템 콜 번호)

이제 마지막으로 call DWORD PTR ds:0x80d6788코드가 int 0x80으로 시스템 호출에 해당하는 인터럽트 번호 0x80을 가리킵니다.

여기까지 해석한 부분을 어셈블리어 코드로 만들어 쉘 코드를 작성해 주면 됩니다.

 

 

ü  어셈블리어 코딩


jmp short lastshell 함수를 호출하기 위해 “last:”로 이동합니다.

last에서 shell call합니다. 그러면 스택에 리턴 주소가 들어가게 되는데 그 주소는 ‘/bin/sh’의 주소가 됩니다.

그럼 pop ebx를 통해 해당 주소(문자열의 시작 주소)를 저장해 줍니다.

xor연산을 통해 eax값을 0으로 만들어 줍니다.

※ xor연산을 해 주는 이유는 쉘 코드 중간 중간에 \x00이라는 널 문자를 없애기 위해서 입니다. 이 널문자는 대부분의 문자열을 다루는 함수들이 문자열의 끝으로 인식하여 값을 읽어 들이는 작업을 중단하기 때문입니다.

따라서 xor이라는 배타적 논리합을 이용해 널문자가 아닌 0을 넣어주는 작업이 필요합니다.

문자열 /bin/sh 끝에 NULL 바이트를 저장합니다.

NULL 바이트가 끝난 ebx+8ebx 레지스터에 저장된 주소 값을 저장합니다.( 문자열의 주소 값이겠죠? )

그리고 ebx+12영역에 eax레지스터에 저장된 32비트 NULL바이트 값을 저장합니다. ( eax 32비트 레지스터니까 )

lda 명령어로 각각 execve함수의 두 번째, 세 번째 인자에 주소를 저장하고 al레지스터에 execve함수의 시스템 콜 번호인 11을 저장합니다.

마지막으로 int 0x80으로 함수를 실행해 줍니다.

ü  기계어로 출력하기


여기서 두 번째 섹션에 있는 기계어들을 모아 쉘 코드를 작성해 줍니다.

\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xe5\xff\xff\xff/bin/sh

ü   코드 삽입하기


sh쉘이 실행된 것을 알 수 있습니다.

ü  permission 변경하기 (seteuid())


seureuid라는 함수를 추가해서 계정 권한을 root로 변경할 수 있습니다.


윗부분을 추가했는데 xor연산을 통해서 각각의 레지스터들의 값을 다 0으로 만들어 줍니다. 이는 root권한으로 설정하기 위함입니다.

그런 다음 al레지스터에 setresuid 시스템 함수의 콜 번호인 164를 저장합니다. 마찬가지로 int 0x80으로 함수를 실행시켜 줍니다.

 


마찬가지로 기계어들을 모아 쉘 코드로 만들어 줍니다.

\xeb\x22\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\xa4\xcd\x80\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xd9\xff\xff\xff/bin/sh


보면 쉘 코드를 카피하는 함수 위에 seteuid(1000)으로 설정이 되어있습니다.

하지만 이를 실행시켜보면


uid=0(root)로 변경된 걸 볼 수 있습니다.

 

Discussion

일단 어셈블리어를 읽어 나가는 게 익숙하지 않아서 힘들었습니다. 계속해서 보면서 외우고 익숙해져 나가야 된다고 생각했습니다.

Shell code를 짜는 목적과 방법은 알겠지만, 이를 직접 생각해서 코딩하는 것은 차이가 있었고, 세그먼트 메모리 구조나 레지스터들의 특징들이 아직 와 닿을 만큼 이해가 되지 않았습니다.

특히 어떤 레지스터를 이용해서 코딩해야 되는지를 모르겠습니다. 위에서는 거의 범용 레지스터들을 이용했는데, 애초에 범용 레지스터이기 때문에 다양한 목적으로 쓰인다지만 그냥 이렇게 써도 되나 싶었습니다.

그리고 NULL바이트를 없애 주어야 된다는 점 이나 아직 해결하지 못한 stack_chk_fail같은 에러를 해결하는 데 어려움이 있었습니다.

참고로, 쉘코드는 취약 프로그램의 한정된 버퍼 안에서 저장되야 하는 경우가 많기 때문에 쉘코드의 사이즈가 적으면 적을수록 공격에 더 유리하다고 합니다. 실제 국외 유명 Exploit사이트인 hack.no.za에서는 가장 짧은 쉘 코드 만들기 콘테스트가 열렸을 정도로 해커들 사이에서 짧은 쉘 코드 만들기 기술은 흥미로운 주제가 되기도 합니다.


728x90
반응형

'Security > 시스템 해킹' 카테고리의 다른 글

어셈블리로 구구단 짜기(nasm)  (1) 2019.04.02
RTL (Return to Libc)  (0) 2019.02.05
리버싱 실습(Easy_ELF)  (0) 2019.01.24
버퍼오버플로우 실습  (0) 2019.01.23
어셈블리어  (0) 2019.01.10
댓글