prettify code

顯示具有 writeup 標籤的文章。 顯示所有文章
顯示具有 writeup 標籤的文章。 顯示所有文章

2019年10月23日 星期三

[Official Write-up] HITCON CTF Quals 2019 - Path of Exploitation

Path of Exploitation (PoE) are the pwn challenges I designed for this year's HITCON CTF qualification. It contains three stages/layers, hackers have to exploit a user-space program, do privilege escalation on Linux, and attack the custom device embedded in QEMU. Solve all three stages to be the master of pwn!

This challenge has a similar hierarchy as "Abyss" has, which is also my series of challenges in HITCON CTF 2018. Abyss contains three layers as well, the user-space program, the kernel, and the hypervisor, but with the self-implemented kernel and hypervisor based on KVM.

For PoE, I modified the latest QEMU source code to add a custom device, Treap Processing Unit (TPU), developed a Linux kernel module to drive my fancy device, and a simple note editor in user-space that utilize TPU to perform string operations.
I have released all the source code and my exploit scripts  for this challenge, along with the patch needed for Linux kernel and QEMU.

2018年9月5日 星期三

[Write-up] TokyoWesterns CTF 2018 - pwn240+300+300 EscapeMe

The best KVM (Kernel-based Virtual Machine) challenge I've ever seen! Thanks @shift_crops for giving such great challenge. He released the source code of EscapeMe after the contest as well.

2018年6月26日 星期二

[Write-up] Google CTF 2018 - pwn420 sandbox compat

Basic Info
This is an interesting sandbox-escaping challenge! Though I solved it after the game, still want to share how fun this challenge is, so I make this writeup.


Attachment contains the binary and source code, you can find them in my github repository.

2018年4月5日 星期四

[Write-up] 0ctf quals 2018 - pwn1000 Mighty Dragon

Glad to say that we (HITCON) are the only team solved this challenge :D
But in my opinion this challenge is more like a reversing but not a pwnable one.
This is a pwn challenge - but the pwn part is extremely easy, and the hardest part is to understand what is this binary doing. Anyway, let's see the details.




2017年11月7日 星期二

[Official Write-up] HITCON CTF 2017 - pwn327 Real Ruby Escaping

Introduction


The attachment can be found in my repository: link

Many people ask me to give this writeup because I announced that all the 11 teams used unintended solution on IRC. Now comes the intended one :D

This task asks you to exploit the ruby interpreter, given arbitrary ruby code execution but protected by seccomp rules.

2017年9月11日 星期一

[Write-up] Tokyo Westerns CTF 2017 - rev500 Steganographer Revenge

It's a long time since I last wrote a writeup for reversing challenge! Since we(217) are the only team to solve this, and it's a great challenge, I decide to do so.This challenge was solved by me and my teammate +PZ Read.

2017年6月21日 星期三

[Write-up] Google CTF 2017 - pwn474 primary

Before Start

This challenge took me 40 hours (including sleeping) to solve! One reason is I'm not familiar with race condition bugs. Main reason is because there're too many strategies (seems) can solve it. I have tried at least four kinds of exploitation and finally got the correct and stable one. I will show the final solution I got and mention why other solutions failed.

Another reason hard to solve this challenge is that race condition is difficult to debug because whenever a debugger presents, the race condition will always fail 😢

Basic Info



Challenge's attachment files can be found in my github repo.

Source code, Makefile, and binary are given.
checksec of binary:

The Makefile is important, it reveals the compile options are:
$ clang-3.8 primary.c -Wall -Wextra -std=gnu11 -lpthread -O0 -o primary -ltcmalloc

It uses tcmalloc as memory allocator which is a package in google-perftools. This link has a nice explanation of it's malloc/free mechanism. I will briefly introduce it as well and explain how to exploit this kind of heap later.

2017年4月25日 星期二

[Write-up] PlaidCTF 2017 - pwn400 Plaid Party Planning


Introduction

The challenge files can be downloaded here.

Challenge contains three files: partyplanning.strippartyplanning.dump, and relocator.py

partyplanning.strip is the main binary, with less protection:


2017年3月21日 星期二

[Write-up] 0ctf 2017 qual - pwn647 pages


Introduction

binary

This is not a normal but very interesting pwn challenge. The target of this chal is to "guess" random 64 bits.

When running binary, it'll fetch 64 random bits from /dev/urandom, and mmap 64 pages according to the following rule:
// bits = random 64 bits
void* base = 0x200000000;
for(int i=0;i<64;i++) {
  mmap(base+(2*i+bits[i]) * 0x1000, 0x1000, ...);
}

2017年3月3日 星期五

[Write-up] Boston Key Party 2017 - pwn99 Solitary Confinement

Introduction

This is a rbash jail escaped challenge.
There're are many solutions of this chal. What we used is 1-day of CVE-2016-9401.
According to the administrator, we (HITCON) are the only team solved this challenge by using this CVE :p.

I'm too lazy to introduce what this challenge doing, if you're not similar with this challenge yet, you can see this writeup with nice introduction.

Exploit Target

Final target is to execute /flag/showFlag, to do this, we want to set PATH as /flag. After that we can execute showFlag directly.

2016年12月24日 星期六

[Write-up] WhiteHat Grand Prix 2016 - pwn400 Cao lau / note_trial_1


This challenge wasn't solved by me but +winesap during the competition. 
I do this challenge off-line and the exploit has been confirmed by +winesap will work.



2016年9月5日 星期一

[Write-up] Tokyo Westerns/MMA CTF 2nd 2016 - pwn300 diary

diary 是我在這場 CTF 當中唯一做到的 pwn 題,趁其他隊友還在睡覺時撿來做的XD

這場比賽題目品質很好,種類多而且都不猜謎,難度也很恰當,打完後覺得學到不少東西的比賽最棒了!


Info



附加檔案是 ELF64 的執行檔 diary

PIE 沒開,Partial RELRO


2016年8月21日 星期日

[Write-up] BioTerra CTF 2016 - web100 sounds

這場 217 沒人打,晚上回家順手註冊個來看看題目

看了幾題題目不太難,洞都滿經典的
發這篇文是為了紀念第一次在比賽中獨力做出 Web 題 XD
以往 Web 全都給隊友,難得自己打比賽就看了一下 Web 題這樣

題目提供一個網站連結:

網站用 php 寫的,駭客最愛的語言!

2016年8月1日 星期一

[Write-up] Trend Micro CTF qual 2016 - rev300

第二屆 Trend Micro CTF,題目一如去年風格很通靈
rev300 是當中少數正常的題目了

難得在 reverse 題中拿到首殺XD,小時候寫過 ActionScript 可能還是有點幫助(?)



這題是個動畫 (SWF) 題,題目提供一個檔案(game.swf)

動畫執行畫面先是個輸入名字:


打完名字後如同檔名是個遊戲:


遊戲方式是上下左右移動人物 (綠色的 S),撞怪物可以殺怪,被怪物撞 9 次就死亡。但身為一個 reverse 題,遊戲本身肯定是不重要的,因此先找工具對動畫進行反編譯。

我使用的反編譯工具是 open source 的 JPEXS Free Flash Decompiler,賽中比較過其他工具但以這個解出來的最完整。

decompile 出來的目錄結構長得像這樣:

game
├── binaryData
│   ├── 1__el_-__-_--.bin
│   ├── 2__el_-__--__.bin
│   ├── 3__el_-_-___-.bin
│   ├── 4__el_-__--_-.bin
│   ├── 5__el_-__---_.bin
│   ├── 6__el_-__----.bin
│   ├── 7__el_-___--_.bin
│   └── 8__el_-__-__.bin
├── frames
│   ├── 1.png
│   └── 2.png
├── scripts
│   ├── KCS___Main.as
│   ├── mx
│   │   └── core
│   │       ├── ByteArrayAsset.as
│   │       ├── IFlexAsset.as
│   │       └── mx_internal.as
│   ├── §_el_-----_§.as
│   ├── §_el_--_--_§.as
│   ├── §_el_-_-___-§.as
│   ├── §_el_-_-____§.as
│   ├── §_el_-__----§.as
│   ├── §_el_-__---_§.as
│   ├── §_el_-__--_-§.as
│   ├── §_el_-__--__§.as
│   ├── §_el_-__-_--§.as
│   ├── §_el_-__-__§.as
│   ├── §_el_-__-_§.as
│   ├── §_el_-___--_§.as
│   └── §_el_-____-§.as
└── symbols.csv


scripts/ 中的那堆噁心檔名(.as) 就是主要的動畫函式,ActionScript 看起來跟 Java 有 7 成像,基本上閱讀不會有困難。不過變數名字跟檔名都被混淆過,可以手動或自動反混淆一下比較方便。

在 *.as 裡兜來兜去會發現感受不到任何一段程式碼在實作輸入名字或玩遊戲的地方。覺得必定有詐時注意到其中有幾個 .as 檔都只有簡單的幾行:
package
{
   import mx.core.ByteArrayAsset;
   
   public class §_el_-___--_§ extends ByteArrayAsset
   {
      public function §_el_-___--_§()
      {
         super();
      }
   }
}

接著發現這幾個檔案的檔名剛好會一一對應到 binaryData 底下的檔案們,因此猜測這幾個 class 應該是會分別 load 那些 bin 檔案成為 ByteArray。

file 一下 binaryData 的檔案們:
$ file *
1__el_-__-_--.bin: JPEG image data, JFIF standard 1.01
2__el_-__--__.bin: PNG image data, 50 x 50, 8-bit/color RGBA, non-interlaced
3__el_-_-___-.bin: PNG image data, 50 x 50, 8-bit/color RGBA, non-interlaced
4__el_-__--_-.bin: PNG image data, 50 x 50, 8-bit/color RGBA, non-interlaced
5__el_-__---_.bin: XML  document text
6__el_-__----.bin: PNG image data, 50 x 50, 8-bit/color RGBA, non-interlaced
7__el_-___--_.bin: data
8__el_-__-__.bin:  data

前幾個圖檔與 XML 感覺就不是很重要。 7 號檔案長度 16 byte,8 號檔案長度 344kb,直覺就覺得肯定是拿 7 號當 key 去解 8 號檔案會得到個什麼。於是就仔細去看程式碼中有用到 7 號與 8 號的部分,果然找到了一段在進行 decode 的程式碼(§_el_-__-_§.as):
public function §_el_-_-_-_§(param1:int) : void
{
    var _loc2_:int = 0;
    while(_loc2_ < param1 && this.§_el_-______§ < this.§_el_-_-__§.length)
    {
        this.§_el_-_-__§[this.§_el_-______§++] =
            this.§_el_-_-__§[this.§_el_-______§++] ^ this.§_el_----_-§();
        _loc2_++;
    }
}

有個 xor 看起來就超可疑XD所以其實滿容易感受到這裏在 decode 的。認真看 code 之後總之大約是個用 bin7 當作 random_shuffle 的 seed,產出 xor_key 之後跟 bin8 xor。照樣寫個一樣的 decoder 就能解出 bin8 真正的樣子 --






另一個動畫檔。


但其實也不是很意外,不然要怎麼弄出那個遊戲畫面XD

於是就開拓了另一個新世界,先來把 bin8.swf 丟 decompile T__T

目錄結構:
bin8
├── binaryData
│   ├── 1__e_--_-.bin
│   ├── 2__e_-----.bin
│   └── 3__e_-_-_--.bin
├── frames
│   └── 1.png
├── scripts
│   ├── mx
│   │   └── core
│   │       ├── ByteArrayAsset.as
│   │       ├── IFlexAsset.as
│   │       └── mx_internal.as
│   ├── §'§.as
│   ├── §_e_-----_§.as
│   ├── §_e_-----§.as
│   ├── §_e_--_--§.as
│   ├── §_e_--_-_§.as
│   ├── §_e_--_-§.as
│   ├── §_e_--_§.as
│   ├── §_e_-_---_§.as
│   ├── §_e_-_--_§.as
│   ├── §_e_-_-_--§.as
│   ├── §_e_-__-_-§.as
│   └── §_e_-____--§.as
└── symbols.csv

正當以為檔案少一些應該好多了的時候真正的痛苦就開始了
點開 *.as 會看到各種💩一般的慘 code



不過出題者很好心地留下了一些 Message:

public function §_e_--___-§() : String
{
    return "aes" + 8 * this.§_e_---__§;
}

大約猜得出來是在 Implement AES 解密吧。

跟下去究竟哪裡在玩 AES 解密之後就發現他是在對前 2 個 binaryData 做各種運算與解密。這部分就比較容易看懂了,大略是在把 bin2 每 16 個字元切開依序當 AES-ECB 的 key 對依某種方式(不贅述)切開的 bin1 解密,script 如下

#!/usr/bin/ruby
#encoding: ascii-8bit
require 'openssl'

$data1 = IO.binread('1__e_--_-.bin')[4..-1]
data2 = IO.binread '2__e_-----.bin'
$bin2_scan_16 = data2[1..-1].chars.each_slice(16).map(&:join)
$decoded = []
def bin2_scan_16;$bin2_scan_16;end

def decode(data, key)
  decipher = OpenSSL::Cipher::AES.new(128, :ECB)
  decipher.decrypt
  decipher.key = key
  plain = decipher.update(data) + decipher.final
  $decoded.push(plain)
end

def work
  data1 = $data1
  len = 27
  j = 0
  while(j < len)
    length = data1[0, 4].reverse.unpack("L*")[0]
    decode(data1[4, length], bin2_scan_16[j % bin2_scan_16.size])
    data1 = data1[(length+4)..-1]
    j+=1
  end
end

work
puts $decoded

Output 結果為:
70d4a3aa
, Game Over :(
http://www.trendmicro.com/us/about-us/careers/portal/index.html

6e415968
, You're too leeeeeeeeet
####
1337
PRESS ANY KEY TO RESTART
1fac0e9c
555555
....
Hey,
Welcome, Input Your Name and Have Fun!
417cfdf3
FFFF
I think you got the flag :)
33
MMMM
02f02daa
center
6962723d
YOUR SCORE:
ad2a75a9
007
6ee2c371
Sorry,
Hope you can join TrendMicro :)

解出了程式碼中一直在用到的 constant 字串們。當中最吸睛的當然是 I think you got the flag :) 這句,回追哪裡用到這個字串後找到底下這段:

if(this.§?§ >= parseInt("1337"))
{
    _loc4_.appendText("Hey, " + this.username + ", You're too leeeeeeeeet\n");
    _loc4_.appendText("Hope you can join TrendMicro :)\n");
    _loc4_.appendText("http://www.trendmicro.com/us/about-us/careers/portal/index.html\n\n");
    _loc5_ = this.§@§(["6e415968","6ee2c371","02f02daa","417cfdf3"]);
    _loc6_ = this.§@§(["6962723d","ad2a75a9","70d4a3aa","1fac0e9c"]);
    if(Boolean(this.§3§(this.§8§,this.username,_loc5_)) && 
       Boolean(this.§3§(this.§7§,this.username,_loc6_)))
    {
        _loc4_.appendText("I think you got the flag :)\n");
    }
}

(constant 字串都是手動取代過了,原本都長得像是 §_e_-----_§.§_e_-_-__-§(-1820302803) 般的鬼東西)
條件是在遊戲通關 1337 次後會印出訊息。要印出 "I think.." 需要讓一開始輸入的 username 經過兩個函式運算後都回傳 true。兩個函式解釋起來有點麻煩... 總之我們最後用 z3py (Theorem Prover) 來解什麼樣的 username 會滿足條件,即可獲得 flag。對 z3 不太熟 T_T,外包給隊友。
from z3 import *

def fun_8(s):
    _loc4_ = BitVecVal(5592405, 32)
    for i in xrange(len(s)):
        c = s[i]
        _loc4_ = _loc4_ ^ ZeroExt(24, c)
        _loc4_ = RotateLeft(_loc4_, 7)
        _loc4_ = simplify(_loc4_)
    return _loc4_

def fun_7(s):
    _loc4_ = BitVecVal(1337, 32)
    for i in xrange(len(s)):
        c = s[i]
        _loc4_ = _loc4_ * 33 + ZeroExt(24, c)
        _loc4_ = simplify(_loc4_)
    return _loc4_

solver = Solver()

ans1 = [0x6ee2c371, 0x02f02daa, 0x417cfdf3]
ans2 = [0xad2a75a9, 0x70d4a3aa, 0x1fac0e9c]

n = 22
flag = [BitVec('x%d' % i, 8) for i in xrange(n)]

known = 'TMCTF{'
for i in xrange(len(known)):
    solver.add(flag[i] == ord(known[i]))

for i in xrange(6, len(flag) - 1):
    x = flag[i]
    #solver.add(Or(And(48 <= x, x <= 57), And(97 <= x, x <= 122)))
    solver.add(And(32 <= x, x <= 126))

for i, val1, val2 in zip(xrange(0, len(flag), 8), ans1, ans2):
    s = flag[i:i+8]
    res = fun_8(s)
    solver.add(res == val1)
    res = fun_7(s)
    solver.add(res == val2)

print solver.check()

m = solver.model()

s = ''
for x in flag:
    if str(m[x]) == 'None': continue
    c = chr(int(str(m[x])))
    s += c
    print c
print s

最後有一點小雷是其實看不太出來 flag 長度應該要多少,因為程式中有把 username 每 8 個 byte 切開做運算,因此猜長度 24,但 z3 一直表示無解。最後把長度隨意改成 22 後就找到了。

Flag: TMCTF{f1ash_s0_4un!!!}