0x01 Files
The challenge has two attached files, a x86_64 binary steganographer_revenge and an image file invitation.png. You can find this two files in my github repo.The goal of solving this challenge is clear. The binary hide some data in the image(i.e. steganography), we need to reverse what the binary did and extract data from the given image.
The usage of binary is:
The binary needs libpng12.so.0 for parsing a png file, you can install it via apt (ubuntu 16.04) or compile from source.
0x02 Encode
The binary do lots of things, but after some analysis we have the outline:- convert input image's RGB into YUV
- do Discrete Cosine Transform(?) on Y dimension, named it Y*
- convert message data from 0/1 bits to -1/1, named it D
- construct a constant matrix V with value -1/0/1
- do scalar multiplication (details later) on V according to D, named U
- calculate a linear combination of rows of U and assign to part of Y*
- do inverse DCT(?) on Y*
- convert YUV back to RGB and save to output image
Transform
I have a question mark in step 2 and 7 because, well, we're still not sure what the transformation is(!). Since it's common to do DCT in image watermarking techniques, we guess it's DCT. But what it actually is doesn't matter. When we want to reverse step 7 and 8, only need to util the binary self to execute step 1 and 2 with output image as input. So forget whatever the transformation is and move to details of those vector operations of step 3 ~ 6.
Details
Use an example would be much clear. Let input data be one-byte string, 'A'.step 3
Convert 'A' to binstring, '01000001', then we will have D = [-1,1,-1,-1,-1,-1,-1,1].
step 4
The matrix V is constructed as follows:
- construct a constant array v with value -1/0/1
- V[i] = [0] * i + v + [0] * (len(D) - i - 1) for i in range(len(D))
So the matrix V would have dimension len(D) x (len(v)+len(D)-1) and look like this:
v 0 0 0 0 0 0 0
0 v 0 0 0 0 0 0
V = 0 0 v 0 0 0 0 0
...
0 0 0 0 0 0 0 v
Notice that v is a vector but not a scalar.
We don't know how the array v is constructed, but its value and dimension are only affected by length of input data.
We found an important property of V, which will be described in 0x03 Decode.
We don't know how the array v is constructed, but its value and dimension are only affected by length of input data.
We found an important property of V, which will be described in 0x03 Decode.
step 5
This step is simple: U[i] = V[i] * D[i] for i in range(len(D)).
Since D=[-1,1,-1,-1,-1,-1,-1,1], then we have
-v 0 0 0 0 0 0 0
0 v 0 0 0 0 0 0
U = 0 0 -v 0 0 0 0 0
...
0 0 0 0 0 0 0 v
step 6
Let E = Σ U[i]*alpha[i] for i in range(len(D))
where alpha are some positive values calculated from Y* and U but not important when decoding.
E will be a floating array with length (len(v)+len(D)-1).
Finally, assign E to part of Y* and do the remained transformations.
First extract the transformed Y* from the image. I put the dumped result in my repo named dump_y. And it's easy to extract E from Y*.
E will be a floating array with length (len(v)+len(D)-1).
Finally, assign E to part of Y* and do the remained transformations.
0x03 Decode
Let's decode the message!First extract the transformed Y* from the image. I put the dumped result in my repo named dump_y. And it's easy to extract E from Y*.
Property of V
We found that each rows in V is orthogonal. That is, for all i != j, V[i].V[j] = 0.
With this property, decoding will be very easy. See again definition of E: Σ U[i]*alpha[i]
Calculate the dot product of E and V[j] acquires
V[j].Σ U[i]*alpha[i]
= Σ(V[j].U[i])*alpha[i]
= (V[j].U[j])*alpha[j]
= D[j]*(V[j].V[j])*alpha[j]
= D[j]*POSITIVE_NUMBER
Since alpha are positive values, the sign of dot product of E and V[j] equals to sign of D[j]! Which is our desired message.
All we need is guess the length of message (D), and dump corresponding v from memory to decode the message.
0x04 Get Flag
We dumped v vector according to different message length from memory. Then try the decode method and see which one is reasonable.
The correct input length is 280 (so len(D)=280*8), corresponding value of v also in my repo.
Decode script:
Run it and wait for about 6 seconds can recover the original message:
Flag: TWCTF{Spr34d_Sp3ctrum_1s_m0r3_u53fu1_f0r_st3g4n0gr4phy}
The correct input length is 280 (so len(D)=280*8), corresponding value of v also in my repo.
Decode script:
#!/usr/bin/env ruby # encoding: ascii-8bit N = 512 ystar = IO.binread('dump_y').split.map(&:to_f) len = 280 value = IO.binread("values/280.dump").split.map(&:to_i) # vector v e = [] N.times do |i| N.times do |j| e << ystar[i * N + j] if i + j >= N / 2 end end result = '' 0.upto(8 * len - 1) do |i| sym = [0] * i + value + [0] * (len * 8 - 1 - i) # dot product < 0 ? 0 : 1 result << (sym.zip(e[0, sym.size]).reduce(0.0) { |s, (a, b)| s + a * b } < 0 ? '0' : '1') end p result.scan(/.{8}/).map { |v| v.to_i(2)}.pack("C*")
Run it and wait for about 6 seconds can recover the original message:
Dear CTFers, We are holding Tokyo Westerns CTF 3rd 2017 from September 2nd to September 4th. This is a security competition hosted by Tokyo Westerns. We would like to invite you to this CTF. P.S. This is my present for you. TWCTF{Spr34d_Sp3ctrum_1s_m0r3_u53fu1_f0r_st3g4n0gr4phy}.
沒有留言:
張貼留言