西湖论剑IoT & X-NUCA && KCTF Q3
前言
杂记,整理一下最近做的题,发完这篇博客就去做项目:D。西湖论剑的题里pwn2、pwn3是可以本地模拟起来的,本来想都做下,不过项目/论文太忙没时间做pwn2了,考的是协议字段的分析,看着还是蛮不错的,以后有空再说好了。
西湖论剑IoT-pwn3
漏洞分析 && 漏洞利用
程序开始的registered函数存在溢出,不过因为程序的text地址是0x000106a0,很多gadget都被截断了,这里的溢出不是很好用。
1 | int __fastcall registered(char *name, char *pwd) |
继续往后看,一个菜单题,在modify中输入0x48字节数据到passwd,这里存在溢出,因其距离main_ebp为0x40。info函数可以将pwd内容输出,这里调试一下发现modify输入时栈上脏数据包含strtol+40的libc地址,通过strncpy拷贝到pwd在info输出即可leak libc,同理可以泄露栈地址。
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
1 | int __fastcall modify(char *a1) |
观察一下长须退出时的汇编,由于我们泄露出了栈地址,因此控制r11(fp)之后再次跳转到0x10f0c来控制sp到输入的开头,进而执行栈上的rop get shell。
1 | .text:00010F08 loc_10F08 ; CODE XREF: main+84↑j |
继续看,play这个子菜单里还包含了add/delete两个函数,存在很明显的UAF漏洞。之前一直以为这个题是awd因此leak不再使用之前的洞。使用double free控制atoi@got为printf@plt泄露出libc,之后printf的返回值将作为atoi的返回值,这里read的范围是0-8因此我们操作sz为4,idx为0-8的chunk修改free@got为system@libc,释放包含binsh的chunk即可(或许使用printf("%12c")
等可以返回大于8的值不过我没测试了)。
原题的环境是2.30,我这里是18.04做的,uClibc的保护机制比x86的少很多,比如malloc到某个块时不会对sz做检查,有兴趣的可以详细看看源码。
1 | int add() |
exp.py
rop的exp。
1 | # encoding=utf-8 |
uaf的exp.
1 | # encoding=utf-8 |
X-NUCA个人赛pwn1
漏洞分析 && 漏洞利用
hello函数中输入you时可以溢出,伪造you[8]
的文件指针,伪造vtable,从而在flcose时执行gadget让esp到达栈上布置的rop,由于scanf的输入有很多gadget截断,这里再read一次,迁移到bss去执行sys_execve系统调用。
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
X-NUCA个人赛pwn2
漏洞分析 && 漏洞利用
这道题很有意思,在团队赛时也有一个类似的洞,这题和pwn1有些相似,但是由于you的输入没有溢出,无法控制you[65]
指针。在xRead函数里有一次输入,sz可控,我们输入超过bss段的sz,从而在v2 += read(0, (char *)&you[66] + v2, v3 - v2);
时返回-1,在之后输入即可控制you[65]
,由于前面已经Leak出了libc,这里直接给个one_gadget(由于远程没打不知道版本,如果gadget都不可行再用点别的gadget去执行rop如pwn1)。
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
exp.py
1 | #coding=utf-8 |
X-NUCA 团队赛 qmips
漏洞分析 && 漏洞利用
mips32大端程序,直接拿超长payload测试可以发现存在溢出,cyclic可以找到溢出长度,search发现除了在栈上存在输入数据,heap上也有,并且heap地址为固定值(qemu-system/qemu-user态测试堆地址均不变),因此在输入前布置shellcode,最后跳转过去执行即可。
这里的shellcode前半部分connect使用shellcraft自带的方法,后面dup2和execve是自己写的shellcode。
exp.py
1 | #!coding=utf8 |
KCTF Q3 pwn1
程序逻辑
这道题有意思的地方在于多了一些逆向的东西,最后一步步地把它变成了自己熟悉的形态。
直接去运行程序会得到Please run in docker!
的输入,让人摸不着头脑.jpg,直接去搜字符串也搜不到。我们看一下check_docker,发现这里通过异或隐藏了上述字符串,那么程序怎样才能正常运行起来呢?
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
我们看一眼.init_array,它先于main函数被执行,可以找到两个函数,我们看一眼fini_func函数,其调用了fini1,fini2调用了fini2
1 | .init_array:0000000000601DD8 90 0A 40 00 00 00 00 00+funcs_401849 dq offset init_func1 ; DATA XREF: LOAD:00000000004000F8↑o |
1 | __int64 fini_func() |
OpenLibc函数打开了libc文件,这里可以写个解码函数进行解码,有个技巧是可以将变量转化为数组,这样方便拷贝c代码出去直接运行即可。
1 | unsigned __int64 __fastcall OpenLibc(void **a1) |
同样的,AntiDebug函数也用相同方法逆出逻辑。
1 | __int64 __fastcall AntiDebug(__int64 a1) |
解码脚本如下:
1 |
|
当进程被调试时,其会在/proc/self/status里增加一个调试进程的pid,赋给TracePid,该函数在这个文件中查找该字段,如果发现就将pid返回给上层函数,否则返回0.
继续看,如果没有调试器,result为1,则调用AutoChange函数。
首先调用mprotect将代码段加上可执行权限,之后使用异或的方式循环修改位于0x4017CD
的汇编代码,我们还是写个c来解码(这部分在上面的test.c里),之后把修改的字节码反汇编一下。
1 | unsigned __int64 __fastcall AutoChange(void **a1) |
反汇编脚本1
2
3
4
5
6
7
8
9
10#coding=utf-8
from pwn import *
context.arch='amd64'
context.os='linux'
c = [0x48, 0x89, 0xf8, 0x48, 0x89, 0xf7, 0x48, 0x89, 0xd6, 0x48, 0x89, 0xca, 0x4c, 0x89, 0xc1, 0xf, 0x5, 0xc9, 0xc3, 0x48, 0x31, 0xc9, 0xc3]
code = ''.join(chr(item) for item in c)
print disasm(code)
最终可以得到这部分修改的汇编,即修改为了syscall函数。我们使用Keypatch将这部分汇编patch过去,并且nop掉AutoChange函数。
1 | ╭─wz@wz-virtual-machine ~/Desktop/CTF/kctf_q3 ‹hexo*› |
patch之后的位置成了一个新的函数.
1 | __int64 __fastcall Syscall(__int64 num, int arg1, int arg2, int arg3) |
到了这里我们再重新看main函数。input函数向bss段写入了0x200的字节
1 | __int64 __fastcall get_input(__int64 a1) |
Mov函数将输入数据拷贝到栈上,这里存在栈溢出,且调用位于stack_val+0x18的函数指针。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26unsigned __int64 __fastcall Mov(unsigned int *stack_val, __int64 input)
{
char v3; // [rsp+10h] [rbp-70h]
unsigned __int64 v4; // [rsp+78h] [rbp-8h]
v4 = __readfsqword(0x28u);
memset(&v3, 0, 0x60uLL);
sub_40177E((__int64)stack_val, (__int64)&v3, input);
(*(void (__fastcall **)(unsigned int *, char *))(*(_QWORD *)stack_val + 0x18LL))(stack_val, &v3);
return __readfsqword(0x28u) ^ v4;
}
//
__int64 __fastcall my_copy(__int64 a1, __int64 a2, __int64 a3)
{
__int64 result; // rax
signed int i; // [rsp+28h] [rbp-4h]
for ( i = 0; ; ++i )
{
result = *(unsigned int *)(a1 + 8);
if ( (signed int)result <= i )
break;
*(_BYTE *)(a2 + i) = *(_BYTE *)(i + a3);
}
return result;
}
这里我们只能通过系统调用执行Sys_execve。需要控制rdi,rsi,rdx,rax。通过csu可以控制edi,rsi,rdx,结合syscall的mov可以间接控制rax,rdi,rsi,再加上xor rcx,rcx; ret
+ mov rdx,rcx
就可以控制rdx最后执行系统调用get shell。
exp.py
1 | #coding=utf-8 |