IT 그리고 정보보안/Knowledge base

Shellcode 작성

plummmm 2021. 4. 15. 21:24
반응형

저번 포스팅에서 기계어코드 작성하는 법을 익혔다.

이제 그걸 토대로 실제 해킹 공격에 쓰이는 셸코드작성을 한번 해보겠음.

기계어 코드 작성과 별반 다를게 없다.

 

일단 먼저 C프로그램을 작성하자. 셸이 실행되는 프로그램을 작성하면 된다.

 

 

시스템 명령어를 실행하기 위해 execve()함수를 사용하였다.

system()도 있고 execl() 함수도 있는데 왜 execve()를 쓴걸까?

앞서 포스팅한 기계어 코드 작성에서 printf를 쓰지않고 write를 사용한 맥락과 일맥상통한다.

execve가 시스템콜 함수이기 때문.

 

셸코드를 작성할때 시스템콜 함수를 써야 되는 건 저번 포스팅에서 언급했었다.

(기계어 작성 포스팅을 공부한 후에 이걸 본다고 가정하겠음)

 

이걸 가지고 이제 셸코드를 작성해보자.

저번에는 어셈 문법을 AT&T로 했는데, 내가 배울 때 Intel로 배워서 너무 헷갈려서.. 인텔로 하겠음

 

 

번잡한 과정을 모두 떼버리고 바로 execve 함수 디스어셈블한 코드를 보자.

여기서 골라내어야 한다. 핵심 코드를..

이글을 보는 님들도 한번 생각해보라고 일부러 표시하지 않았음.

 

일단 가장 먼저 생각해야 하는 것이, exeve함수의 인자 전달이 올바르게 되냐를 체크해야한다.

시스템콜 함수들은 대게 eax, ebx, ecx, edx에 인자를 저장한다.

이 레지스터들 안에 인자가 저장되는 부분(eax에는 시스템콜 함수 번호), 인터럽트 부분

두가지를 꼭 셸코드 작성시에 넣어야 한다고 생각하고 찾아보면,,

 

 

이렇게 나온다.

eax에 0xb 시스템콜 함수 번호 들어갔고,

ebx에 '/bin/sh' 로 추정되는 내용이 들어갔고..( edi에 한번 넣었다가 빼는걸 보면 문자열이라고 짐작가능.)

ecx, edx에도 값이 들어가 있고

int 0x80 인터럽트 명령도 있고.

 

레지스터들의 값이 정확하게 알고 싶다면 break point를 걸어서 #info reg 명령어로 레지스터를 확인해보면 된다.

요래요래 해서 어셈블리 코드를 작성해보자.

 

위에서 셸코드 작성할 때 참고해야 할 점들을 열거했는데, 기계어코드랑 다르게 셸코드 작성할 때는 몇가지 더 고려해야한다.

일단 포인터배열의 ['bin/sh'의 주소][0]의 주소를 넣어야한다.

각설하고 어셈 코드를 작성해보겠다.

 

 

다른 부분은 이미 기계어코드 작성 포스팅시에 설명했고,

movl %esp,%esi

movl %ebx,(%esi)

movl $0x00,, 0x4(%esi)

leal (%esi), %ecx

 

이부분이 앞서 작성했던 부분과 다르다.

먼저 execve 함수가 인자 3개를 갖는걸 기억하자.

(문자열, 문자열시작주소, null)

 

popl 명령 라인까지 수행되었다고 생각하면,

eax에 시스템콜 함수 번호가,

ebx에 문자열 시작주소가 들어가 있을 것이다.

 

movl %esp,%esi​ : esi 레지스터를 초기화 시키는 부분이다.

movl %ebx,(%esi) : ​esi안의 주소값 즉, esp 주소에 ebx의 값을 복사한다. 즉, 문자열의 시작주소를 복사한다.

movl $0x00,, 0x4(%esi) : ​인텔로 치면,[esp-4] 주소에 0x00 값을 복사한다.

 

위 3가지 movl 명령은 스택에 문자열을 쌓는 부분이다.

그다음,

 

leal (%esi), %ecx : ​esi에 든 주소값 즉, 문자열의 시작주소를 ecx레지스터에 저장한다.

 

그 뒤에 명령들이 수행되고 나면 execve함수에 들어갈 인자3개, 시스템콜 함수 번호, 인터럽트 번호

코드를 실행시키기 위한 모든 것이 완성된다.

(func의 아래 3줄은 exit 함수 코드이다.)

 

언뜻 보기엔 완성된 코드 같지만 아니다.

 

objdump를 떠보면 00 00 00 , 널 문자가 많이 보이는걸 볼 수있다.

셸코드에 null문자가 있다면 문제가 된다. 왜그럴까?

우리가 BOF 공격을 할 때 노리는 함수들은 주로 문자열 관련된 함수들 strcpy, strcmp 등등이다.

문자열의 끝은 뭘로 판단하는가. 그래 NULL 이다.

 

근데 셸코드 중간에 떡하니 NULL이 있다면 코드가 끝까지 옳게 쓰이지 않을 것이다.

그래서 우리는 NULL 문자 (\x00)를 박멸해야한다.

 

null을 없애기 전에 하나만 알고 넘어가자.

어셈 코드 작성할 때 mov 뒤에 l 을 붙였었다.

이건 longword 를 뜻하고 4바이트이다.

l 외에 w와 b도 있다.

w는 2바이트

b는 1바이트 이다.

movw 를 하면 2바이트만 mov명령을 수행

movb를 하면 1바이트만 mov 명령을 수행하는 것이다.

 

이 얘기를 왜 했냐면..

func의 맨 첫줄 movl $0x0b, %eax 를 보면

꼴랑 1바이트를 쓰면서 eax, 4바이트 짜리 레지스터를 쓰고 있다.

이러니 null이 안생기고 베겨?

 

movl $0x0b, %eax ​요 놈을 movb $0x0b, %al

로 바꿔준다. 

 

그리고 한가지 더 xor을 이용해서 NULL을 제거하는 방법이 있다.

이건 movl $0x00 %ebx 같은 명령에서 사용되는데,

위에 한거 처럼 movb $0x00 %bl 을 해봐야 0x00이 그대로 들어가므로 여전히 NULL이 생긴다.

 

이 때 사용하는 방법이 'XOR 연산'이다.

XOR연산은 서로 다를 때만 참을 반환한다. 즉, 두 값이 같으면 거짓(null)을 반환하다는 말.

어셈블리에서 XOR 연산을 하면 A와 B를 XOR연산하고 값을 저장한다.

그렇다면 movl %eax, %eax를 한다면 eax 레지스터에 0이 들어 있을 터,

그럼 굳이 0x00을 직접 넣어 셸코드에 NULL이 생기는 현상을 겪지 않고 eax레지스터에 0을 넣을 수 있다.

 

이렇게 두가지 방법으로 NULL을 없애는 방법이 있고,

또 하나는 레지스터 하나를 통째로 빌려서 NULL을 제거 해주어야 할 때도 있다.

바로 movl $0x00,, 0x4(%esi) ​이런 부분

xor 연산을 독자적으로 수행할 수 없는 이런 부분이다. xor 0x4(%esi), 0x4(%esi) ​이런 연산은 문법에 맞지않다.

이 부분은 esp 레지스터를 빌려와서 수행해야 한다.

 

xor %esp, %esp

movl %esp, 0x4(%esi)

 

요렇게.

어차피 이 아래 코드들은 esp가 필요하지 않아서(딱히 POP연산이 있는 것도 아니고)

esp를 빌려와서 NULL을 제거하면 된다.

 

방금 위에서 말한 3가지 테크닉을 이용하여 null 문자를 없애보자.

 

이렇게 생겼다. objdump로 NULL이 잘 없어졌는지 확인해보자.

오 없다.

 

완성된 셸코드는

\xeb\x18\xb0\x0b\x5b\x89\xe6\x89\x1e\x31\xe4\x89\x66\x04\x8d\x0e\x31\xd2\xcd\x80\xb0\x01\x31\xdb

\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68

반응형

'IT 그리고 정보보안 > Knowledge base' 카테고리의 다른 글

Omega Project  (0) 2021.04.15
RTL (Return-to-Libc) Attack  (0) 2021.04.15
기계어코드 작성  (0) 2021.04.15
버퍼 오버플로우 (Buffer Overflow) 개요  (0) 2021.04.15
Metasploit - Meterpreter  (0) 2021.04.15