函数调用与返回
栈的概念在PPC等CPU中,不是由CPU实现的,而是由编译器维护的。通常情况下,在PPC中栈顶指针寄存器使用r1,栈底指针寄存器使用r11或r31。或者r11为栈顶,其他为栈底。根据不同的编译选项和编译器环境,其使用方式都有不同,但各个编译器的共识为r1是帧栈指针,其他寄存器都可根据他为准灵活使用。
栈帧在函数中,通常用于存储局部变量、编译器产生的临时变量等。由于PPC和ARM等CPU在寄存器较多,所以函数的形参和实参大多数情况下会使用寄存器,参数较多的情况下使用栈。
PowerPC体系结构中栈的增长方向同样是从高地址到低地址,堆的增长方式是从低地址到搞地址,当两者相遇时就会产生溢出。
可以看到在进入函数的时候会先执行
r1就类似栈顶指针,第一条指令中,stwu最后的u表示update,指令中有效地址EA=r1+back_chain,该指令首先会将r1的值存放到EA中,接着会把有效地址EA存到r1里。back_chain对应新栈帧大小,是一个负值,此处为0x60,所以这里实际上就是开辟了一块新的栈帧,让r1指向新栈顶,同时在新栈顶处存储了上一个栈帧的栈顶,从而构成一个类似链表的东西,在之后帮助恢复栈帧。
mflr r0,把lr寄存器的值保存到r0中。接着stw将r0保存到栈上,从而在栈上保存了lr返回地址的值。指令中对栈变量的索引使用的是0x60+sender_lr(r1),r1已经指向新栈帧的栈顶,所以这里是通过栈顶指针索引栈上的局部变量,栈帧空间大小即0x60。
下一条stw指令将r31存储到栈上,然后执行mr把r1的值赋给r31。
由于test函数传入了一个参数n,是通过r3传递的。所以在之后看到首先把r3存到了栈上,接着不断连续调用lwz和stw指令,以r9为中间量,并通过r31索引,对栈上局部变量进行赋值。
接下来就是为函数调用布置参数了,这里由于我们使用的参数很多,会同时使用寄存器和栈变量进行传参。ppc中没有push、pop这样的指令,栈帧空间是提前设置好的,这里指令做的就是把参数从右往左把多出来的4个参数依次在栈上从高地址往低地址放置,第9个参数与栈顶位置中间还会留下一个字长的空间,用来存放下一个栈帧的返回地址;剩下的8个参数按照从右往左依次放入r3~r10中,指定执行时是从r10开始存放的。crclr是用来调整条件寄存器CR的。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
栈的概念在PPC等CPU中,不是由CPU实现的,而是由编译器维护的。通常情况下,在PPC中栈顶指针寄存器使用r1,栈底指针寄存器使用r11或r31。或者r11为栈顶,其他为栈底。根据不同的编译选项和编译器环境,其使用方式都有不同,但各个编译器的共识为r1是帧栈指针,其他寄存器都可根据他为准灵活使用。
栈帧在函数中,通常用于存储局部变量、编译器产生的临时变量等。由于PPC和ARM等CPU在寄存器较多,所以函数的形参和实参大多数情况下会使用寄存器,参数较多的情况下使用栈。
PowerPC体系结构中栈的增长方向同样是从高地址到低地址,堆的增长方式是从低地址到搞地址,当两者相遇时就会产生溢出。
// powerpc-linux-gnu-gcc -static -g -o t t.c
#include <stdio.h>
unsigned int test(unsigned int n)
{
int ok,n1,n2,n3,n4,n5,n6,n7,n8,n9;
ok=n1=n2=n3=n4=n5=n6=n7=n8=n9=n;
printf("%d%d%d%d%d%d%d%d%d%d%d",ok,n,n1,n2,n3,n4,n5,n6,n7,n8,n9);
return n;
}
void main(void)
{
unsigned int n;
n=test(n);
}
可以看到在进入函数的时候会先执行
r1就类似栈顶指针,第一条指令中,stwu最后的u表示update,指令中有效地址EA=r1+back_chain,该指令首先会将r1的值存放到EA中,接着会把有效地址EA存到r1里。back_chain对应新栈帧大小,是一个负值,此处为0x60,所以这里实际上就是开辟了一块新的栈帧,让r1指向新栈顶,同时在新栈顶处存储了上一个栈帧的栈顶,从而构成一个类似链表的东西,在之后帮助恢复栈帧。
mflr r0,把lr寄存器的值保存到r0中。接着stw将r0保存到栈上,从而在栈上保存了lr返回地址的值。指令中对栈变量的索引使用的是0x60+sender_lr(r1),r1已经指向新栈帧的栈顶,所以这里是通过栈顶指针索引栈上的局部变量,栈帧空间大小即0x60。
下一条stw指令将r31存储到栈上,然后执行mr把r1的值赋给r31。
由于test函数传入了一个参数n,是通过r3传递的。所以在之后看到首先把r3存到了栈上,接着不断连续调用lwz和stw指令,以r9为中间量,并通过r31索引,对栈上局部变量进行赋值。
接下来就是为函数调用布置参数了,这里由于我们使用的参数很多,会同时使用寄存器和栈变量进行传参。ppc中没有push、pop这样的指令,栈帧空间是提前设置好的,这里指令做的就是把参数从右往左把多出来的4个参数依次在栈上从高地址往低地址放置,第9个参数与栈顶位置中间还会留下一个字长的空间,用来存放下一个栈帧的返回地址;剩下的8个参数按照从右往左依次放入r3~r10中,指定执行时是从r10开始存放的。crclr是用来调整条件寄存器CR的。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
首先测一下缓冲区长度,直接莽然后就能控制lr地址了
动态调试
和调试mips、arm的方法类似。执行qemu-ppc-static -g 1234 -L ./ ./PPPPPPC即可在1234端口开启调试
gdb-multiarch加载的脚本如下,其中架构设置为powerpc:403
直接用powerpc-linux-gnu-gcc编译出来然后放进ida里看看,发现这最后系统调用的sc指令就对应的是0x44000002,然后把shellstorm那份shellcode的最后sc处改成0x44000002就通了。也就是说这题在本质上就是在栈上输入shellcode然后返回地址控制跳到shellcode上即可
因为ppc属于精简指令集,指令长度固定四字节,qemu模拟的好像一般地址都不变,所以这里赋值r3考虑直接通过计算得到字符串位于栈上的地址。另外编译的时候说是赋值范围要在0xffffffffffff8000到0x0000000000007fff,无法一步到位,所以分成两步加法就行。
shellcode如下,后面再跟上/bin/sh,过程其实也就是清空相应的寄存器,r0赋值成execve的系统调用号,r3作为函数第一个参数让它指向binsh即可,最后sc就是系统调用
当然这里也能写出更加通用的shellcode,由于我们覆盖了lr,所以我们就可以直接通过lr来索引到字符串地址
简单来说add能够申请一个size小于0x91的chunk并写入信息作为book description,会有一个bookname与之一一对应,索引也使用name来索引,最多只能有三个book,bookname、book description和description的size均作为全局变量记录
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
动态调试
和调试mips、arm的方法类似。执行qemu-ppc-static -g 1234 -L ./ ./PPPPPPC即可在1234端口开启调试
gdb-multiarch加载的脚本如下,其中架构设置为powerpc:403
from pwn import *
import sys
context.log_level = 'debug'
context.endian = 'big'
context.arch='powerpc'
Debug = sys.argv[1]
elf = ELF('./PPPPPPC')
def get_sh(other_libc=null):
if Debug == '1':
return process(["./qemu-ppc-static","-g","1234","-L","./","./PPPPPPC"])
log.info('Please use GDB remote!(Enter to continue)')
pause()
return r
else:
return process(["./qemu-ppc-static","-L","./","./PPPPPPC"])
r = get_sh()
r.recvuntil("Tell me your name: ")
直接用powerpc-linux-gnu-gcc编译出来然后放进ida里看看,发现这最后系统调用的sc指令就对应的是0x44000002,然后把shellstorm那份shellcode的最后sc处改成0x44000002就通了。也就是说这题在本质上就是在栈上输入shellcode然后返回地址控制跳到shellcode上即可
因为ppc属于精简指令集,指令长度固定四字节,qemu模拟的好像一般地址都不变,所以这里赋值r3考虑直接通过计算得到字符串位于栈上的地址。另外编译的时候说是赋值范围要在0xffffffffffff8000到0x0000000000007fff,无法一步到位,所以分成两步加法就行。
shellcode如下,后面再跟上/bin/sh,过程其实也就是清空相应的寄存器,r0赋值成execve的系统调用号,r3作为函数第一个参数让它指向binsh即可,最后sc就是系统调用
当然这里也能写出更加通用的shellcode,由于我们覆盖了lr,所以我们就可以直接通过lr来索引到字符串地址
简单来说add能够申请一个size小于0x91的chunk并写入信息作为book description,会有一个bookname与之一一对应,索引也使用name来索引,最多只能有三个book,bookname、book description和description的size均作为全局变量记录
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
漏洞利用
前期的利用就是通过不断用不同大小add和delete填充堆空间,溢出修改size从而造出unsorted bin并得到libc基址,然后通过溢出修改tcache的fd就能任意地址申请了。
在这里由于架构原因泄露出来的基址的计算和平时略有不同,不过计算方法也很简单,在文件所在目录下启动gdb-multiarch,就能直接在其中打印找到__malloc_hook地址位置,再进行索引即可得到libc基址
接着,按照一般的想法只需要劫持__free_hook,写入system地址,然后就能执行system("/bin/sh")getshell了。然而在如此想法下尝试了一下却发现走到了0xdeadbeef。重新看一下给的libc文件,发现里面的execve和system都被修改了
也就是说通过调用函数getshell的路都走不通了
但是,由于题目远程大概率也是用qemu-user模拟的,是可以直接执行shellcode,所以只要通过__free_hook控制执行流后跳转到我们写的shellcode上即可完成getshell
shellcode编写
尝试自己编写shellcode。这里和32位的ppc是类似的,具体实现时针对r3寄存器的设置我用了三种思路
要执行execve("/bin/sh", NULL, NULL),r3要指向对应字符串,可以将/bin/sh写到堆上然后让r3指过去。在调试的时候发现寄存器r9就是一个堆上的地址,所以第一种思路就是直接通过异或把值迁移到r3上再进行相应偏移的调整即可
shellcode做的就是首先清空r3、r4、r5寄存器,将r0赋值为11对应execve,然后通过异或和加法让r3指向/bin/sh,最后sc即执行系统调用
第二种思路是把地址拼出来,需要用到移位操作
addis是将计算结果左移2字节后再进行存储,sld是左移双字的意思。同时由于限制操作数的大小所以低两个字节需要两次加法得到
第三种思路是最通用的,本题由于我们无法控制lr寄存器,所以无法像上一道题目通过lr索引到/bin/sh。然而我们可以换一种思路,即通过shellcode在某个可写地址写入/bin/sh然后指向即可。在所有寄存器中,我们知道r1永远是指向栈空间的,而栈空间必然是可写的,所以不妨借由r1在栈上写入/bin/sh,从而确保r3参数的布置。
最终exp如下,本题思路简单来说就是首先通过不断编辑造成足够多字节的堆溢出,然后修改chunk头的size位造成堆块重叠,从而得到unsorted bin并泄露基址;之后再攻击tcache的fd劫持__free_hook,跳转到布置在堆空间上的shellcode从而getshell。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
前期的利用就是通过不断用不同大小add和delete填充堆空间,溢出修改size从而造出unsorted bin并得到libc基址,然后通过溢出修改tcache的fd就能任意地址申请了。
在这里由于架构原因泄露出来的基址的计算和平时略有不同,不过计算方法也很简单,在文件所在目录下启动gdb-multiarch,就能直接在其中打印找到__malloc_hook地址位置,再进行索引即可得到libc基址
接着,按照一般的想法只需要劫持__free_hook,写入system地址,然后就能执行system("/bin/sh")getshell了。然而在如此想法下尝试了一下却发现走到了0xdeadbeef。重新看一下给的libc文件,发现里面的execve和system都被修改了
也就是说通过调用函数getshell的路都走不通了
但是,由于题目远程大概率也是用qemu-user模拟的,是可以直接执行shellcode,所以只要通过__free_hook控制执行流后跳转到我们写的shellcode上即可完成getshell
shellcode编写
尝试自己编写shellcode。这里和32位的ppc是类似的,具体实现时针对r3寄存器的设置我用了三种思路
要执行execve("/bin/sh", NULL, NULL),r3要指向对应字符串,可以将/bin/sh写到堆上然后让r3指过去。在调试的时候发现寄存器r9就是一个堆上的地址,所以第一种思路就是直接通过异或把值迁移到r3上再进行相应偏移的调整即可
shellcode做的就是首先清空r3、r4、r5寄存器,将r0赋值为11对应execve,然后通过异或和加法让r3指向/bin/sh,最后sc即执行系统调用
第二种思路是把地址拼出来,需要用到移位操作
addis是将计算结果左移2字节后再进行存储,sld是左移双字的意思。同时由于限制操作数的大小所以低两个字节需要两次加法得到
第三种思路是最通用的,本题由于我们无法控制lr寄存器,所以无法像上一道题目通过lr索引到/bin/sh。然而我们可以换一种思路,即通过shellcode在某个可写地址写入/bin/sh然后指向即可。在所有寄存器中,我们知道r1永远是指向栈空间的,而栈空间必然是可写的,所以不妨借由r1在栈上写入/bin/sh,从而确保r3参数的布置。
sc3='''
li 4, 0
li 5, 0
li 0, 11
li 6, 0
addis 6, 6, 0x2f62
addi 6, 6, 0x696e
stw 6, 0(1)
li 6, 0
addis 6, 6, 0x2f73
addi 6, 6, 0x6800
stw 6, 4(1)
mr 3, 1
sc
'''
最终exp如下,本题思路简单来说就是首先通过不断编辑造成足够多字节的堆溢出,然后修改chunk头的size位造成堆块重叠,从而得到unsorted bin并泄露基址;之后再攻击tcache的fd劫持__free_hook,跳转到布置在堆空间上的shellcode从而getshell。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
探索Windows内核系列——句柄,利用句柄进行进程保护
方便在R3查看句柄信息的小工具
我们可以使用微软官方的工具Process Explorer来查看文件或进程被哪个进程使用着,它的官方文档在这里。
比如我使用CE打开了notepad.exe,使用Process Explorer查找句柄功能,搜索notepad.exe,即可找到对应占用它的进程。这在我们想删除一个文件却提示"该文件已被占用"时非常有用,可以直接搜索到占用它的进程。
句柄在内核中的存在形式
本节将通过实时内核调试结合逆向查看内核源代码的方式,来探索内核中的句柄结构。句柄结构经历各个windows版本到现在,变化非常大,这里使用的系统是Win10 1809。
Windows系统的句柄表主要有三种,第一种是系统全局PspCidTable句柄表,保存所有进程和线程的句柄,第二种是进程内部句柄表,保存该进程所打开的内核对象句柄,最后一种是系统进程system的全局句柄表,这几种句柄表对应的格式都是一样的。我们主要介绍进程中的句柄表。
_EPROCESS是进程在内核中内核对象结构,每个进程在内核中都有一个这样对应的内核结构来记录进程的信息。EPOCESS结构中的ObjectTable即该进程所有的句柄表,ObjectTable中的TableCode指向handle_table_entrys的首地址。
<center>EPROCESS -> HANDLE_TABLE -> handle_table_entry </center>
TableCode事实上比上图描述的要复杂一点点,句柄表是一个多层结构,TableCode的低两位代表了句柄表的层数。若低两位为0,则TableCode直接指向一个存放handle_table_entrys的表,若低两位为1则TableCode指向一个中间的句柄表,该表里存放的也是"TableCode";同理若TableCode低两位为3,则往后还有两层TableCode表。
从EPROCESS中找到ObjectTable,即句柄表 _HANDLE_TABLE
查看句柄表的TableCode
这里TableCode为0xffffa08c`9ec4f001,最低为1,则表示后续还有一层TableCode表。去除TableCode的低两位,查看一下内容,表里只有两项TableCode,且它们的低两位均为0,表示它们指向的就是handle_table_entrys的基址。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
方便在R3查看句柄信息的小工具
我们可以使用微软官方的工具Process Explorer来查看文件或进程被哪个进程使用着,它的官方文档在这里。
比如我使用CE打开了notepad.exe,使用Process Explorer查找句柄功能,搜索notepad.exe,即可找到对应占用它的进程。这在我们想删除一个文件却提示"该文件已被占用"时非常有用,可以直接搜索到占用它的进程。
句柄在内核中的存在形式
本节将通过实时内核调试结合逆向查看内核源代码的方式,来探索内核中的句柄结构。句柄结构经历各个windows版本到现在,变化非常大,这里使用的系统是Win10 1809。
Windows系统的句柄表主要有三种,第一种是系统全局PspCidTable句柄表,保存所有进程和线程的句柄,第二种是进程内部句柄表,保存该进程所打开的内核对象句柄,最后一种是系统进程system的全局句柄表,这几种句柄表对应的格式都是一样的。我们主要介绍进程中的句柄表。
_EPROCESS是进程在内核中内核对象结构,每个进程在内核中都有一个这样对应的内核结构来记录进程的信息。EPOCESS结构中的ObjectTable即该进程所有的句柄表,ObjectTable中的TableCode指向handle_table_entrys的首地址。
<center>EPROCESS -> HANDLE_TABLE -> handle_table_entry </center>
TableCode事实上比上图描述的要复杂一点点,句柄表是一个多层结构,TableCode的低两位代表了句柄表的层数。若低两位为0,则TableCode直接指向一个存放handle_table_entrys的表,若低两位为1则TableCode指向一个中间的句柄表,该表里存放的也是"TableCode";同理若TableCode低两位为3,则往后还有两层TableCode表。
从EPROCESS中找到ObjectTable,即句柄表 _HANDLE_TABLE
查看句柄表的TableCode
这里TableCode为0xffffa08c`9ec4f001,最低为1,则表示后续还有一层TableCode表。去除TableCode的低两位,查看一下内容,表里只有两项TableCode,且它们的低两位均为0,表示它们指向的就是handle_table_entrys的基址。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
CobaltStrike Beacon生成原理
反编译的一个CobaltStrike4.1版本,所以不在需要重新反编译了。如下是工作目录。decomplie_src是存储着CS反编译的代码,lib目录存储着cobaltstrike4.1.jar文件,作为库文件。out作为编译输出的目录,src存放着所需要的源码文件,也就是decomplie_src源码的子集。这里需要使用的工具是IDEA。
把反编译之后的decomplie_src目录里面的所有java文件复制到项目中的decomplie_src,并把cobaltstrike4.1.jar文件放到项目中的lib目录。
对项目进行设置,添加模块依赖,在路径File-->Project Structure-->Modules-->Dependencies设置SDK。然后在Dependencies这个页面点一下"+"号,添加jar包,完成后点应用。
进入Artifacts-->JAR-->From modules with dependencies,设置一个MAIN CLASS为aggressor.Aggressor,这个值可以在MANIFEST.MF文件中查看。完成之后就是这样的结果。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
反编译的一个CobaltStrike4.1版本,所以不在需要重新反编译了。如下是工作目录。decomplie_src是存储着CS反编译的代码,lib目录存储着cobaltstrike4.1.jar文件,作为库文件。out作为编译输出的目录,src存放着所需要的源码文件,也就是decomplie_src源码的子集。这里需要使用的工具是IDEA。
把反编译之后的decomplie_src目录里面的所有java文件复制到项目中的decomplie_src,并把cobaltstrike4.1.jar文件放到项目中的lib目录。
对项目进行设置,添加模块依赖,在路径File-->Project Structure-->Modules-->Dependencies设置SDK。然后在Dependencies这个页面点一下"+"号,添加jar包,完成后点应用。
进入Artifacts-->JAR-->From modules with dependencies,设置一个MAIN CLASS为aggressor.Aggressor,这个值可以在MANIFEST.MF文件中查看。完成之后就是这样的结果。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
从handle_table_entry找到句柄对应的内核对象
从进程的Handle找到进程内核中的handle_table_entry,本节我们将介绍如何从handle_table_entry找到对应的内核对象,完成从进程的handle找到对应内核对象的过程。
<center>handle_table_entry -> object</center>
在Win7中,handle_table_entry结构中的object直接指向对应的内核对象地址,但在win10并没有这么简单。接着前文的调试内容继续查看ffffa08c`9e0e3010地址处handle_table_entry结构的具体内容。通常我们会去google这些新结构的含义,但这些都是未公开的,假设在google找不到的情况下,我们如何去做呢。
这个时候我们会去IDA查看内核中相关的实现,思路是找到handle与object相关联的函数。如ObFindHandleForObject,ObReferenceObjectByHandle
这里我选择ObFindHandleForObject来介绍,因为它流程相对简单一些。
该函数首先使用ObReferenceProcessHandleTable从EPROCESS的中获取HANDLE_TABLE,然后使用ExEnumHandleTable遍历该HANDLE_TABLE中的handle_table_entry。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
从进程的Handle找到进程内核中的handle_table_entry,本节我们将介绍如何从handle_table_entry找到对应的内核对象,完成从进程的handle找到对应内核对象的过程。
<center>handle_table_entry -> object</center>
在Win7中,handle_table_entry结构中的object直接指向对应的内核对象地址,但在win10并没有这么简单。接着前文的调试内容继续查看ffffa08c`9e0e3010地址处handle_table_entry结构的具体内容。通常我们会去google这些新结构的含义,但这些都是未公开的,假设在google找不到的情况下,我们如何去做呢。
这个时候我们会去IDA查看内核中相关的实现,思路是找到handle与object相关联的函数。如ObFindHandleForObject,ObReferenceObjectByHandle
这里我选择ObFindHandleForObject来介绍,因为它流程相对简单一些。
该函数首先使用ObReferenceProcessHandleTable从EPROCESS的中获取HANDLE_TABLE,然后使用ExEnumHandleTable遍历该HANDLE_TABLE中的handle_table_entry。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke